(function() {
/*
    http://www.JSON.org/json2.js
    2011-02-23

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html


    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.


    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the value

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.
*/

/*jslint evil: true, strict: false, regexp: false */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/

// Create a JSON object only if one does not already exist.
var JSON;
if (!JSON) {
	JSON = {};
}

(function () {
	

	function f(n) {
		// Format integers to have at least two digits.
		return n < 10 ? '0' + n : n;
	}

	if (typeof Date.prototype.toJSON !== 'function') {

		Date.prototype.toJSON = function (key) {

			return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null;
		};

		String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) {
			return this.valueOf();
		};
	}

	var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
		escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
		gap,
		indent,
		meta = { // table of character substitutions
			'\b': '\\b',
			'\t': '\\t',
			'\n': '\\n',
			'\f': '\\f',
			'\r': '\\r',
			'"': '\\"',
			'\\': '\\\\'
		},
		rep;


	function quote(string) {

		// If the string contains no control characters, no quote characters, and no
		// backslash characters, then we can safely slap some quotes around it.
		// Otherwise we must also replace the offending characters with safe escape
		// sequences.

		escapable.lastIndex = 0;
		return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
			var c = meta[a];
			return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
		}) + '"' : '"' + string + '"';
	}


	function str(key, holder) {

		// Produce a string from holder[key].

		var i, // The loop counter.
		k, // The member key.
		v, // The member value.
		length,
		mind = gap,
			partial,
			value = holder[key];

		// If the value has a toJSON method, call it to obtain a replacement value.

		if (value && typeof value === 'object' && typeof value.toJSON === 'function') {
			value = value.toJSON(key);
		}

		// If we were called with a replacer function, then call the replacer to
		// obtain a replacement value.

		if (typeof rep === 'function') {
			value = rep.call(holder, key, value);
		}

		// What happens next depends on the value's type.

		switch (typeof value) {
		case 'string':
			return quote(value);

		case 'number':

			// JSON numbers must be finite. Encode non-finite numbers as null.

			return isFinite(value) ? String(value) : 'null';

		case 'boolean':
		case 'null':

			// If the value is a boolean or null, convert it to a string. Note:
			// typeof null does not produce 'null'. The case is included here in
			// the remote chance that this gets fixed someday.

			return String(value);

			// If the type is 'object', we might be dealing with an object or an array or
			// null.

		case 'object':

			// Due to a specification blunder in ECMAScript, typeof null is 'object',
			// so watch out for that case.

			if (!value) {
				return 'null';
			}

			// Make an array to hold the partial results of stringifying this object value.

			gap += indent;
			partial = [];

			// Is the value an array?

			if (Object.prototype.toString.apply(value) === '[object Array]') {

				// The value is an array. Stringify every element. Use null as a placeholder
				// for non-JSON values.

				length = value.length;
				for (i = 0; i < length; i += 1) {
					partial[i] = str(i, value) || 'null';
				}

				// Join all of the elements together, separated with commas, and wrap them in
				// brackets.

				v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']';
				gap = mind;
				return v;
			}

			// If the replacer is an array, use it to select the members to be stringified.

			if (rep && typeof rep === 'object') {
				length = rep.length;
				for (i = 0; i < length; i += 1) {
					if (typeof rep[i] === 'string') {
						k = rep[i];
						v = str(k, value);
						if (v) {
							partial.push(quote(k) + (gap ? ': ' : ':') + v);
						}
					}
				}
			} else {

				// Otherwise, iterate through all of the keys in the object.

				for (k in value) {
					if (Object.prototype.hasOwnProperty.call(value, k)) {
						v = str(k, value);
						if (v) {
							partial.push(quote(k) + (gap ? ': ' : ':') + v);
						}
					}
				}
			}

			// Join all of the member texts together, separated with commas,
			// and wrap them in braces.

			v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}';
			gap = mind;
			return v;
		}
	}

	// If the JSON object does not yet have a stringify method, give it one.

	if (typeof JSON.stringify !== 'function') {
		JSON.stringify = function (value, replacer, space) {

			// The stringify method takes a value and an optional replacer, and an optional
			// space parameter, and returns a JSON text. The replacer can be a function
			// that can replace values, or an array of strings that will select the keys.
			// A default replacer method can be provided. Use of the space parameter can
			// produce text that is more easily readable.

			var i;
			gap = '';
			indent = '';

			// If the space parameter is a number, make an indent string containing that
			// many spaces.

			if (typeof space === 'number') {
				for (i = 0; i < space; i += 1) {
					indent += ' ';
				}

				// If the space parameter is a string, it will be used as the indent string.

			} else if (typeof space === 'string') {
				indent = space;
			}

			// If there is a replacer, it must be a function or an array.
			// Otherwise, throw an error.

			rep = replacer;
			if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) {
				throw new Error('JSON.stringify');
			}

			// Make a fake root object containing our value under the key of ''.
			// Return the result of stringifying the value.

			return str('', {
				'': value
			});
		};
	}


	// If the JSON object does not yet have a parse method, give it one.

	if (typeof JSON.parse !== 'function') {
		JSON.parse = function (text, reviver) {

			// The parse method takes a text and an optional reviver function, and returns
			// a JavaScript value if the text is a valid JSON text.

			var j;

			function walk(holder, key) {

				// The walk method is used to recursively walk the resulting structure so
				// that modifications can be made.

				var k, v, value = holder[key];
				if (value && typeof value === 'object') {
					for (k in value) {
						if (Object.prototype.hasOwnProperty.call(value, k)) {
							v = walk(value, k);
							if (v !== undefined) {
								value[k] = v;
							} else {
								delete value[k];
							}
						}
					}
				}
				return reviver.call(holder, key, value);
			}


			// Parsing happens in four stages. In the first stage, we replace certain
			// Unicode characters with escape sequences. JavaScript handles many characters
			// incorrectly, either silently deleting them, or treating them as line endings.

			text = String(text);
			cx.lastIndex = 0;
			if (cx.test(text)) {
				text = text.replace(cx, function (a) {
					return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
				});
			}

			// In the second stage, we run the text against regular expressions that look
			// for non-JSON patterns. We are especially concerned with '()' and 'new'
			// because they can cause invocation, and '=' because it can cause mutation.
			// But just to be safe, we want to reject all unexpected forms.

			// We split the second stage into 4 regexp operations in order to work around
			// crippling inefficiencies in IE's and Safari's regexp engines. First we
			// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
			// replace all simple value tokens with ']' characters. Third, we delete all
			// open brackets that follow a colon or comma or that begin the text. Finally,
			// we look to see that the remaining characters are only whitespace or ']' or
			// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

			if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

				// In the third stage we use the eval function to compile the text into a
				// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
				// in JavaScript: it can begin a block or an object literal. We wrap the text
				// in parens to eliminate the ambiguity.

				j = eval('(' + text + ')');

				// In the optional fourth stage, we recursively walk the new structure, passing
				// each name/value pair to a reviver function for possible transformation.

				return typeof reviver === 'function' ? walk({
					'': j
				}, '') : j;
			}

			// If the text is not JSON parseable, then a SyntaxError is thrown.

			throw new SyntaxError('JSON.parse');
		};
	}
}());

define('util/json2', [], function () {
	return JSON;
});

/**
 * @license Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Copyright 2011, Tim Down
 * Licensed under the MIT license.
 * Version: 1.2.1
 * Build date: 8 October 2011
 */
(function () {
	var rangy = (function () {


		var OBJECT = "object",
			FUNCTION = "function",
			UNDEFINED = "undefined";

		var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"];

		var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];

		var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];

		// Subset of TextRange's full set of methods that we're interested in
		var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark", "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"];

		/*----------------------------------------------------------------------------------------------------------------*/

		// Trio of functions taken from Peter Michaux's article:
		// http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
		function isHostMethod(o, p) {
			var t = typeof o[p];
			return t == FUNCTION || ( !! (t == OBJECT && o[p])) || t == "unknown";
		}

		function isHostObject(o, p) {
			return !!(typeof o[p] == OBJECT && o[p]);
		}

		function isHostProperty(o, p) {
			return typeof o[p] != UNDEFINED;
		}

		// Creates a convenience function to save verbose repeated calls to tests functions
		function createMultiplePropertyTest(testFunc) {
			return function (o, props) {
				var i = props.length;
				while (i--) {
					if (!testFunc(o, props[i])) {
						return false;
					}
				}
				return true;
			};
		}

		// Next trio of functions are a convenience to save verbose repeated calls to previous two functions
		var areHostMethods = createMultiplePropertyTest(isHostMethod);
		var areHostObjects = createMultiplePropertyTest(isHostObject);
		var areHostProperties = createMultiplePropertyTest(isHostProperty);

		function isTextRange(range) {
			return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
		}

		var api = {
			version: "1.2.1",
			initialized: false,
			supported: true,

			util: {
				isHostMethod: isHostMethod,
				isHostObject: isHostObject,
				isHostProperty: isHostProperty,
				areHostMethods: areHostMethods,
				areHostObjects: areHostObjects,
				areHostProperties: areHostProperties,
				isTextRange: isTextRange
			},

			features: {},

			modules: {},
			config: {
				alertOnWarn: false,
				preferTextRange: false
			}
		};

		function fail(reason) {
			window.alert("Rangy not supported in your browser. Reason: " + reason);
			api.initialized = true;
			api.supported = false;
		}

		api.fail = fail;

		function warn(msg) {
			var warningMessage = "Rangy warning: " + msg;
			if (api.config.alertOnWarn) {
				window.alert(warningMessage);
			} else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) {
				window.console.log(warningMessage);
			}
		}

		api.warn = warn;

		if ({}.hasOwnProperty) {
			api.util.extend = function (o, props) {
				for (var i in props) {
					if (props.hasOwnProperty(i)) {
						o[i] = props[i];
					}
				}
			};
		} else {
			fail("hasOwnProperty not supported");
		}

		var initListeners = [];
		var moduleInitializers = [];

		// Initialization
		function init() {
			if (api.initialized) {
				return;
			}
			var testRange;
			var implementsDomRange = false,
				implementsTextRange = false;

			// First, perform basic feature tests

			if (isHostMethod(document, "createRange")) {
				testRange = document.createRange();
				if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
					implementsDomRange = true;
				}
				testRange.detach();
			}

			// this was patched due to a performance problem in IE: When creating a TextRange for a body that contains
			// lots of DOM elements, calculating the bounding rectangle (or getting the bounding... properties) is
			// extremely slow (starting with IE8 and up to and including IE11)
//			var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];
			var body = document.createElement("body");

			if (body && isHostMethod(body, "createTextRange")) {
				testRange = body.createTextRange();
				if (isTextRange(testRange)) {
					implementsTextRange = true;
				}
			}

			if (!implementsDomRange && !implementsTextRange) {
				fail("Neither Range nor TextRange are implemented");
			}

			api.initialized = true;
			api.features = {
				implementsDomRange: implementsDomRange,
				implementsTextRange: implementsTextRange
			};

			// Initialize modules and call init listeners
			var allListeners = moduleInitializers.concat(initListeners);
			for (var i = 0, len = allListeners.length; i < len; ++i) {
				try {
					allListeners[i](api);
				} catch (ex) {
					if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
						window.console.log("Init listener threw an exception. Continuing.", ex);
					}

				}
			}
		}

		// Allow external scripts to initialize this library in case it's loaded after the document has loaded
		api.init = init;

		// Execute listener immediately if already initialized
		api.addInitListener = function (listener) {
			if (api.initialized) {
				listener(api);
			} else {
				initListeners.push(listener);
			}
		};

		var createMissingNativeApiListeners = [];

		api.addCreateMissingNativeApiListener = function (listener) {
			createMissingNativeApiListeners.push(listener);
		};

		function createMissingNativeApi(win) {
			win = win || window;
			init();

			// Notify listeners
			for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) {
				createMissingNativeApiListeners[i](win);
			}
		}

		api.createMissingNativeApi = createMissingNativeApi;

		/**
		 * @constructor
		 */
		function Module(name) {
			this.name = name;
			this.initialized = false;
			this.supported = false;
		}

		Module.prototype.fail = function (reason) {
			this.initialized = true;
			this.supported = false;

			throw new Error("Module '" + this.name + "' failed to load: " + reason);
		};

		Module.prototype.warn = function (msg) {
			api.warn("Module " + this.name + ": " + msg);
		};

		Module.prototype.createError = function (msg) {
			return new Error("Error in Rangy " + this.name + " module: " + msg);
		};

		api.createModule = function (name, initFunc) {
			var module = new Module(name);
			api.modules[name] = module;

			moduleInitializers.push(function (api) {
				initFunc(api, module);
				module.initialized = true;
				module.supported = true;
			});
		};

		api.requireModules = function (modules) {
			for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) {
				moduleName = modules[i];
				module = api.modules[moduleName];
				if (!module || !(module instanceof Module)) {
					throw new Error("Module '" + moduleName + "' not found");
				}
				if (!module.supported) {
					throw new Error("Module '" + moduleName + "' not supported");
				}
			}
		};

		/*----------------------------------------------------------------------------------------------------------------*/

		// Wait for document to load before running tests

		var docReady = false;

		var loadHandler = function (e) {

			if (!docReady) {
				docReady = true;
				if (!api.initialized) {
					init();
				}
			}
		};

		// Test whether we have window and document objects that we will need
		if (typeof window == UNDEFINED) {
			fail("No window found");
			return;
		}
		if (typeof document == UNDEFINED) {
			fail("No document found");
			return;
		}

		if (isHostMethod(document, "addEventListener")) {
			document.addEventListener("DOMContentLoaded", loadHandler, false);
		}

		// Add a fallback in case the DOMContentLoaded event isn't supported
		if (isHostMethod(window, "addEventListener")) {
			window.addEventListener("load", loadHandler, false);
		} else if (isHostMethod(window, "attachEvent")) {
			window.attachEvent("onload", loadHandler);
		} else {
			fail("Window does not have required addEventListener or attachEvent method");
		}

		return api;
	})();
	rangy.createModule("DomUtil", function (api, module) {

		var UNDEF = "undefined";
		var util = api.util;

		// Perform feature tests
		if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
			module.fail("document missing a Node creation method");
		}

		if (!util.isHostMethod(document, "getElementsByTagName")) {
			module.fail("document missing getElementsByTagName method");
		}

		var el = document.createElement("div");
		if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
			module.fail("Incomplete Element implementation");
		}

		// innerHTML is required for Range's createContextualFragment method
		if (!util.isHostProperty(el, "innerHTML")) {
			module.fail("Element is missing innerHTML property");
		}

		var textNode = document.createTextNode("test");
		if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || !util.areHostProperties(textNode, ["data"]))) {
			module.fail("Incomplete Text Node implementation");
		}

		/*----------------------------------------------------------------------------------------------------------------*/

		// Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
		// able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
		// contains just the document as a single element and the value searched for is the document.
		var arrayContains =
		/*Array.prototype.indexOf ?
        function(arr, val) {
            return arr.indexOf(val) > -1;
        }:*/

			function (arr, val) {
				var i = arr.length;
				while (i--) {
					if (arr[i] === val) {
						return true;
					}
				}
				return false;
			};

		// Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
		function isHtmlNamespace(node) {
			var ns;
			return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
		}

		function parentElement(node) {
			var parent = node.parentNode;
			return (parent.nodeType == 1) ? parent : null;
		}

		function getNodeIndex(node) {
			var i = 0;
			while ((node = node.previousSibling)) {
				i++;
			}
			return i;
		}

		function getNodeLength(node) {
			var childNodes;
			return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0);
		}

		function getCommonAncestor(node1, node2) {
			var ancestors = [],
				n;
			for (n = node1; n; n = n.parentNode) {
				ancestors.push(n);
			}

			for (n = node2; n; n = n.parentNode) {
				if (arrayContains(ancestors, n)) {
					return n;
				}
			}

			return null;
		}

		function isAncestorOf(ancestor, descendant, selfIsAncestor) {
			var n = selfIsAncestor ? descendant : descendant.parentNode;
			while (n) {
				if (n === ancestor) {
					return true;
				} else {
					n = n.parentNode;
				}
			}
			return false;
		}

		function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
			var p, n = selfIsAncestor ? node : node.parentNode;
			while (n) {
				p = n.parentNode;
				if (p === ancestor) {
					return n;
				}
				n = p;
			}
			return null;
		}

		function isCharacterDataNode(node) {
			var t = node.nodeType;
			return t == 3 || t == 4 || t == 8; // Text, CDataSection or Comment
		}

		function insertAfter(node, precedingNode) {
			var nextNode = precedingNode.nextSibling,
				parent = precedingNode.parentNode;
			if (nextNode) {
				parent.insertBefore(node, nextNode);
			} else {
				parent.appendChild(node);
			}
			return node;
		}

		// Note that we cannot use splitText() because it is bugridden in IE 9.
		function splitDataNode(node, index) {
			var newNode = node.cloneNode(false);
			newNode.deleteData(0, index);
			node.deleteData(index, node.length - index);
			insertAfter(newNode, node);
			return newNode;
		}

		function getDocument(node) {
			if (node.nodeType == 9) {
				return node;
			} else if (typeof node.ownerDocument != UNDEF) {
				return node.ownerDocument;
			} else if (typeof node.document != UNDEF) {
				return node.document;
			} else if (node.parentNode) {
				return getDocument(node.parentNode);
			} else {
				throw new Error("getDocument: no document found for node");
			}
		}

		function getWindow(node) {
			var doc = getDocument(node);
			if (typeof doc.defaultView != UNDEF) {
				return doc.defaultView;
			} else if (typeof doc.parentWindow != UNDEF) {
				return doc.parentWindow;
			} else {
				throw new Error("Cannot get a window object for node");
			}
		}

		function getIframeDocument(iframeEl) {
			if (typeof iframeEl.contentDocument != UNDEF) {
				return iframeEl.contentDocument;
			} else if (typeof iframeEl.contentWindow != UNDEF) {
				return iframeEl.contentWindow.document;
			} else {
				throw new Error("getIframeWindow: No Document object found for iframe element");
			}
		}

		function getIframeWindow(iframeEl) {
			if (typeof iframeEl.contentWindow != UNDEF) {
				return iframeEl.contentWindow;
			} else if (typeof iframeEl.contentDocument != UNDEF) {
				return iframeEl.contentDocument.defaultView;
			} else {
				throw new Error("getIframeWindow: No Window object found for iframe element");
			}
		}

		function getBody(doc) {
			return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
		}

		function getRootContainer(node) {
			var parent;
			while ((parent = node.parentNode)) {
				node = parent;
			}
			return node;
		}

		/**
		 * This is a very ugly workaround for an IE9 issue Before comparing DOM
		 * elements "normalize" them. There are cases, where anchorNode and
		 * focusNode in a nativeselection point to DOM elements with same
		 * parentNode, same previousSibling and same nextSibling, but the nodes
		 * themselves are not the same
		 * If such nodes are compared in the comparePoints method, an error occurs.
		 * To fix this, we move to the previousSibling/nextSibling/parentNode and back, to hopefully get
		 * the "correct" node in the DOM
		 * @param node node to fix
		 * @return normalized node
		 */
		function fixNode(node) {
			if (!node) {
				return;
			}
			if (node.previousSibling) {
				return node.previousSibling.nextSibling;
			} else if (node.nextSibling) {
				return node.nextSibling.previousSibling;
			} else if (node.parentNode) {
				return node.parentNode.firstChild;
			} else {
				return node;
			}
		}

		function comparePoints(nodeA, offsetA, nodeB, offsetB) {
			// fix the nodes before comparing them
			nodeA = fixNode(nodeA);
			nodeB = fixNode(nodeB);
			// See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
			var nodeC, root, childA, childB, n;
			if (nodeA == nodeB) {

				// Case 1: nodes are the same
				return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
			} else if ((nodeC = getClosestAncestorIn(nodeB, nodeA, true))) {

				// Case 2: node C (container B or an ancestor) is a child node of A
				return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
			} else if ((nodeC = getClosestAncestorIn(nodeA, nodeB, true))) {

				// Case 3: node C (container A or an ancestor) is a child node of B
				return getNodeIndex(nodeC) < offsetB ? -1 : 1;
			} else {

				// Case 4: containers are siblings or descendants of siblings
				root = getCommonAncestor(nodeA, nodeB);
				childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
				childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);

				if (childA === childB) {
					// This shouldn't be possible

					throw new Error("comparePoints got to case 4 and childA and childB are the same!");
				} else {
					n = root.firstChild;
					while (n) {
						if (n === childA) {
							return -1;
						} else if (n === childB) {
							return 1;
						}
						n = n.nextSibling;
					}
					throw new Error("Should not be here!");
				}
			}
		}

		function fragmentFromNodeChildren(node) {
			var fragment = getDocument(node).createDocumentFragment(),
				child;
			while ((child = node.firstChild)) {
				fragment.appendChild(child);
			}
			return fragment;
		}

		function inspectNode(node) {
			if (!node) {
				return "[No node]";
			}
			if (isCharacterDataNode(node)) {
				return '"' + node.data + '"';
			} else if (node.nodeType == 1) {
				var idAttr = node.id ? ' id="' + node.id + '"' : "";
				return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]";
			} else {
				return node.nodeName;
			}
		}

		/**
		 * @constructor
		 */
		function NodeIterator(root) {
			this.root = root;
			this._next = root;
		}

		NodeIterator.prototype = {
			_current: null,

			hasNext: function () {
				return !!this._next;
			},

			next: function () {
				var n = this._current = this._next;
				var child, next;
				if (this._current) {
					child = n.firstChild;
					if (child) {
						this._next = child;
					} else {
						next = null;
						while ((n !== this.root) && !(next = n.nextSibling)) {
							n = n.parentNode;
						}
						this._next = next;
					}
				}
				return this._current;
			},

			detach: function () {
				this._current = this._next = this.root = null;
			}
		};

		function createIterator(root) {
			return new NodeIterator(root);
		}

		/**
		 * @constructor
		 */
		function DomPosition(node, offset) {
			this.node = node;
			this.offset = offset;
		}

		DomPosition.prototype = {
			equals: function (pos) {
				return this.node === pos.node & this.offset == pos.offset;
			},

			inspect: function () {
				return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
			}
		};

		/**
		 * @constructor
		 */
		function DOMException(codeName) {
			this.code = this[codeName];
			this.codeName = codeName;
			this.message = "DOMException: " + this.codeName;
		}

		DOMException.prototype = {
			INDEX_SIZE_ERR: 1,
			HIERARCHY_REQUEST_ERR: 3,
			WRONG_DOCUMENT_ERR: 4,
			NO_MODIFICATION_ALLOWED_ERR: 7,
			NOT_FOUND_ERR: 8,
			NOT_SUPPORTED_ERR: 9,
			INVALID_STATE_ERR: 11
		};

		DOMException.prototype.toString = function () {
			return this.message;
		};

		api.dom = {
			arrayContains: arrayContains,
			isHtmlNamespace: isHtmlNamespace,
			parentElement: parentElement,
			getNodeIndex: getNodeIndex,
			getNodeLength: getNodeLength,
			getCommonAncestor: getCommonAncestor,
			isAncestorOf: isAncestorOf,
			getClosestAncestorIn: getClosestAncestorIn,
			isCharacterDataNode: isCharacterDataNode,
			insertAfter: insertAfter,
			splitDataNode: splitDataNode,
			getDocument: getDocument,
			getWindow: getWindow,
			getIframeWindow: getIframeWindow,
			getIframeDocument: getIframeDocument,
			getBody: getBody,
			getRootContainer: getRootContainer,
			comparePoints: comparePoints,
			inspectNode: inspectNode,
			fragmentFromNodeChildren: fragmentFromNodeChildren,
			createIterator: createIterator,
			DomPosition: DomPosition
		};

		api.DOMException = DOMException;
	});
	rangy.createModule("DomRange", function (api, module) {
		api.requireModules(["DomUtil"]);


		var dom = api.dom;
		var DomPosition = dom.DomPosition;
		var DOMException = api.DOMException;

		/*----------------------------------------------------------------------------------------------------------------*/

		// Utility functions

		function isNonTextPartiallySelected(node, range) {
			return (node.nodeType != 3) && (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true));
		}

		function getRangeDocument(range) {
			return dom.getDocument(range.startContainer);
		}

		function dispatchEvent(range, type, args) {
			var listeners = range._listeners[type];
			if (listeners) {
				for (var i = 0, len = listeners.length; i < len; ++i) {
					listeners[i].call(range, {
						target: range,
						args: args
					});
				}
			}
		}

		function getBoundaryBeforeNode(node) {
			return new DomPosition(node.parentNode, dom.getNodeIndex(node));
		}

		function getBoundaryAfterNode(node) {
			return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1);
		}

		function insertNodeAtPosition(node, n, o) {
			var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
			if (dom.isCharacterDataNode(n)) {
				if (o == n.length) {
					dom.insertAfter(node, n);
				} else {
					n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o));
				}
			} else if (o >= n.childNodes.length) {
				n.appendChild(node);
			} else {
				n.insertBefore(node, n.childNodes[o]);
			}
			return firstNodeInserted;
		}

		function cloneSubtree(iterator) {
			var partiallySelected;
			for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next();) {
				partiallySelected = iterator.isPartiallySelectedSubtree();

				node = node.cloneNode(!partiallySelected);
				if (partiallySelected) {
					subIterator = iterator.getSubtreeIterator();
					node.appendChild(cloneSubtree(subIterator));
					subIterator.detach(true);
				}

				if (node.nodeType == 10) { // DocumentType
					throw new DOMException("HIERARCHY_REQUEST_ERR");
				}
				frag.appendChild(node);
			}
			return frag;
		}

		function iterateSubtree(rangeIterator, func, iteratorState) {
			var it, n;
			iteratorState = iteratorState || {
				stop: false
			};
			for (var node, subRangeIterator; node = rangeIterator.next();) {
				//log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node));
				if (rangeIterator.isPartiallySelectedSubtree()) {
					// The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the
					// node selected by the Range.
					if (func(node) === false) {
						iteratorState.stop = true;
						return;
					} else {
						subRangeIterator = rangeIterator.getSubtreeIterator();
						iterateSubtree(subRangeIterator, func, iteratorState);
						subRangeIterator.detach(true);
						if (iteratorState.stop) {
							return;
						}
					}
				} else {
					// The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
					// descendant
					it = dom.createIterator(node);
					while ((n = it.next())) {
						if (func(n) === false) {
							iteratorState.stop = true;
							return;
						}
					}
				}
			}
		}

		function deleteSubtree(iterator) {
			var subIterator;
			while (iterator.next()) {
				if (iterator.isPartiallySelectedSubtree()) {
					subIterator = iterator.getSubtreeIterator();
					deleteSubtree(subIterator);
					subIterator.detach(true);
				} else {
					iterator.remove();
				}
			}
		}

		function extractSubtree(iterator) {

			for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next();) {


				if (iterator.isPartiallySelectedSubtree()) {
					node = node.cloneNode(false);
					subIterator = iterator.getSubtreeIterator();
					node.appendChild(extractSubtree(subIterator));
					subIterator.detach(true);
				} else {
					iterator.remove();
				}
				if (node.nodeType == 10) { // DocumentType
					throw new DOMException("HIERARCHY_REQUEST_ERR");
				}
				frag.appendChild(node);
			}
			return frag;
		}

		function getNodesInRange(range, nodeTypes, filter) {
			//log.info("getNodesInRange, " + nodeTypes.join(","));
			var filterNodeTypes = !! (nodeTypes && nodeTypes.length),
				regex;
			var filterExists = !! filter;
			if (filterNodeTypes) {
				regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
			}

			var nodes = [];
			iterateSubtree(new RangeIterator(range, false), function (node) {
				if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) {
					nodes.push(node);
				}
			});
			return nodes;
		}

		function inspect(range) {
			var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
			return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
		}

		/*----------------------------------------------------------------------------------------------------------------*/

		// RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)

		/**
		 * @constructor
		 */
		function RangeIterator(range, clonePartiallySelectedTextNodes) {
			this.range = range;
			this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;



			if (!range.collapsed) {
				this.sc = range.startContainer;
				this.so = range.startOffset;
				this.ec = range.endContainer;
				this.eo = range.endOffset;
				var root = range.commonAncestorContainer;

				if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) {
					this.isSingleCharacterDataNode = true;
					this._first = this._last = this._next = this.sc;
				} else {
					this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ? this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true);
					this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ? this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true);
				}

			}
		}

		RangeIterator.prototype = {
			_current: null,
			_next: null,
			_first: null,
			_last: null,
			isSingleCharacterDataNode: false,

			reset: function () {
				this._current = null;
				this._next = this._first;
			},

			hasNext: function () {
				return !!this._next;
			},

			next: function () {
				// Move to next node
				var current = this._current = this._next;
				if (current) {
					this._next = (current !== this._last) ? current.nextSibling : null;

					// Check for partially selected text nodes
					if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
						if (current === this.ec) {

							(current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
						}
						if (this._current === this.sc) {

							(current = current.cloneNode(true)).deleteData(0, this.so);
						}
					}
				}

				return current;
			},

			remove: function () {
				var current = this._current,
					start, end;

				if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
					start = (current === this.sc) ? this.so : 0;
					end = (current === this.ec) ? this.eo : current.length;
					if (start != end) {
						current.deleteData(start, end - start);
					}
				} else {
					if (current.parentNode) {
						current.parentNode.removeChild(current);
					} else {

					}
				}
			},

			// Checks if the current node is partially selected
			isPartiallySelectedSubtree: function () {
				var current = this._current;
				return isNonTextPartiallySelected(current, this.range);
			},

			getSubtreeIterator: function () {
				var subRange;
				if (this.isSingleCharacterDataNode) {
					subRange = this.range.cloneRange();
					subRange.collapse();
				} else {
					subRange = new Range(getRangeDocument(this.range));
					var current = this._current;
					var startContainer = current,
						startOffset = 0,
						endContainer = current,
						endOffset = dom.getNodeLength(current);

					if (dom.isAncestorOf(current, this.sc, true)) {
						startContainer = this.sc;
						startOffset = this.so;
					}
					if (dom.isAncestorOf(current, this.ec, true)) {
						endContainer = this.ec;
						endOffset = this.eo;
					}

					updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
				}
				return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
			},

			detach: function (detachRange) {
				if (detachRange) {
					this.range.detach();
				}
				this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
			}
		};

		/*----------------------------------------------------------------------------------------------------------------*/

		// Exceptions

		/**
		 * @constructor
		 */
		function RangeException(codeName) {
			this.code = this[codeName];
			this.codeName = codeName;
			this.message = "RangeException: " + this.codeName;
		}

		RangeException.prototype = {
			BAD_BOUNDARYPOINTS_ERR: 1,
			INVALID_NODE_TYPE_ERR: 2
		};

		RangeException.prototype.toString = function () {
			return this.message;
		};

		/*----------------------------------------------------------------------------------------------------------------*/

		/**
		 * Currently iterates through all nodes in the range on creation until I think of a decent way to do it
		 * TODO: Look into making this a proper iterator, not requiring preloading everything first
		 * @constructor
		 */
		function RangeNodeIterator(range, nodeTypes, filter) {
			this.nodes = getNodesInRange(range, nodeTypes, filter);
			this._next = this.nodes[0];
			this._position = 0;
		}

		RangeNodeIterator.prototype = {
			_current: null,

			hasNext: function () {
				return !!this._next;
			},

			next: function () {
				this._current = this._next;
				this._next = this.nodes[++this._position];
				return this._current;
			},

			detach: function () {
				this._current = this._next = this.nodes = null;
			}
		};

		var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
		var rootContainerNodeTypes = [2, 9, 11];
		var readonlyNodeTypes = [5, 6, 10, 12];
		var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
		var surroundNodeTypes = [1, 3, 4, 5, 7, 8];

		function createAncestorFinder(nodeTypes) {
			return function (node, selfIsAncestor) {
				var t, n = selfIsAncestor ? node : node.parentNode;
				while (n) {
					t = n.nodeType;
					if (dom.arrayContains(nodeTypes, t)) {
						return n;
					}
					n = n.parentNode;
				}
				return null;
			};
		}

		var getRootContainer = dom.getRootContainer;
		var getDocumentOrFragmentContainer = createAncestorFinder([9, 11]);
		var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
		var getDocTypeNotationEntityAncestor = createAncestorFinder([6, 10, 12]);

		function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
			if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
				throw new RangeException("INVALID_NODE_TYPE_ERR");
			}
		}

		function assertNotDetached(range) {
			if (!range.startContainer) {
				throw new DOMException("INVALID_STATE_ERR");
			}
		}

		function assertValidNodeType(node, invalidTypes) {
			if (!dom.arrayContains(invalidTypes, node.nodeType)) {
				throw new RangeException("INVALID_NODE_TYPE_ERR");
			}
		}

		function assertValidOffset(node, offset) {
			if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
				throw new DOMException("INDEX_SIZE_ERR");
			}
		}

		function assertSameDocumentOrFragment(node1, node2) {
			if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
				throw new DOMException("WRONG_DOCUMENT_ERR");
			}
		}

		function assertNodeNotReadOnly(node) {
			if (getReadonlyAncestor(node, true)) {
				throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
			}
		}

		function assertNode(node, codeName) {
			if (!node) {
				throw new DOMException(codeName);
			}
		}

		function isOrphan(node) {
			return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
		}

		function isValidOffset(node, offset) {
			return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length);
		}

		function assertRangeValid(range) {
			assertNotDetached(range);
			if (isOrphan(range.startContainer) || isOrphan(range.endContainer) || !isValidOffset(range.startContainer, range.startOffset) || !isValidOffset(range.endContainer, range.endOffset)) {
				throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
			}
		}

		/*----------------------------------------------------------------------------------------------------------------*/

		// Test the browser's innerHTML support to decide how to implement createContextualFragment
		var styleEl = document.createElement("style");
		var htmlParsingConforms = false;
		try {
			styleEl.innerHTML = "<b>x</b>";
			htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
		} catch (e) {
			// IE 6 and 7 throw
		}

		api.features.htmlParsingConforms = htmlParsingConforms;

		var createContextualFragment = htmlParsingConforms ?

		// Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
		// discussion and base code for this implementation at issue 67.
		// Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
		// Thanks to Aleks Williams.
			function (fragmentStr) {
				// "Let node the context object's start's node."
				var node = this.startContainer;
				var doc = dom.getDocument(node);

				// "If the context object's start's node is null, raise an INVALID_STATE_ERR
				// exception and abort these steps."
				if (!node) {
					throw new DOMException("INVALID_STATE_ERR");
				}

				// "Let element be as follows, depending on node's interface:"
				// Document, Document Fragment: null
				var el = null;

				// "Element: node"
				if (node.nodeType == 1) {
					el = node;

					// "Text, Comment: node's parentElement"
				} else if (dom.isCharacterDataNode(node)) {
					el = dom.parentElement(node);
				}

				// "If either element is null or element's ownerDocument is an HTML document
				// and element's local name is "html" and element's namespace is the HTML
				// namespace"
				if (el === null || (
				el.nodeName == "HTML" && dom.isHtmlNamespace(dom.getDocument(el).documentElement) && dom.isHtmlNamespace(el))) {

					// "let element be a new Element with "body" as its local name and the HTML
					// namespace as its namespace.""
					el = doc.createElement("body");
				} else {
					el = el.cloneNode(false);
				}

				// "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
				// "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
				// "In either case, the algorithm must be invoked with fragment as the input
				// and element as the context element."
				el.innerHTML = fragmentStr;

				// "If this raises an exception, then abort these steps. Otherwise, let new
				// children be the nodes returned."

				// "Let fragment be a new DocumentFragment."
				// "Append all new children to fragment."
				// "Return fragment."
				return dom.fragmentFromNodeChildren(el);
			} :

			// In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
			// previous versions of Rangy used (with the exception of using a body element rather than a div)
			function (fragmentStr) {
				assertNotDetached(this);
				var doc = getRangeDocument(this);
				var el = doc.createElement("body");
				el.innerHTML = fragmentStr;

				return dom.fragmentFromNodeChildren(el);
			};

		/*----------------------------------------------------------------------------------------------------------------*/

		var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", "commonAncestorContainer"];

		var s2s = 0,
			s2e = 1,
			e2e = 2,
			e2s = 3;
		var n_b = 0,
			n_a = 1,
			n_b_a = 2,
			n_i = 3;

		function RangePrototype() {}

		RangePrototype.prototype = {
			attachListener: function (type, listener) {
				this._listeners[type].push(listener);
			},

			compareBoundaryPoints: function (how, range) {
				assertRangeValid(this);
				assertSameDocumentOrFragment(this.startContainer, range.startContainer);

				var nodeA, offsetA, nodeB, offsetB;
				var prefixA = (how == e2s || how == s2s) ? "start" : "end";
				var prefixB = (how == s2e || how == s2s) ? "start" : "end";
				nodeA = this[prefixA + "Container"];
				offsetA = this[prefixA + "Offset"];
				nodeB = range[prefixB + "Container"];
				offsetB = range[prefixB + "Offset"];
				return dom.comparePoints(nodeA, offsetA, nodeB, offsetB);
			},

			insertNode: function (node) {
				assertRangeValid(this);
				assertValidNodeType(node, insertableNodeTypes);
				assertNodeNotReadOnly(this.startContainer);

				if (dom.isAncestorOf(node, this.startContainer, true)) {
					throw new DOMException("HIERARCHY_REQUEST_ERR");
				}

				// No check for whether the container of the start of the Range is of a type that does not allow
				// children of the type of node: the browser's DOM implementation should do this for us when we attempt
				// to add the node

				var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
				this.setStartBefore(firstNodeInserted);
			},

			cloneContents: function () {
				assertRangeValid(this);

				var clone, frag;
				if (this.collapsed) {
					return getRangeDocument(this).createDocumentFragment();
				} else {
					if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) {
						clone = this.startContainer.cloneNode(true);
						clone.data = clone.data.slice(this.startOffset, this.endOffset);
						frag = getRangeDocument(this).createDocumentFragment();
						frag.appendChild(clone);
						return frag;
					} else {
						var iterator = new RangeIterator(this, true);
						clone = cloneSubtree(iterator);
						iterator.detach();
					}
					return clone;
				}
			},

			canSurroundContents: function () {
				assertRangeValid(this);
				assertNodeNotReadOnly(this.startContainer);
				assertNodeNotReadOnly(this.endContainer);

				// Check if the contents can be surrounded. Specifically, this means whether the range partially selects
				// no non-text nodes.
				var iterator = new RangeIterator(this, true);
				var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
				iterator.detach();
				return !boundariesInvalid;
			},

			surroundContents: function (node) {
				assertValidNodeType(node, surroundNodeTypes);

				if (!this.canSurroundContents()) {
					throw new RangeException("BAD_BOUNDARYPOINTS_ERR");
				}

				// Extract the contents
				var content = this.extractContents();

				// Clear the children of the node
				if (node.hasChildNodes()) {
					while (node.lastChild) {
						node.removeChild(node.lastChild);
					}
				}

				// Insert the new node and add the extracted contents
				insertNodeAtPosition(node, this.startContainer, this.startOffset);
				node.appendChild(content);

				this.selectNode(node);
			},

			cloneRange: function () {
				assertRangeValid(this);
				var range = new Range(getRangeDocument(this));
				var i = rangeProperties.length,
					prop;
				while (i--) {
					prop = rangeProperties[i];
					range[prop] = this[prop];
				}
				return range;
			},

			toString: function () {
				assertRangeValid(this);
				var sc = this.startContainer;
				if (sc === this.endContainer && dom.isCharacterDataNode(sc)) {
					return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
				} else {
					var textBits = [],
						iterator = new RangeIterator(this, true);

					iterateSubtree(iterator, function (node) {
						// Accept only text or CDATA nodes, not comments

						if (node.nodeType == 3 || node.nodeType == 4) {
							textBits.push(node.data);
						}
					});
					iterator.detach();
					return textBits.join("");
				}
			},

			// The methods below are all non-standard. The following batch were introduced by Mozilla but have since
			// been removed from Mozilla.

			compareNode: function (node) {
				assertRangeValid(this);

				var parent = node.parentNode;
				var nodeIndex = dom.getNodeIndex(node);

				if (!parent) {
					throw new DOMException("NOT_FOUND_ERR");
				}

				var startComparison = this.comparePoint(parent, nodeIndex),
					endComparison = this.comparePoint(parent, nodeIndex + 1);

				if (startComparison < 0) { // Node starts before
					return (endComparison > 0) ? n_b_a : n_b;
				} else {
					return (endComparison > 0) ? n_a : n_i;
				}
			},

			comparePoint: function (node, offset) {
				assertRangeValid(this);
				assertNode(node, "HIERARCHY_REQUEST_ERR");
				assertSameDocumentOrFragment(node, this.startContainer);

				if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
					return -1;
				} else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
					return 1;
				}
				return 0;
			},

			createContextualFragment: createContextualFragment,

			toHtml: function () {
				assertRangeValid(this);
				var container = getRangeDocument(this).createElement("div");
				container.appendChild(this.cloneContents());
				return container.innerHTML;
			},

			// touchingIsIntersecting determines whether this method considers a node that borders a range intersects
			// with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
			intersectsNode: function (node, touchingIsIntersecting) {
				assertRangeValid(this);
				assertNode(node, "NOT_FOUND_ERR");
				if (dom.getDocument(node) !== getRangeDocument(this)) {
					return false;
				}

				var parent = node.parentNode,
					offset = dom.getNodeIndex(node);
				assertNode(parent, "NOT_FOUND_ERR");

				var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset),
					endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset);

				return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
			},


			isPointInRange: function (node, offset) {
				assertRangeValid(this);
				assertNode(node, "HIERARCHY_REQUEST_ERR");
				assertSameDocumentOrFragment(node, this.startContainer);

				return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
			},

			// The methods below are non-standard and invented by me.

			// Sharing a boundary start-to-end or end-to-start does not count as intersection.
			intersectsRange: function (range, touchingIsIntersecting) {
				assertRangeValid(this);

				if (getRangeDocument(range) != getRangeDocument(this)) {
					throw new DOMException("WRONG_DOCUMENT_ERR");
				}

				var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset),
					endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset);

				return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
			},

			intersection: function (range) {
				if (this.intersectsRange(range)) {
					var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
						endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);

					var intersectionRange = this.cloneRange();

					if (startComparison == -1) {
						intersectionRange.setStart(range.startContainer, range.startOffset);
					}
					if (endComparison == 1) {
						intersectionRange.setEnd(range.endContainer, range.endOffset);
					}
					return intersectionRange;
				}
				return null;
			},

			union: function (range) {
				if (this.intersectsRange(range, true)) {
					var unionRange = this.cloneRange();
					if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
						unionRange.setStart(range.startContainer, range.startOffset);
					}
					if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
						unionRange.setEnd(range.endContainer, range.endOffset);
					}
					return unionRange;
				} else {
					throw new RangeException("Ranges do not intersect");
				}
			},

			containsNode: function (node, allowPartial) {
				if (allowPartial) {
					return this.intersectsNode(node, false);
				} else {
					return this.compareNode(node) == n_i;
				}
			},

			containsNodeContents: function (node) {
				return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0;
			},

			containsRange: function (range) {
				return this.intersection(range).equals(range);
			},

			containsNodeText: function (node) {
				var nodeRange = this.cloneRange();
				nodeRange.selectNode(node);
				var textNodes = nodeRange.getNodes([3]);
				if (textNodes.length > 0) {
					nodeRange.setStart(textNodes[0], 0);
					var lastTextNode = textNodes.pop();
					nodeRange.setEnd(lastTextNode, lastTextNode.length);
					var contains = this.containsRange(nodeRange);
					nodeRange.detach();
					return contains;
				} else {
					return this.containsNodeContents(node);
				}
			},

			createNodeIterator: function (nodeTypes, filter) {
				assertRangeValid(this);
				return new RangeNodeIterator(this, nodeTypes, filter);
			},

			getNodes: function (nodeTypes, filter) {
				assertRangeValid(this);
				return getNodesInRange(this, nodeTypes, filter);
			},

			getDocument: function () {
				return getRangeDocument(this);
			},

			collapseBefore: function (node) {
				assertNotDetached(this);

				this.setEndBefore(node);
				this.collapse(false);
			},

			collapseAfter: function (node) {
				assertNotDetached(this);

				this.setStartAfter(node);
				this.collapse(true);
			},

			getName: function () {
				return "DomRange";
			},

			equals: function (range) {
				return Range.rangesEqual(this, range);
			},

			inspect: function () {
				return inspect(this);
			}
		};

		function copyComparisonConstantsToObject(obj) {
			obj.START_TO_START = s2s;
			obj.START_TO_END = s2e;
			obj.END_TO_END = e2e;
			obj.END_TO_START = e2s;

			obj.NODE_BEFORE = n_b;
			obj.NODE_AFTER = n_a;
			obj.NODE_BEFORE_AND_AFTER = n_b_a;
			obj.NODE_INSIDE = n_i;
		}

		function copyComparisonConstants(constructor) {
			copyComparisonConstantsToObject(constructor);
			copyComparisonConstantsToObject(constructor.prototype);
		}

		function createRangeContentRemover(remover, boundaryUpdater) {
			return function () {
				assertRangeValid(this);

				var sc = this.startContainer,
					so = this.startOffset,
					root = this.commonAncestorContainer;

				var iterator = new RangeIterator(this, true);

				// Work out where to position the range after content removal
				var node, boundary;
				if (sc !== root) {
					node = dom.getClosestAncestorIn(sc, root, true);
					boundary = getBoundaryAfterNode(node);
					sc = boundary.node;
					so = boundary.offset;
				}

				// Check none of the range is read-only
				iterateSubtree(iterator, assertNodeNotReadOnly);

				iterator.reset();

				// Remove the content
				var returnValue = remover(iterator);
				iterator.detach();

				// Move to the new position
				boundaryUpdater(this, sc, so, sc, so);

				return returnValue;
			};
		}

		function createPrototypeRange(constructor, boundaryUpdater, detacher) {
			function createBeforeAfterNodeSetter(isBefore, isStart) {
				return function (node) {
					assertNotDetached(this);
					assertValidNodeType(node, beforeAfterNodeTypes);
					assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);

					var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
					(isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
				};
			}

			function setRangeStart(range, node, offset) {
				var ec = range.endContainer,
					eo = range.endOffset;
				if (node !== range.startContainer || offset !== this.startOffset) {
					// Check the root containers of the range and the new boundary, and also check whether the new boundary
					// is after the current end. In either case, collapse the range to the new position
					if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) {
						ec = node;
						eo = offset;
					}
					boundaryUpdater(range, node, offset, ec, eo);
				}
			}

			function setRangeEnd(range, node, offset) {
				var sc = range.startContainer,
					so = range.startOffset;
				if (node !== range.endContainer || offset !== this.endOffset) {
					// Check the root containers of the range and the new boundary, and also check whether the new boundary
					// is after the current end. In either case, collapse the range to the new position
					if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) {
						sc = node;
						so = offset;
					}
					boundaryUpdater(range, sc, so, node, offset);
				}
			}

			function setRangeStartAndEnd(range, node, offset) {
				if (node !== range.startContainer || offset !== this.startOffset || node !== range.endContainer || offset !== this.endOffset) {
					boundaryUpdater(range, node, offset, node, offset);
				}
			}

			constructor.prototype = new RangePrototype();

			api.util.extend(constructor.prototype, {
				setStart: function (node, offset) {
					assertNotDetached(this);
					assertNoDocTypeNotationEntityAncestor(node, true);
					assertValidOffset(node, offset);

					setRangeStart(this, node, offset);
				},

				setEnd: function (node, offset) {
					assertNotDetached(this);
					assertNoDocTypeNotationEntityAncestor(node, true);
					assertValidOffset(node, offset);

					setRangeEnd(this, node, offset);
				},

				setStartBefore: createBeforeAfterNodeSetter(true, true),
				setStartAfter: createBeforeAfterNodeSetter(false, true),
				setEndBefore: createBeforeAfterNodeSetter(true, false),
				setEndAfter: createBeforeAfterNodeSetter(false, false),

				collapse: function (isStart) {
					assertRangeValid(this);
					if (isStart) {
						boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
					} else {
						boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
					}
				},

				selectNodeContents: function (node) {
					// This doesn't seem well specified: the spec talks only about selecting the node's contents, which
					// could be taken to mean only its children. However, browsers implement this the same as selectNode for
					// text nodes, so I shall do likewise
					assertNotDetached(this);
					assertNoDocTypeNotationEntityAncestor(node, true);

					boundaryUpdater(this, node, 0, node, dom.getNodeLength(node));
				},

				selectNode: function (node) {
					assertNotDetached(this);
					assertNoDocTypeNotationEntityAncestor(node, false);
					assertValidNodeType(node, beforeAfterNodeTypes);

					var start = getBoundaryBeforeNode(node),
						end = getBoundaryAfterNode(node);
					boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
				},

				extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),

				deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),

				canSurroundContents: function () {
					assertRangeValid(this);
					assertNodeNotReadOnly(this.startContainer);
					assertNodeNotReadOnly(this.endContainer);

					// Check if the contents can be surrounded. Specifically, this means whether the range partially selects
					// no non-text nodes.
					var iterator = new RangeIterator(this, true);
					var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
					iterator.detach();
					return !boundariesInvalid;
				},

				detach: function () {
					detacher(this);
				},

				splitBoundaries: function () {
					assertRangeValid(this);


					var sc = this.startContainer,
						so = this.startOffset,
						ec = this.endContainer,
						eo = this.endOffset;
					var startEndSame = (sc === ec);

					if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
						dom.splitDataNode(ec, eo);

					}

					if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) {

						sc = dom.splitDataNode(sc, so);
						if (startEndSame) {
							eo -= so;
							ec = sc;
						} else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) {
							eo++;
						}
						so = 0;

					}
					boundaryUpdater(this, sc, so, ec, eo);
				},

				normalizeBoundaries: function () {
					assertRangeValid(this);

					var sc = this.startContainer,
						so = this.startOffset,
						ec = this.endContainer,
						eo = this.endOffset;

					var mergeForward = function (node) {
						var sibling = node.nextSibling;
						if (sibling && sibling.nodeType == node.nodeType) {
							ec = node;
							eo = node.length;
							node.appendData(sibling.data);
							sibling.parentNode.removeChild(sibling);
						}
					};

					var mergeBackward = function (node) {
						var sibling = node.previousSibling;
						if (sibling && sibling.nodeType == node.nodeType) {
							sc = node;
							var nodeLength = node.length;
							so = sibling.length;
							node.insertData(0, sibling.data);
							sibling.parentNode.removeChild(sibling);
							if (sc == ec) {
								eo += so;
								ec = sc;
							} else if (ec == node.parentNode) {
								var nodeIndex = dom.getNodeIndex(node);
								if (eo == nodeIndex) {
									ec = node;
									eo = nodeLength;
								} else if (eo > nodeIndex) {
									eo--;
								}
							}
						}
					};

					var normalizeStart = true;

					if (dom.isCharacterDataNode(ec)) {
						if (ec.length == eo) {
							mergeForward(ec);
						}
					} else {
						if (eo > 0) {
							var endNode = ec.childNodes[eo - 1];
							if (endNode && dom.isCharacterDataNode(endNode)) {
								mergeForward(endNode);
							}
						}
						normalizeStart = !this.collapsed;
					}

					if (normalizeStart) {
						if (dom.isCharacterDataNode(sc)) {
							if (so == 0) {
								mergeBackward(sc);
							}
						} else {
							if (so < sc.childNodes.length) {
								var startNode = sc.childNodes[so];
								if (startNode && dom.isCharacterDataNode(startNode)) {
									mergeBackward(startNode);
								}
							}
						}
					} else {
						sc = ec;
						so = eo;
					}

					boundaryUpdater(this, sc, so, ec, eo);
				},

				collapseToPoint: function (node, offset) {
					assertNotDetached(this);

					assertNoDocTypeNotationEntityAncestor(node, true);
					assertValidOffset(node, offset);

					setRangeStartAndEnd(this, node, offset);
				}
			});

			copyComparisonConstants(constructor);
		}

		/*----------------------------------------------------------------------------------------------------------------*/

		// Updates commonAncestorContainer and collapsed after boundary change
		function updateCollapsedAndCommonAncestor(range) {
			range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
			range.commonAncestorContainer = range.collapsed ? range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
		}

		function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
			var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset);
			var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset);

			range.startContainer = startContainer;
			range.startOffset = startOffset;
			range.endContainer = endContainer;
			range.endOffset = endOffset;

			updateCollapsedAndCommonAncestor(range);
			dispatchEvent(range, "boundarychange", {
				startMoved: startMoved,
				endMoved: endMoved
			});
		}

		function detach(range) {
			assertNotDetached(range);
			range.startContainer = range.startOffset = range.endContainer = range.endOffset = null;
			range.collapsed = range.commonAncestorContainer = null;
			dispatchEvent(range, "detach", null);
			range._listeners = null;
		}

		/**
		 * @constructor
		 */
		function Range(doc) {
			this.startContainer = doc;
			this.startOffset = 0;
			this.endContainer = doc;
			this.endOffset = 0;
			this._listeners = {
				boundarychange: [],
				detach: []
			};
			updateCollapsedAndCommonAncestor(this);
		}

		createPrototypeRange(Range, updateBoundaries, detach);

		api.rangePrototype = RangePrototype.prototype;

		Range.rangeProperties = rangeProperties;
		Range.RangeIterator = RangeIterator;
		Range.copyComparisonConstants = copyComparisonConstants;
		Range.createPrototypeRange = createPrototypeRange;
		Range.inspect = inspect;
		Range.getRangeDocument = getRangeDocument;
		Range.rangesEqual = function (r1, r2) {
			return r1.startContainer === r2.startContainer && r1.startOffset === r2.startOffset && r1.endContainer === r2.endContainer && r1.endOffset === r2.endOffset;
		};

		api.DomRange = Range;
		api.RangeException = RangeException;
	});
	rangy.createModule("WrappedRange", function (api, module) {
		api.requireModules(["DomUtil", "DomRange"]);

		/**
		 * @constructor
		 */
		var WrappedRange;
		var dom = api.dom;
		var DomPosition = dom.DomPosition;
		var DomRange = api.DomRange;



		/*----------------------------------------------------------------------------------------------------------------*/

		/*
    This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
    method. For example, in the following (where pipes denote the selection boundaries):

    <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>

    var range = document.selection.createRange();
    alert(range.parentElement().id); // Should alert "ul" but alerts "b"

    This method returns the common ancestor node of the following:
    - the parentElement() of the textRange
    - the parentElement() of the textRange after calling collapse(true)
    - the parentElement() of the textRange after calling collapse(false)
     */
		function getTextRangeContainerElement(textRange) {
			var parentEl = textRange.parentElement();

			var range = textRange.duplicate();
			range.collapse(true);
			var startEl = range.parentElement();
			range = textRange.duplicate();
			range.collapse(false);
			var endEl = range.parentElement();
			var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);

			return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
		}

		function textRangeIsCollapsed(textRange) {
			return textRange.compareEndPoints("StartToEnd", textRange) == 0;
		}

		// Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as
		// an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has
		// grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling
		// for inputs and images, plus optimizations.
		function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) {
			var workingRange = textRange.duplicate();

			workingRange.collapse(isStart);
			var containerElement = workingRange.parentElement();

			// Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
			// check for that
			// TODO: Find out when. Workaround for wholeRangeContainerElement may break this
			if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) {
				containerElement = wholeRangeContainerElement;

			}



			// Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
			// similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
			if (!containerElement.canHaveHTML) {
				return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
			}

			var workingNode = dom.getDocument(containerElement).createElement("span");

			// Workaround for HTML5 Shiv's insane violation of
			// document.createElement(). See Rangy issue 104 and HTML 5 Shiv issue
			// 64: https://github.com/aFarkas/html5shiv/issues/64
			if (workingNode.parentNode) {
				workingNode.parentNode.removeChild(workingNode);
			}

			var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
			var previousNode, nextNode, boundaryPosition, boundaryNode;

			// Move the working range through the container's children, starting at the end and working backwards, until the
			// working range reaches or goes past the boundary we're interested in
			do {
				containerElement.insertBefore(workingNode, workingNode.previousSibling);
				workingRange.moveToElementText(workingNode);
			} while ((comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 && workingNode.previousSibling);

			// We've now reached or gone past the boundary of the text range we're interested in
			// so have identified the node we want
			boundaryNode = workingNode.nextSibling;

			if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) {
				// This is a character data node (text, comment, cdata). The working range is collapsed at the start of the
				// node containing the text range's boundary, so we move the end of the working range to the boundary point
				// and measure the length of its text to get the boundary's offset within the node.
				workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);


				var offset;

				if (/[\r\n]/.test(boundaryNode.data)) {
					/*
                For the particular case of a boundary within a text node containing line breaks (within a <pre> element,
                for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:

                - Each line break is represented as \r in the text node's data/nodeValue properties
                - Each line break is represented as \r\n in the TextRange's 'text' property
                - The 'text' property of the TextRange does not contain trailing line breaks

                To get round the problem presented by the final fact above, we can use the fact that TextRange's
                moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
                the same as the number of characters it was instructed to move. The simplest approach is to use this to
                store the characters moved when moving both the start and end of the range to the start of the document
                body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
                However, this is extremely slow when the document is large and the range is near the end of it. Clearly
                doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
                problem.

                Another approach that works is to use moveStart() to move the start boundary of the range up to the end
                boundary one character at a time and incrementing a counter with the value returned by the moveStart()
                call. However, the check for whether the start boundary has reached the end boundary is expensive, so
                this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
                the range within the document).

                The method below is a hybrid of the two methods above. It uses the fact that a string containing the
                TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
                text of the TextRange, so the start of the range is moved that length initially and then a character at
                a time to make up for any trailing line breaks not contained in the 'text' property. This has good
                performance in most situations compared to the previous two methods.
                */
					var tempRange = workingRange.duplicate();
					var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;

					offset = tempRange.moveStart("character", rangeLength);
					while ((comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
						offset++;
						tempRange.moveStart("character", 1);
					}
				} else {
					// IE7 sometimes has weird workingranges that apparently do not start in the workingNode any more, but in
					// some kind of phantom paragraph, that cannot be found in the DOM.
					// in such situations, the workingRange.text no longer is a substring at the start of the boundaryNode.data
					// If we find such a situation, we skip all characters at the start of the workingRange.data, that are not
					// at the start of the boundaryNode.data.
					// Before comparing, we have to replace all nbsp with normal spaces
					var wrText = workingRange.text.replace(/\u00a0/g, " ");
					var bnText = boundaryNode.data.replace(/\u00a0/g, " ");
					if (bnText.indexOf(wrText) !== 0) {
						while (wrText.length > 0 && bnText.indexOf(wrText) !== 0) {
							wrText = wrText.substr(1);
						}
						offset = wrText.length;
					} else {
						offset = workingRange.text.length;
					}
				}
				boundaryPosition = new DomPosition(boundaryNode, offset);
			} else {


				// If the boundary immediately follows a character data node and this is the end boundary, we should favour
				// a position within that, and likewise for a start boundary preceding a character data node
				previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
				nextNode = (isCollapsed || isStart) && workingNode.nextSibling;



				if (nextNode && dom.isCharacterDataNode(nextNode)) {
					boundaryPosition = new DomPosition(nextNode, 0);
				} else if (previousNode && dom.isCharacterDataNode(previousNode)) {
					boundaryPosition = new DomPosition(previousNode, previousNode.length);
				} else {
					boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
				}
			}

			// Clean up
			workingNode.parentNode.removeChild(workingNode);

			return boundaryPosition;
		}

		// Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
		// This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
		// (http://code.google.com/p/ierange/)
		function createBoundaryTextRange(boundaryPosition, isStart) {
			var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
			var doc = dom.getDocument(boundaryPosition.node);
			var workingNode, childNodes, workingRange = doc.body.createTextRange();
			var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);

			if (nodeIsDataNode) {
				boundaryNode = boundaryPosition.node;
				boundaryParent = boundaryNode.parentNode;
			} else {
				childNodes = boundaryPosition.node.childNodes;
				boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
				boundaryParent = boundaryPosition.node;
			}

			// Position the range immediately before the node containing the boundary
			workingNode = doc.createElement("span");

			// Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
			// element rather than immediately before or after it, which is what we want
			workingNode.innerHTML = "&#feff;";

			// insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
			// for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
			if (boundaryNode) {
				boundaryParent.insertBefore(workingNode, boundaryNode);
			} else {
				boundaryParent.appendChild(workingNode);
			}

			try {
				workingRange.moveToElementText(workingNode);
				workingRange.collapse(!isStart);
			} catch (err) {
				// @todo window.console.log('problem with moveToElementText');
				//return false;
			}

			// Clean up
			boundaryParent.removeChild(workingNode);

			// Move the working range to the text offset, if required
			if (nodeIsDataNode) {
				workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
			}

			return workingRange;
		}

		/*----------------------------------------------------------------------------------------------------------------*/

		if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
			// This is a wrapper around the browser's native DOM Range. It has two aims:
			// - Provide workarounds for specific browser bugs
			// - provide convenient extensions, which are inherited from Rangy's DomRange

			(function () {
				var rangeProto;
				var rangeProperties = DomRange.rangeProperties;
				var canSetRangeStartAfterEnd;

				function updateRangeProperties(range) {
					var i = rangeProperties.length,
						prop;
					while (i--) {
						prop = rangeProperties[i];
						range[prop] = range.nativeRange[prop];
					}
				}

				function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
					var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
					var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);

					// Always set both boundaries for the benefit of IE9 (see issue 35)
					if (startMoved || endMoved) {
						range.setEnd(endContainer, endOffset);
						range.setStart(startContainer, startOffset);
					}
				}

				function detach(range) {
					range.nativeRange.detach();
					range.detached = true;
					var i = rangeProperties.length,
						prop;
					while (i--) {
						prop = rangeProperties[i];
						range[prop] = null;
					}
				}

				var createBeforeAfterNodeSetter;

				WrappedRange = function (range) {
					if (!range) {
						throw new Error("Range must be specified");
					}
					this.nativeRange = range;
					updateRangeProperties(this);
				};

				DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);

				rangeProto = WrappedRange.prototype;

				rangeProto.selectNode = function (node) {
					this.nativeRange.selectNode(node);
					updateRangeProperties(this);
				};

				rangeProto.deleteContents = function () {
					this.nativeRange.deleteContents();
					updateRangeProperties(this);
				};

				rangeProto.extractContents = function () {
					var frag = this.nativeRange.extractContents();
					updateRangeProperties(this);
					return frag;
				};

				rangeProto.cloneContents = function () {
					return this.nativeRange.cloneContents();
				};

				// TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
				// present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
				// insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
				// insertNode, which works but is almost certainly slower than the native implementation.
				/*
            rangeProto.insertNode = function(node) {
                this.nativeRange.insertNode(node);
                updateRangeProperties(this);
            };
*/

				rangeProto.surroundContents = function (node) {
					this.nativeRange.surroundContents(node);
					updateRangeProperties(this);
				};

				rangeProto.collapse = function (isStart) {
					this.nativeRange.collapse(isStart);
					updateRangeProperties(this);
				};

				rangeProto.cloneRange = function () {
					return new WrappedRange(this.nativeRange.cloneRange());
				};

				rangeProto.refresh = function () {
					updateRangeProperties(this);
				};

				rangeProto.toString = function () {
					return this.nativeRange.toString();
				};

				// Create test range and node for feature detection

				var testTextNode = document.createTextNode("test");
				dom.getBody(document).appendChild(testTextNode);
				var range = document.createRange();

				/*--------------------------------------------------------------------------------------------------------*/

				// Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
				// correct for it

				range.setStart(testTextNode, 0);
				range.setEnd(testTextNode, 0);

				try {
					range.setStart(testTextNode, 1);
					canSetRangeStartAfterEnd = true;

					rangeProto.setStart = function (node, offset) {
						this.nativeRange.setStart(node, offset);
						updateRangeProperties(this);
					};

					rangeProto.setEnd = function (node, offset) {
						this.nativeRange.setEnd(node, offset);
						updateRangeProperties(this);
					};

					createBeforeAfterNodeSetter = function (name) {
						return function (node) {
							this.nativeRange[name](node);
							updateRangeProperties(this);
						};
					};

				} catch (ex) {


					canSetRangeStartAfterEnd = false;

					rangeProto.setStart = function (node, offset) {
						try {
							this.nativeRange.setStart(node, offset);
						} catch (ex) {
							this.nativeRange.setEnd(node, offset);
							this.nativeRange.setStart(node, offset);
						}
						updateRangeProperties(this);
					};

					rangeProto.setEnd = function (node, offset) {
						try {
							this.nativeRange.setEnd(node, offset);
						} catch (ex) {
							this.nativeRange.setStart(node, offset);
							this.nativeRange.setEnd(node, offset);
						}
						updateRangeProperties(this);
					};

					createBeforeAfterNodeSetter = function (name, oppositeName) {
						return function (node) {
							try {
								this.nativeRange[name](node);
							} catch (ex) {
								this.nativeRange[oppositeName](node);
								this.nativeRange[name](node);
							}
							updateRangeProperties(this);
						};
					};
				}

				rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
				rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
				rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
				rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");

				/*--------------------------------------------------------------------------------------------------------*/

				// Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
				// the 0th character of the text node
				range.selectNodeContents(testTextNode);
				if (range.startContainer == testTextNode && range.endContainer == testTextNode && range.startOffset == 0 && range.endOffset == testTextNode.length) {
					rangeProto.selectNodeContents = function (node) {
						this.nativeRange.selectNodeContents(node);
						updateRangeProperties(this);
					};
				} else {
					rangeProto.selectNodeContents = function (node) {
						this.setStart(node, 0);
						this.setEnd(node, DomRange.getEndOffset(node));
					};
				}

				/*--------------------------------------------------------------------------------------------------------*/

				// Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
				// START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738

				range.selectNodeContents(testTextNode);
				range.setEnd(testTextNode, 3);

				var range2 = document.createRange();
				range2.selectNodeContents(testTextNode);
				range2.setEnd(testTextNode, 4);
				range2.setStart(testTextNode, 2);

				if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 & range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
					// This is the wrong way round, so correct for it


					rangeProto.compareBoundaryPoints = function (type, range) {
						range = range.nativeRange || range;
						if (type == range.START_TO_END) {
							type = range.END_TO_START;
						} else if (type == range.END_TO_START) {
							type = range.START_TO_END;
						}
						return this.nativeRange.compareBoundaryPoints(type, range);
					};
				} else {
					rangeProto.compareBoundaryPoints = function (type, range) {
						return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
					};
				}

				/*--------------------------------------------------------------------------------------------------------*/

				// Test for existence of createContextualFragment and delegate to it if it exists
				if (api.util.isHostMethod(range, "createContextualFragment")) {
					rangeProto.createContextualFragment = function (fragmentStr) {
						return this.nativeRange.createContextualFragment(fragmentStr);
					};
				}

				/*--------------------------------------------------------------------------------------------------------*/

				// Clean up
				dom.getBody(document).removeChild(testTextNode);
				range.detach();
				range2.detach();
			})();

			api.createNativeRange = function (doc) {
				doc = doc || document;
				return doc.createRange();
			};
		} else if (api.features.implementsTextRange) {
			// This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
			// prototype

			WrappedRange = function (textRange) {
				this.textRange = textRange;
				this.refresh();
			};

			WrappedRange.prototype = new DomRange(document);

			WrappedRange.prototype.refresh = function () {
				var start, end;

				// TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
				var rangeContainerElement = getTextRangeContainerElement(this.textRange);

				if (textRangeIsCollapsed(this.textRange)) {
					end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);
				} else {

					start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
					end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);
				}

				this.setStart(start.node, start.offset);
				this.setEnd(end.node, end.offset);
			};

			DomRange.copyComparisonConstants(WrappedRange);

			// Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
			var globalObj = (function () {
				return this;
			})();
			if (typeof globalObj.Range == "undefined") {
				globalObj.Range = WrappedRange;
			}

			api.createNativeRange = function (doc) {
				doc = doc || document;
				return doc.body.createTextRange();
			};
		}

		if (api.features.implementsTextRange) {
			WrappedRange.rangeToTextRange = function (range) {
				if (range.collapsed) {
					var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
					return tr;
					//return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
				} else {
					var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
					var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
					var textRange = dom.getDocument(range.startContainer).body.createTextRange();
					textRange.setEndPoint("StartToStart", startRange);
					textRange.setEndPoint("EndToEnd", endRange);
					return textRange;
				}
			};
		}

		WrappedRange.prototype.getName = function () {
			return "WrappedRange";
		};

		api.WrappedRange = WrappedRange;

		api.createRange = function (doc) {
			doc = doc || document;
			return new WrappedRange(api.createNativeRange(doc));
		};

		api.createRangyRange = function (doc) {
			doc = doc || document;
			return new DomRange(doc);
		};

		api.createIframeRange = function (iframeEl) {
			return api.createRange(dom.getIframeDocument(iframeEl));
		};

		api.createIframeRangyRange = function (iframeEl) {
			return api.createRangyRange(dom.getIframeDocument(iframeEl));
		};

		api.addCreateMissingNativeApiListener(function (win) {
			var doc = win.document;
			if (typeof doc.createRange == "undefined") {
				doc.createRange = function () {
					return api.createRange(this);
				};
			}
			doc = win = null;
		});
	});
	rangy.createModule("WrappedSelection", function (api, module) {
		// This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
		// spec (http://html5.org/specs/dom-range.html)

		api.requireModules(["DomUtil", "DomRange", "WrappedRange"]);

		api.config.checkSelectionRanges = true;

		var BOOLEAN = "boolean",
			windowPropertyName = "_rangySelection",
			dom = api.dom,
			util = api.util,
			DomRange = api.DomRange,
			WrappedRange = api.WrappedRange,
			DOMException = api.DOMException,
			DomPosition = dom.DomPosition,
			getSelection,
			selectionIsCollapsed,
			CONTROL = "Control";



		function getWinSelection(winParam) {
			return (winParam || window).getSelection();
		}

		function getDocSelection(winParam) {
			return (winParam || window).document.selection;
		}

		// Test for the Range/TextRange and Selection features required
		// Test for ability to retrieve selection
		var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),
			implementsDocSelection = api.util.isHostObject(document, "selection");

		var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);

		if (useDocumentSelection) {
			getSelection = getDocSelection;
			api.isSelectionValid = function (winParam) {
				var doc = (winParam || window).document,
					nativeSel = doc.selection;

				// Check whether the selection TextRange is actually contained within the correct document
				return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
			};
		} else if (implementsWinGetSelection) {
			getSelection = getWinSelection;
			api.isSelectionValid = function () {
				return true;
			};
		} else {
			module.fail("Neither document.selection or window.getSelection() detected.");
		}

		api.getNativeSelection = getSelection;

		var testSelection = getSelection();
		var testRange = api.createNativeRange(document);
		var body = dom.getBody(document);

		// Obtaining a range from a selection
		var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] && util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));
		api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;

		// Test for existence of native selection extend() method
		var selectionHasExtend = util.isHostMethod(testSelection, "extend");
		api.features.selectionHasExtend = selectionHasExtend;

		// Test if rangeCount exists
		var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
		api.features.selectionHasRangeCount = selectionHasRangeCount;

		var selectionSupportsMultipleRanges = false;
		var collapsedNonEditableSelectionsSupported = true;

		if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) && typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {

			(function () {
				var iframe = document.createElement("iframe");
				body.appendChild(iframe);

				var iframeDoc = dom.getIframeDocument(iframe);
				iframeDoc.open();
				iframeDoc.write("<html><head></head><body>12</body></html>");
				iframeDoc.close();

				var sel = dom.getIframeWindow(iframe).getSelection();
				var docEl = iframeDoc.documentElement;
				var iframeBody = docEl.lastChild,
					textNode = iframeBody.firstChild;

				// Test whether the native selection will allow a collapsed selection within a non-editable element
				var r1 = iframeDoc.createRange();
				r1.setStart(textNode, 1);
				r1.collapse(true);
				sel.addRange(r1);
				collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
				sel.removeAllRanges();

				// Test whether the native selection is capable of supporting multiple ranges
				var r2 = r1.cloneRange();
				r1.setStart(textNode, 0);
				r2.setEnd(textNode, 2);
				sel.addRange(r1);
				sel.addRange(r2);

				selectionSupportsMultipleRanges = (sel.rangeCount == 2);

				// Clean up
				r1.detach();
				r2.detach();

				body.removeChild(iframe);
			})();
		}

		api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
		api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;

		// ControlRanges
		var implementsControlRange = false,
			testControlRange;

		if (body && util.isHostMethod(body, "createControlRange")) {
			testControlRange = body.createControlRange();
			if (util.areHostProperties(testControlRange, ["item", "add"])) {
				implementsControlRange = true;
			}
		}
		api.features.implementsControlRange = implementsControlRange;

		// Selection collapsedness
		if (selectionHasAnchorAndFocus) {
			selectionIsCollapsed = function (sel) {
				return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
			};
		} else {
			selectionIsCollapsed = function (sel) {
				return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
			};
		}

		function updateAnchorAndFocusFromRange(sel, range, backwards) {
			var anchorPrefix = backwards ? "end" : "start",
				focusPrefix = backwards ? "start" : "end";
			sel.anchorNode = range[anchorPrefix + "Container"];
			sel.anchorOffset = range[anchorPrefix + "Offset"];
			sel.focusNode = range[focusPrefix + "Container"];
			sel.focusOffset = range[focusPrefix + "Offset"];
		}

		function updateAnchorAndFocusFromNativeSelection(sel) {
			var nativeSel = sel.nativeSelection;
			sel.anchorNode = nativeSel.anchorNode;
			sel.anchorOffset = nativeSel.anchorOffset;
			sel.focusNode = nativeSel.focusNode;
			sel.focusOffset = nativeSel.focusOffset;
		}

		function updateEmptySelection(sel) {
			sel.anchorNode = sel.focusNode = null;
			sel.anchorOffset = sel.focusOffset = 0;
			sel.rangeCount = 0;
			sel.isCollapsed = true;
			sel._ranges.length = 0;
		}

		function getNativeRange(range) {
			var nativeRange;
			if (range instanceof DomRange) {
				nativeRange = range._selectionNativeRange;
				if (!nativeRange) {
					nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));
					nativeRange.setEnd(range.endContainer, range.endOffset);
					nativeRange.setStart(range.startContainer, range.startOffset);
					range._selectionNativeRange = nativeRange;
					range.attachListener("detach", function () {

						this._selectionNativeRange = null;
					});
				}
			} else if (range instanceof WrappedRange) {
				nativeRange = range.nativeRange;
			} else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
				nativeRange = range;
			}
			return nativeRange;
		}

		function rangeContainsSingleElement(rangeNodes) {
			if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
				return false;
			}
			for (var i = 1, len = rangeNodes.length; i < len; ++i) {
				if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
					return false;
				}
			}
			return true;
		}

		function getSingleElementFromRange(range) {
			var nodes = range.getNodes();
			if (!rangeContainsSingleElement(nodes)) {
				throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
			}
			return nodes[0];
		}

		function isTextRange(range) {
			return !!range && typeof range.text != "undefined";
		}

		function updateFromTextRange(sel, range) {
			// Create a Range from the selected TextRange
			var wrappedRange = new WrappedRange(range);
			sel._ranges = [wrappedRange];

			updateAnchorAndFocusFromRange(sel, wrappedRange, false);
			sel.rangeCount = 1;
			sel.isCollapsed = wrappedRange.collapsed;
		}

		function updateControlSelection(sel) {
			// Update the wrapped selection based on what's now in the native selection
			sel._ranges.length = 0;
			if (sel.docSelection.type == "None") {
				updateEmptySelection(sel);
			} else {
				var controlRange = sel.docSelection.createRange();
				if (isTextRange(controlRange)) {
					// This case (where the selection type is "Control" and calling createRange() on the selection returns
					// a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
					// ControlRange have been removed from the ControlRange and removed from the document.
					updateFromTextRange(sel, controlRange);
				} else {
					sel.rangeCount = controlRange.length;
					var range, doc = dom.getDocument(controlRange.item(0));
					for (var i = 0; i < sel.rangeCount; ++i) {
						range = api.createRange(doc);
						range.selectNode(controlRange.item(i));
						sel._ranges.push(range);
					}
					sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
					updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
				}
			}
		}

		function addRangeToControlSelection(sel, range) {
			var controlRange = sel.docSelection.createRange();
			var rangeElement = getSingleElementFromRange(range);

			// Create a new ControlRange containing all the elements in the selected ControlRange plus the element
			// contained by the supplied range
			var doc = dom.getDocument(controlRange.item(0));
			var newControlRange = dom.getBody(doc).createControlRange();
			for (var i = 0, len = controlRange.length; i < len; ++i) {
				newControlRange.add(controlRange.item(i));
			}
			try {
				newControlRange.add(rangeElement);
			} catch (ex) {
				throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
			}
			newControlRange.select();

			// Update the wrapped selection based on what's now in the native selection
			updateControlSelection(sel);
		}

		var getSelectionRangeAt;

		if (util.isHostMethod(testSelection, "getRangeAt")) {
			getSelectionRangeAt = function (sel, index) {
				try {
					return sel.getRangeAt(index);
				} catch (ex) {
					return null;
				}
			};
		} else if (selectionHasAnchorAndFocus) {
			getSelectionRangeAt = function (sel) {
				var doc = dom.getDocument(sel.anchorNode);
				var range = api.createRange(doc);
				range.setStart(sel.anchorNode, sel.anchorOffset);
				range.setEnd(sel.focusNode, sel.focusOffset);

				// Handle the case when the selection was selected backwards (from the end to the start in the
				// document)
				if (range.collapsed !== this.isCollapsed) {
					range.setStart(sel.focusNode, sel.focusOffset);
					range.setEnd(sel.anchorNode, sel.anchorOffset);
				}

				return range;
			};
		}

		/**
		 * @constructor
		 */
		function WrappedSelection(selection, docSelection, win) {
			this.nativeSelection = selection;
			this.docSelection = docSelection;
			this._ranges = [];
			this.win = win;
			this.refresh();
		}

		api.getSelection = function (win) {
			win = win || window;
			var sel = win[windowPropertyName];
			var nativeSel = getSelection(win),
				docSel = implementsDocSelection ? getDocSelection(win) : null;
			if (sel) {
				sel.nativeSelection = nativeSel;
				sel.docSelection = docSel;
				sel.refresh(win);
			} else {
				sel = new WrappedSelection(nativeSel, docSel, win);
				win[windowPropertyName] = sel;
			}
			return sel;
		};

		api.getIframeSelection = function (iframeEl) {
			return api.getSelection(dom.getIframeWindow(iframeEl));
		};

		var selProto = WrappedSelection.prototype;

		function createControlSelection(sel, ranges) {
			// Ensure that the selection becomes of type "Control"
			var doc = dom.getDocument(ranges[0].startContainer);
			var controlRange = dom.getBody(doc).createControlRange();
			for (var i = 0, el; i < rangeCount; ++i) {
				el = getSingleElementFromRange(ranges[i]);
				try {
					controlRange.add(el);
				} catch (ex) {
					throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
				}
			}
			controlRange.select();

			// Update the wrapped selection based on what's now in the native selection
			updateControlSelection(sel);
		}

		// Selecting a range
		if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
			selProto.removeAllRanges = function () {
				this.nativeSelection.removeAllRanges();
				updateEmptySelection(this);
			};

			var addRangeBackwards = function (sel, range) {
				var doc = DomRange.getRangeDocument(range);
				var endRange = api.createRange(doc);
				endRange.collapseToPoint(range.endContainer, range.endOffset);
				sel.nativeSelection.addRange(getNativeRange(endRange));
				sel.nativeSelection.extend(range.startContainer, range.startOffset);
				sel.refresh();
			};

			if (selectionHasRangeCount) {
				selProto.addRange = function (range, backwards) {
					if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
						addRangeToControlSelection(this, range);
					} else {
						if (backwards && selectionHasExtend) {
							addRangeBackwards(this, range);
						} else {
							var previousRangeCount;
							if (selectionSupportsMultipleRanges) {
								previousRangeCount = this.rangeCount;
							} else {
								this.removeAllRanges();
								previousRangeCount = 0;
							}
							this.nativeSelection.addRange(getNativeRange(range));

							// Check whether adding the range was successful
							this.rangeCount = this.nativeSelection.rangeCount;

							if (this.rangeCount == previousRangeCount + 1) {
								// The range was added successfully

								// Check whether the range that we added to the selection is reflected in the last range extracted from
								// the selection
								if (api.config.checkSelectionRanges) {
									var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
									if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {
										// Happens in WebKit with, for example, a selection placed at the start of a text node
										range = new WrappedRange(nativeRange);
									}
								}
								this._ranges[this.rangeCount - 1] = range;
								updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));
								this.isCollapsed = selectionIsCollapsed(this);
							} else {
								// The range was not added successfully. The simplest thing is to refresh
								this.refresh();
							}
						}
					}
				};
			} else {
				selProto.addRange = function (range, backwards) {
					if (backwards && selectionHasExtend) {
						addRangeBackwards(this, range);
					} else {
						this.nativeSelection.addRange(getNativeRange(range));
						this.refresh();
					}
				};
			}

			selProto.setRanges = function (ranges) {
				if (implementsControlRange && ranges.length > 1) {
					createControlSelection(this, ranges);
				} else {
					this.removeAllRanges();
					for (var i = 0, len = ranges.length; i < len; ++i) {
						this.addRange(ranges[i]);
					}
				}
			};
		} else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") && implementsControlRange && useDocumentSelection) {

			selProto.removeAllRanges = function () {
				// Added try/catch as fix for issue #21
				try {

					var isNativeIE7 = (Aloha.browser.msie && Aloha.browser.version < 8 && (typeof document.documentMode === 'undefined'));
					if (!isNativeIE7) {
						this.docSelection.empty();
					}

					// Check for empty() not working (issue #24)
					if (this.docSelection.type != "None") {

						if (isNativeIE7) {
							this.docSelection.empty();
						}

						// removed workaround of rangy-core implementation
						// for IE to fix issue with strange selection of
						// hole body in some selection change cases
					}

				} catch (ex) {}
				updateEmptySelection(this);
			};

			selProto.addRange = function (range) {
				if (this.docSelection.type == CONTROL) {
					addRangeToControlSelection(this, range);
				} else {
					try {
						WrappedRange.rangeToTextRange(range).select();
						this._ranges[0] = range;
						this.rangeCount = 1;
						this.isCollapsed = this._ranges[0].collapsed;
						updateAnchorAndFocusFromRange(this, range, false);
					} catch (e) {
						// @todo
						// window.console.log('problem at addRange');
					}
				}
			};

			selProto.setRanges = function (ranges) {
				this.removeAllRanges();
				var rangeCount = ranges.length;
				if (rangeCount > 1) {
					createControlSelection(this, ranges);
				} else if (rangeCount) {
					this.addRange(ranges[0]);
				}
			};
		} else {
			module.fail("No means of selecting a Range or TextRange was found");
			return false;
		}

		selProto.getRangeAt = function (index) {
			if (index < 0 || index >= this.rangeCount) {
				throw new DOMException("INDEX_SIZE_ERR");
			} else {
				return this._ranges[index];
			}
		};

		var refreshSelection;

		if (useDocumentSelection) {
			refreshSelection = function (sel) {
				var range;
				if (api.isSelectionValid(sel.win)) {
					range = sel.docSelection.createRange();
				} else {
					range = dom.getBody(sel.win.document).createTextRange();
					range.collapse(true);
				}


				if (sel.docSelection.type == CONTROL) {
					updateControlSelection(sel);
				} else if (isTextRange(range)) {
					updateFromTextRange(sel, range);
				} else {
					updateEmptySelection(sel);
				}
			};
		} else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
			refreshSelection = function (sel) {
				if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
					updateControlSelection(sel);
				} else {
					sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
					if (sel.rangeCount) {
						for (var i = 0, len = sel.rangeCount; i < len; ++i) {
							sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
						}
						updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));
						sel.isCollapsed = selectionIsCollapsed(sel);
					} else {
						updateEmptySelection(sel);
					}
				}
			};
		} else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
			refreshSelection = function (sel) {
				var range, nativeSel = sel.nativeSelection;
				if (nativeSel.anchorNode) {
					range = getSelectionRangeAt(nativeSel, 0);
					sel._ranges = [range];
					sel.rangeCount = 1;
					updateAnchorAndFocusFromNativeSelection(sel);
					sel.isCollapsed = selectionIsCollapsed(sel);
				} else {
					updateEmptySelection(sel);
				}
			};
		} else {
			module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
			return false;
		}

		selProto.refresh = function (checkForChanges) {
			var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
			refreshSelection(this);
			if (checkForChanges) {
				var i = oldRanges.length;
				if (i != this._ranges.length) {
					return false;
				}
				while (i--) {
					if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
						return false;
					}
				}
				return true;
			}
		};

		// Removal of a single range
		var removeRangeManually = function (sel, range) {
			var ranges = sel.getAllRanges(),
				removed = false;
			sel.removeAllRanges();
			for (var i = 0, len = ranges.length; i < len; ++i) {
				if (removed || range !== ranges[i]) {
					sel.addRange(ranges[i]);
				} else {
					// According to the draft WHATWG Range spec, the same range may be added to the selection multiple
					// times. removeRange should only remove the first instance, so the following ensures only the first
					// instance is removed
					removed = true;
				}
			}
			if (!sel.rangeCount) {
				updateEmptySelection(sel);
			}
		};

		if (implementsControlRange) {
			selProto.removeRange = function (range) {
				if (this.docSelection.type == CONTROL) {
					var controlRange = this.docSelection.createRange();
					var rangeElement = getSingleElementFromRange(range);

					// Create a new ControlRange containing all the elements in the selected ControlRange minus the
					// element contained by the supplied range
					var doc = dom.getDocument(controlRange.item(0));
					var newControlRange = dom.getBody(doc).createControlRange();
					var el, removed = false;
					for (var i = 0, len = controlRange.length; i < len; ++i) {
						el = controlRange.item(i);
						if (el !== rangeElement || removed) {
							newControlRange.add(controlRange.item(i));
						} else {
							removed = true;
						}
					}
					newControlRange.select();

					// Update the wrapped selection based on what's now in the native selection
					updateControlSelection(this);
				} else {
					removeRangeManually(this, range);
				}
			};
		} else {
			selProto.removeRange = function (range) {
				removeRangeManually(this, range);
			};
		}

		// Detecting if a selection is backwards
		var selectionIsBackwards;
		if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
			selectionIsBackwards = function (sel) {
				var backwards = false;
				if (sel.anchorNode) {
					backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
				}
				return backwards;
			};

			selProto.isBackwards = function () {
				return selectionIsBackwards(this);
			};
		} else {
			selectionIsBackwards = selProto.isBackwards = function () {
				return false;
			};
		}

		// Selection text
		// This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
		selProto.toString = function () {

			var rangeTexts = [];
			for (var i = 0, len = this.rangeCount; i < len; ++i) {
				rangeTexts[i] = "" + this._ranges[i];
			}
			return rangeTexts.join("");
		};

		function assertNodeInSameDocument(sel, node) {
			if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
				throw new DOMException("WRONG_DOCUMENT_ERR");
			}
		}

		// No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
		selProto.collapse = function (node, offset) {
			assertNodeInSameDocument(this, node);
			var range = api.createRange(dom.getDocument(node));
			range.collapseToPoint(node, offset);
			this.removeAllRanges();
			this.addRange(range);
			this.isCollapsed = true;
		};

		selProto.collapseToStart = function () {
			if (this.rangeCount) {
				var range = this._ranges[0];
				this.collapse(range.startContainer, range.startOffset);
			} else {
				throw new DOMException("INVALID_STATE_ERR");
			}
		};

		selProto.collapseToEnd = function () {
			if (this.rangeCount) {
				var range = this._ranges[this.rangeCount - 1];
				this.collapse(range.endContainer, range.endOffset);
			} else {
				throw new DOMException("INVALID_STATE_ERR");
			}
		};

		// The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
		// never used by Rangy.
		selProto.selectAllChildren = function (node) {
			assertNodeInSameDocument(this, node);
			var range = api.createRange(dom.getDocument(node));
			range.selectNodeContents(node);
			this.removeAllRanges();
			this.addRange(range);
		};

		selProto.deleteFromDocument = function () {
			// Sepcial behaviour required for Control selections
			if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
				var controlRange = this.docSelection.createRange();
				var element;
				while (controlRange.length) {
					element = controlRange.item(0);
					controlRange.remove(element);
					element.parentNode.removeChild(element);
				}
				this.refresh();
			} else if (this.rangeCount) {
				var ranges = this.getAllRanges();
				this.removeAllRanges();
				for (var i = 0, len = ranges.length; i < len; ++i) {
					ranges[i].deleteContents();
				}
				// The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
				// range. Firefox moves the selection to where the final selected range was, so we emulate that
				this.addRange(ranges[len - 1]);
			}
		};

		// The following are non-standard extensions
		selProto.getAllRanges = function () {
			return this._ranges.slice(0);
		};

		selProto.setSingleRange = function (range) {
			this.setRanges([range]);
		};

		selProto.containsNode = function (node, allowPartial) {
			for (var i = 0, len = this._ranges.length; i < len; ++i) {
				if (this._ranges[i].containsNode(node, allowPartial)) {
					return true;
				}
			}
			return false;
		};

		selProto.toHtml = function () {
			var html = "";
			if (this.rangeCount) {
				var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");
				for (var i = 0, len = this._ranges.length; i < len; ++i) {
					container.appendChild(this._ranges[i].cloneContents());
				}
				html = container.innerHTML;
			}
			return html;
		};

		function inspect(sel) {
			var rangeInspects = [];
			var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
			var focus = new DomPosition(sel.focusNode, sel.focusOffset);
			var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";

			if (typeof sel.rangeCount != "undefined") {
				for (var i = 0, len = sel.rangeCount; i < len; ++i) {
					rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
				}
			}
			return "[" + name + "(Ranges: " + rangeInspects.join(", ") + ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";

		}

		selProto.getName = function () {
			return "WrappedSelection";
		};

		selProto.inspect = function () {
			return inspect(this);
		};

		selProto.detach = function () {
			this.win[windowPropertyName] = null;
			this.win = this.anchorNode = this.focusNode = null;
		};

		WrappedSelection.inspect = inspect;

		api.Selection = WrappedSelection;

		api.selectionPrototype = selProto;

		api.addCreateMissingNativeApiListener(function (win) {
			if (typeof win.getSelection == "undefined") {
				win.getSelection = function () {
					return api.getSelection(this);
				};
			}
			win = null;
		});
	});

	// TODO we should avoid populating the global namespace
	window.rangy = rangy;
}());

define("vendor/rangy-core", function(){});

/* rangy-core.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/rangy-core', ['jquery', 'vendor/rangy-core'], function (jQuery) {
	
	return window.rangy;
});

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
/*
 * MODIFICATIONS: 
 * * The name of the "constructor" method was changed from "init" to "_constructor"
 * * Mixin Support using https://gist.github.com/1006243
 * * Modified to be a require.js module
 */
define('util/class',[], function () {
	var initializing = false,
		fnTest = /xyz/.test(function () {
			xyz;
		}) ? /\b_super\b/ : /.*/;

	// The base Class implementation (does nothing)
	// with doing that Class is available in the global namespace.
	this.Class = function () {};

	// Create a new Class that inherits from this class
	Class.extend = function () {
		var _super = this.prototype;

		// Instantiate a base class (but only create the instance,
		// don't run the init constructor)
		initializing = true;
		var prototype = new this();
		initializing = false;

		// Copy the properties over onto the new prototype
		for (var i = 0; i < arguments.length; i++) {
			var prop = arguments[i];
			for (var name in prop) {
				// Check if we're overwriting an existing function
				prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function (name, fn) {
					return function () {

						var tmp = this._super;

						// Add a new ._super() method that is the same method
						// but on the super-class
						this._super = _super[name];

						// The method only need to be bound temporarily, so we
						// remove it when we're done executing
						var ret = fn.apply(this, arguments);
						this._super = tmp;

						return ret;
					};
				})(name, prop[name]) : prop[name];
			}
		}

		// The dummy class constructor
		function Class() {
			// All construction is actually done in the _constructor method
			if (!initializing && this._constructor) this._constructor.apply(this, arguments);
		}

		// Populate our constructed prototype object
		Class.prototype = prototype;

		// Enforce the constructor to be what we expect
		Class.constructor = Class;

		// And make this class extendable
		Class.extend = arguments.callee;

		return Class;

	};

	return this.Class;

});

/* lang.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
// Ensure GENTICS Namespace
window.GENTICS = window.GENTICS || {};
window.GENTICS.Utils = window.GENTICS.Utils || {};

define('util/lang', [], function () {
	
});

// Start Closure
(function (window) {
	
	var jQuery = window.alohaQuery || window.jQuery,
		$ = jQuery,
		GENTICS = window.GENTICS,
		Class = window.Class,
		console = window.console;

	/**
	 * Takes over all properties from the 'properties' object to the target object.
	 * If a property in 'target' with the same name as a property in 'properties' is already defined it is overridden.
	 *
	 * Example:
	 *
	 * var o1 = {a : 1, b : 'hello'};
	 * var o2 = {a : 3, c : 'world'};
	 *
	 * GENTICS.Utils.applyProperties(o1, o2);
	 *
	 * Will result in an o1 object like this:
	 *
	 * {a : 3, b: 'hello', c: 'world'}
	 *
	 * @static
	 * @return void
	 */
	GENTICS.Utils.applyProperties = function (target, properties) {
		var name;
		for (name in properties) {
			if (properties.hasOwnProperty(name)) {
				target[name] = properties[name];
			}
		}
	};

	/**
	 * Generate a unique hexadecimal string with 4 charachters
	 * @return {string}
	 */
	GENTICS.Utils.uniqeString4 = function () {
		return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
	};

	/**
	 * Generate a unique value represented as a 32 character hexadecimal string,
	 * such as 21EC2020-3AEA-1069-A2DD-08002B30309D
	 * @return {string}
	 */
	GENTICS.Utils.guid = function () {
		var S4 = GENTICS.Utils.uniqeString4;
		return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4());
	};

}(window));

/**
 * ecma5schims.js - Shim for ECMA5 compatibility
 * (http://en.wikipedia.org/wiki/Shim_%28computing%29)
 *
 * A shim library that implements common functions that are missing on some
 * environments in order to complete ECMA5 compatibility across all major
 * browsers.
 *
 * TODO: This code needs to be refactored so as to conform to Aloha coding
 *       standards.  It is also severly lacking in documentation.  Please take
 *       note of: https://github.com/alohaeditor/Aloha-Editor/wiki/Commit-Checklist .
 */

define('aloha/ecma5shims',[], function () {
	
	var $_;

	var shims = {
		// Function bind
		bind: function (owner) {
			var obj = this.obj || this;
			var native_method = Function.prototype.bind;
			var args = Array.prototype.slice.call(arguments, 1);

			if (native_method) {
				return native_method.apply(obj, arguments);
			}
			return function () {
				return obj.apply(owner, arguments.length === 0 ? args : args.concat(Array.prototype.slice.call(arguments)));
			};
		},

		// String trim
		trim: function () {
			var obj = this.obj || this;
			var native_method = String.prototype.trim;

			if (native_method) {
				return native_method.call(obj);
			}
			return obj.replace(/^\s+/, '').replace(/\s+$/, '');
		},

		// Array methods 
		// i optional
		indexOf: function (find, i) {
			var obj = this.obj || this;
			var native_method = Array.prototype.indexOf;

			if (native_method) {
				return native_method.call(obj, find, i);
			}
			if (i === undefined) {
				i = 0;
			}
			if (i < 0) {
				i += obj.length;
			}
			if (i < 0) {
				i = 0;
			}
			var n;
			for (n = obj.length; i < n; i++) {
				if (undefined !== obj[i] && obj[i] === find) {
					return i;
				}
			}
			return -1;
		},

		// that optional
		forEach: function (action, that) {
			var obj = this.obj || this;
			var native_method = Array.prototype.forEach;

			if (native_method) {
				return native_method.call(obj, action, that);
			}
			var i, n;
			for (i = 0, n = obj.length; i < n; i++) {
				if (undefined !== obj[i]) {
					action.call(that, obj[i], i, obj);
				}
			}
		},

		// that optional
		// chain optional
		map: function (mapper, that, chain) {
			var obj = this.obj || this;
			var native_method = Array.prototype.map;
			var returnWrapper = (typeof arguments[arguments.length - 1] == "boolean") ? Array.prototype.pop.call(arguments) : false;
			var result = [];

			if (native_method) {
				result = native_method.call(obj, mapper, that);
			} else {
				var other = [];
				var i, n;
				for (i = 0, n = obj.length; i < n; i++) {
					if (undefined !== obj[i]) {
						other[i] = mapper.call(that, obj[i], i, obj);
					}
				}
				result = other;
			}

			return returnWrapper ? $_(result) : result;
		},

		// that optional
		// chain optional
		filter: function (filterFunc, that, chain) {
			var obj = this.obj || this;
			var native_method = Array.prototype.filter;
			var returnWrapper = (typeof arguments[arguments.length - 1] == "boolean") ? Array.prototype.pop.call(arguments) : false;
			var result = [];

			if (native_method) {
				result = native_method.call(obj, filterFunc, that);
			} else {
				var other = [],
				    v,
				    i,
				    n;
				for (i = 0, n = obj.length; i < n; i++) {
					if (undefined !== obj[i] && filterFunc.call(that, v = obj[i], i, obj)) {
						other.push(v);
					}
				}
				result = other;
			}

			return returnWrapper ? $_(result) : result;
		},

		// that optional
		every: function (tester, that) {
			var obj = this.obj || this;
			var native_method = Array.prototype.every;

			if (native_method) {
				return native_method.call(obj, tester, that);
			}
			var i, n;
			for (i = 0, n = obj.length; i < n; i++) {
				if (undefined !== obj[i] && !tester.call(that, obj[i], i, obj)) {
					return false;
				}
			}
			return true;
		},

		// that optional
		some: function (tester, that) {
			var obj = this.obj || this;
			var native_method = Array.prototype.some;

			if (native_method) {
				return native_method.call(obj, tester, that);
			}
			var i, n;
			for (i = 0, n = obj.length; i < n; i++) {
				if (undefined !== obj[i] && tester.call(that, obj[i], i, obj)) {
					return true;
				}
			}
			return false;
		},

		// Since IE7 doesn't support 'hasAttribute' method on nodes
		// TODO: raise an exception if the object is not an node
		hasAttribute: function (attr) {
			var obj = this.obj || this;
			var native_method = obj.hasAttribute;

			if (native_method) {
				return obj.hasAttribute(attr);
			}
			return !!obj.getAttribute(attr);
		}

	};

	$_ = function (obj) {
		var Wrapper = function () {};
		Wrapper.prototype = shims;

		var wrapper_instance = new Wrapper();
		wrapper_instance.obj = obj;
		return wrapper_instance;
	};

	var shim;
	for (shim in shims) {
		if (shims.hasOwnProperty(shim)) {
			$_[shim] = shims[shim];
		}
	}


	// Node constants
	// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1841493061
	if (typeof window.Node != 'undefined') {
		$_.Node = window.Node;
	} else {
		$_.Node = {
			'ELEMENT_NODE': 1,
			'ATTRIBUTE_NODE': 2,
			'TEXT_NODE': 3,
			'CDATA_SECTION_NODE': 4,
			'ENTITY_REFERENCE_NODE': 5,
			'ENTITY_NODE': 6,
			'PROCESSING_INSTRUCTION_NODE': 7,
			'COMMENT_NODE': 8,
			'DOCUMENT_NODE': 9,
			'DOCUMENT_TYPE_NODE': 10,
			'DOCUMENT_FRAGMENT_NODE': 11,
			'NOTATION_NODE': 12,
			//The two nodes are disconnected. Order between disconnected nodes is always implementation-specific.
			'DOCUMENT_POSITION_DISCONNECTED': 0x01,
			//The second node precedes the reference node.
			'DOCUMENT_POSITION_PRECEDING': 0x02,
			//The node follows the reference node.
			'DOCUMENT_POSITION_FOLLOWING': 0x04,
			//The node contains the reference node. A node which contains is always preceding, too.
			'DOCUMENT_POSITION_CONTAINS': 0x08,
			//The node is contained by the reference node. A node which is contained is always following, too.
			'DOCUMENT_POSITION_CONTAINED_BY': 0x10,
			//The determination of preceding versus following is implementation-specific.
			'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 0x20
		};
	}

	//node.ownerDocument gives the document object, which isn't the right info for a disconnect
	function getRootParent(node) {
		var parent = null;

		if (node) {
			do {
				parent = node;
			} while (null != (node = node.parentNode));
		}

		return parent;
	}

	//Compare Position - MIT Licensed, John Resig; http://ejohn.org/blog/comparing-document-position/
	//Already checked for equality and disconnect
	function comparePosition(node1, node2) {
		return (node1.contains(node2) && 16) + (node2.contains(node1) && 8) + (node1.sourceIndex >= 0 && node2.sourceIndex >= 0 ? (node1.sourceIndex < node2.sourceIndex && 4) + (node1.sourceIndex > node2.sourceIndex && 2) : 1);
	}

	//get a node with a sourceIndex to use
	function getUseNode(node) {
		//if the node already has a sourceIndex, use that node
		if (null != node.sourceIndex) {
			return node;
		}
		//otherwise, insert a comment (which has a sourceIndex but minimal DOM impact) before the node and use that
		return node.parentNode.insertBefore(document.createComment(""), node);
	}

	// http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition
	// FIXME: Check if the DOMNode prototype can be set.
	$_.compareDocumentPosition = function (node1, node2) {

		if (document.documentElement.compareDocumentPosition) {
			return node1.compareDocumentPosition(node2);
		}

		if (!document.documentElement.contains) {
			throw 'neither compareDocumentPosition nor contains is supported by this browser.';
		}

		if (node1 == node2) {
			return 0;
		}

		//if they don't have the same parent, there's a disconnect
		if (getRootParent(node1) != getRootParent(node2)) {
			return 1;
		}

		//use this if both nodes have a sourceIndex (text nodes don't)
		if (null != node1.sourceIndex && null != node2.sourceIndex) {
			return comparePosition(node1, node2);
		}

		//document will definitely contain the other node
		if (node1 == document) {
			return 20;
		}
		if (node2 == document) {
			return 10;
		}

		//get sourceIndexes to use for both nodes
		var useNode1 = getUseNode(node1),
			useNode2 = getUseNode(node2);

		//call this function again to get the result
		var result = comparePosition(useNode1, useNode2);

		//clean up if needed
		if (node1 != useNode1) {
			useNode1.parentNode.removeChild(useNode1);
		}
		if (node2 != useNode2) {
			useNode2.parentNode.removeChild(useNode2);
		}
		return result;
	};

	$_.getComputedStyle = function (node, style) {
		if (window.getComputedStyle) {
			return window.getComputedStyle(node, style);
		}
		if (node.currentStyle) {
			return node.currentStyle;
		}
		return null;
	};

	return $_;
});

/* dom.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/*jslint eqeq: true */
// Ensure GENTICS Namespace
window.GENTICS = window.GENTICS || {};
window.GENTICS.Utils = window.GENTICS.Utils || {};

define('util/dom',['jquery', 'util/class', 'aloha/ecma5shims'], function (jQuery, Class, $_) {
	

	var	GENTICS = window.GENTICS,
		//		Class = window.Class,
		// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1841493061
		Node = {
			'ELEMENT_NODE': 1,
			'ATTRIBUTE_NODE': 2,
			'TEXT_NODE': 3,
			'CDATA_SECTION_NODE': 4,
			'ENTITY_REFERENCE_NODE': 5,
			'ENTITY_NODE': 6,
			'PROCESSING_INSTRUCTION_NODE': 7,
			'COMMENT_NODE': 8,
			'DOCUMENT_NODE': 9,
			'DOCUMENT_TYPE_NODE': 10,
			'DOCUMENT_FRAGMENT_NODE': 11,
			'NOTATION_NODE': 12,
			//The two nodes are disconnected. Order between disconnected nodes is always implementation-specific.
			'DOCUMENT_POSITION_DISCONNECTED': 0x01,
			//The second node precedes the reference node.
			'DOCUMENT_POSITION_PRECEDING': 0x02,
			//The node follows the reference node.
			'DOCUMENT_POSITION_FOLLOWING': 0x04,
			//The node contains the reference node. A node which contains is always preceding, too.
			'DOCUMENT_POSITION_CONTAINS': 0x08,
			//The node is contained by the reference node. A node which is contained is always following, too.
			'DOCUMENT_POSITION_CONTAINED_BY': 0x10,
			//The determination of preceding versus following is implementation-specific.
			'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 0x20
		},
		blockElementNames = {
			'P': true,
			'H1': true,
			'H2': true,
			'H3': true,
			'H4': true,
			'H5': true,
			'H6': true,
			'LI': true
		};

	/**
	 * Can't use elem.childNodes.length because
	 * http://www.quirksmode.org/dom/w3c_core.html
	 * "IE up to 8 does not count empty text nodes."
	 *
	 * Taken from Dom2.js
	 */
	function numChildren(elem) {
		var count = 0;
		var child = elem.firstChild;
		while (child) {
			count += 1;
			child = child.nextSibling;
		}
		return count;
	}

	/**
	 * Gets the index of the given node within its parent element.
	 * @param {Element} node
	 * @return {number}
	 *         Index in the parent node or -1 if no node given or node has no
	 *         parent.
	 */
	function getIndexInParent(node) {
		if (!node) {
			return -1;
		}

		var i,
		    childNodes = node.parentNode.childNodes,
			len = childNodes.length;
		for (i = 0; i < len; i++) {
			if (childNodes[i] === node) {
				return i;
			}
		}

		return -1;
	}

	/**
	 * Taken from Dom2.js
	 */
	function nodeLength(node) {
		if (1 === node.nodeType) {
			return numChildren(node);
		}
		if (3 === node.nodeType) {
			return node.length;
		}
		return 0;
	}

	/**
	 * Checks if the element given is an aloha-editing-p helper, added by split.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if the given element is an
	 *                   aloha-editing-paragraph.
	 */
	function isAlohaEditingP(node) {
		return (
			node.className === 'aloha-editing-p'
				&& nodeLength(node) === 1
					&& node.children[0].nodeName === 'BR'
						&& node.children[0].className === 'aloha-end-br'
		);
	}

	/**
	 * Starting from the given node, will walk forward (right-ward) through the
	 * node until an element is found that matches the predicate `match` or we
	 * reach the last element in the tree inside the editing host.
	 *
	 * @param {HTMLElement} node An element that must be inside an editable.
	 * @param {function(HTMLElement):Boolean} match A prediate function to
	 *                                              determine wether or not the
	 *                                              node matches one we are
	 *                                              looking for.
	 * @return {HTMLElement} The matched node that is forward in the DOM tree
	 *                       from `node`; null if nothing can be found that
	 *                       matches `match`.
	 */
	function findNodeForward(node, match) {
		if (!node) {
			return null;
		}
		if (match(node)) {
			return node;
		}
		var next = node.firstChild
		        || node.nextSibling
		        || (
		            node.parentNode
		            && !GENTICS.Utils.Dom.isEditingHost(node.parentNode)
		            && node.parentNode.nextSibling
		        );
		return next ? findNodeForward(next, match) : null;
	}

	function isVisiblyEmpty(node) {
		if (!node) {
			return true;
		}
		// TODO: use isChildlessElement()
		if ('BR' === node.nodeName) {
			return false;
		}
		if (node.nodeType === Node.TEXT_NODE) {
			// TODO: would prefer to use
			// (Html.isWhitespaces(node) || Html.isZeroWidthCharacters(node))
			// but cannot because of circular dependency
			if (node.data.search(/\S/) === -1) {
				return true;
			}
			// Fix for IE with zero-width characters
			if (1 === node.data.length && node.data.charCodeAt(0) >= 0x2000) {
				return true;
			}
			return false;
		}
		var numChildren = nodeLength(node);
		if (0 === numChildren) {
			return true;
		}
		var children = node.childNodes;
		var i;
		for (i = 0; i < numChildren; i++) {
			if (!isVisiblyEmpty(children[i])) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Checks for the opposite condition of isVisiblyEmpty().
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if the given node is visible not empty.
	 */
	function isNotVisiblyEmpty(node) {
		return !isVisiblyEmpty(node);
	}

	/**
	 * Checks whether the given element is a "phantom" element-- ie: an element
	 * that is either invisible or an aloha-editing-paragraph element.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if the element is a "phantom" element.
	 */
	function isPhantomNode(node) {
		return isVisiblyEmpty(node) || isAlohaEditingP(node);
	}

	/**
	 * Inserts the DOM node `element` appropriately during a split operation.
	 *
	 * The element `head` is used to reference where the element should be
	 * inserted.  Depending on the structure of this `head` node, the `element`
	 * will either replace/overwrite `head` or be appending immediately after
	 * `head`.
	 *
	 * @param {HTMLElement} head The "head" element of the "head and tail"
	 *                           nodes resulting from splitting a DOM element.
	 * @param {HTMLElement} element The element to be inserted between the head
	 *                              and tail split parts.
	 */
	function insertAfterSplit(head, element) {
		if (head.nodeType !== Node.TEXT_NODE && isPhantomNode(head)) {
			jQuery(head).replaceWith(element);
		} else {
			jQuery(head).after(element);
		}
	}

	/**
	 * @namespace GENTICS.Utils
	 * @class Dom provides methods to get information about the DOM and to manipulate it
	 * @singleton
	 */
	var Dom = Class.extend({
		getIndexInParent: getIndexInParent,
		/**
		 * Regex to find word characters.
		 */
		wordRegex: /[\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0525\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971\u0972\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8B\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA65F\uA662-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B\uA78C\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/,

		/**
		 * Regex to find non-word characters.
		 */
		nonWordRegex: /[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0525\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971\u0972\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8B\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA65F\uA662-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B\uA78C\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/,

		/**
		 * Tags which can safely be merged
		 * @hide
		 */
		mergeableTags: ['b', 'code', 'del', 'em', 'i', 'ins', 'strong', 'sub', 'sup', '#text'],

		/**
		 * Tags which do not mark word boundaries
		 * @hide
		 */
		nonWordBoundaryTags: ['a', 'b', 'code', 'del', 'em', 'i', 'ins', 'span', 'strong', 'sub', 'sup', '#text'],

		/**
		 * Tags which are considered 'nonempty', even if they have no children (or not data)
		 * TODO: finish this list
		 * @hide
		 */
		nonEmptyTags: ['br'],

		/**
		 * Tags which make up Flow Content or Phrasing Content, according to the HTML 5 specification,
		 * @see http://dev.w3.org/html5/spec/Overview.html#flow-content
		 * @see http://dev.w3.org/html5/spec/Overview.html#phrasing-content
		 * @hide
		 */
		tags: {
			'flow': ['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'blockquote', 'br', 'button', 'canvas', 'cite', 'code', 'command', 'datalist', 'del', 'details', 'dfn', 'div', 'dl', 'em', 'embed', 'fieldset', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math', 'menu', 'meter', 'nav', 'noscript', 'object', 'ol', 'output', 'p', 'pre', 'progress', 'q', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'span', 'strong', 'style', 'sub', 'sup', 'svg', 'table', 'textarea', 'time', 'u', 'ul', 'var', 'video', 'wbr', '#text'],
			'phrasing': ['a', 'abbr', 'acronym', 'area', 'audio', 'b', 'bdi', 'bdo', 'br', 'button', 'canvas', 'cite', 'code', 'command', 'datalist', 'del', 'dfn', 'em', 'embed', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math', 'meter', 'noscript', 'object', 'output', 'progress', 'q', 'ruby', 'samp', 'script', 'select', 'small', 'span', 'strong', 'sub', 'sup', 'svg', 'textarea', 'time', 'u', 'var', 'video', 'wbr', '#text']
		},

		/**
		 * Possible children of tags, according to the HTML 5
		 * specification.
		 * See http://dev.w3.org/html5/spec/Overview.html#elements-1
		 * Moved to http://www.whatwg.org/specs/web-apps/current-work/#elements-1
		 * @hide
		 */
		children: {
			'a': 'phrasing', // transparent
			'abbr': 'phrasing',
			'acronym': 'phrasing',
			'address': 'flow',
			'area': 'empty',
			'article': 'flow',
			'aside': 'flow',
			'audio': 'source', // transparent
			'b': 'phrasing',
			'base': 'empty',
			'bdo': 'phrasing',
			'blockquote': 'phrasing',
			'body': 'flow',
			'br': 'empty',
			'button': 'phrasing',
			'canvas': 'phrasing', // transparent
			'caption': 'flow',
			'cite': 'phrasing',
			'code': 'phrasing',
			'col': 'empty',
			'colgroup': 'col',
			'command': 'empty',
			'datalist': ['phrasing', 'option'],
			'dd': 'flow',
			'del': 'phrasing',
			'div': 'flow',
			'details': ['summary', 'flow'],
			'dfn': 'flow',
			'dl': ['dt', 'dd'],
			'dt': 'phrasing', // varies
			'em': 'phrasing',
			'embed': 'empty',
			'fieldset': ['legend', 'flow'],
			'figcaption': 'flow',
			'figure': ['figcaption', 'flow'],
			'footer': 'flow',
			'form': 'flow',
			'h1': 'phrasing',
			'h2': 'phrasing',
			'h3': 'phrasing',
			'h4': 'phrasing',
			'h5': 'phrasing',
			'h6': 'phrasing',
			//head
			'header': 'flow',
			'hgroup': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
			'hr': 'empty',
			//html :)
			'i': 'phrasing',
			'iframe': '#text',
			'img': 'empty',
			'input': 'empty',
			'ins': 'phrasing', // transparent
			'kbd': 'phrasing',
			'keygen': 'empty',
			'label': 'phrasing',
			'legend': 'phrasing',
			'li': 'flow',
			'link': 'empty',
			'map': 'area', // transparent
			'mark': 'phrasing',
			'menu': ['li', 'flow'],
			'meta': 'empty',
			'meter': 'phrasing',
			'nav': 'flow',
			'noscript': 'phrasing', // varies
			'object': 'param', // transparent
			'ol': 'li',
			'optgroup': 'option',
			'option': '#text',
			'output': 'phrasing',
			'p': 'phrasing',
			'param': 'empty',
			'pre': 'phrasing',
			'progress': 'phrasing',
			'q': 'phrasing',
			'rp': 'phrasing',
			'rt': 'phrasing',
			'ruby': ['phrasing', 'rt', 'rp'],
			's': 'phrasing',
			'samp': 'pharsing',
			'script': '#script', //script
			'section': 'flow',
			'select': ['option', 'optgroup'],
			'small': 'phrasing',
			'source': 'empty',
			'span': 'phrasing',
			'strong': 'phrasing',
			'style': 'phrasing', // varies
			'sub': 'phrasing',
			'summary': 'phrasing',
			'sup': 'phrasing',
			'table': ['caption', 'colgroup', 'thead', 'tbody', 'tfoot', 'tr'],
			'tbody': 'tr',
			'td': 'flow',
			'textarea': '#text',
			'tfoot': 'tr',
			'th': 'phrasing',
			'thead': 'tr',
			'time': 'phrasing',
			'title': '#text',
			'tr': ['th', 'td'],
			'track': 'empty',
			'u': 'phrasing',
			'ul': 'li',
			'var': 'phrasing',
			'video': 'source', // transparent
			'wbr': 'empty'
		},

		/**
		 * List of nodenames of blocklevel elements
		 * TODO: finish this list
		 * @hide
		 */
		blockLevelElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'div', 'pre'],

		/**
		 * List of nodenames of list elements
		 * @hide
		 */
		listElements: ['li', 'ol', 'ul'],

		/**
		 * List of nodenames of table elements
		 * @hide
		 */
		tableElements: ['caption', 'colgroup', 'table', 'thead', 'tbody', 'tfoot', 'tr', 'td'],

		/**
		 * Splits a DOM element at the given position up until the limiting object(s), so that it is valid HTML again afterwards.
		 * @param {RangeObject} range Range object that indicates the position of the splitting.
		 *				This range will be updated, so that it represents the same range as before the split.
		 * @param {jQuery} limit Limiting node(s) for the split.
		 *				The limiting node will not be included in the split itself.
		 *				If no limiting object is set, the document body will be the limiting object.
		 * @param {boolean} atEnd If set to true, the DOM will be splitted at the end of the range otherwise at the start.
		 * @return {object} jQuery object containing the two root DOM objects of the split, true if the DOM did not need to be split or false if the DOM could not be split
		 * @method
		 */
		split: function (range, limit, atEnd) {
			var splitElement = jQuery(range.startContainer),
				splitPosition = range.startOffset,
				updateRange,
			    path,
			    parents,
				newDom,
			    insertElement,
			    secondPart,
				i,
			    pathLength,
			    element,
			    jqelement,
			    children,
			    newElement,
				next,
			    prev,
			    offset;

			if (atEnd) {
				splitElement = jQuery(range.endContainer);
				splitPosition = range.endOffset;
			}

			if (limit.length < 1) {
				limit = jQuery(document.body);
			}

			// we may have to update the range if it is not collapsed and we are splitting at the start
			updateRange = (!range.isCollapsed() && !atEnd);

			// find the path up to the highest object that will be splitted
			parents = splitElement.parents().get();
			parents.unshift(splitElement.get(0));

			jQuery.each(parents, function (index, element) {
				var isLimit = limit.filter(
					function () {
						return this == element;
					}
				).length;
				if (isLimit) {
					if (index > 0) {
						path = parents.slice(0, index);
					}
					return false;
				}
			});

			// nothing found to split -> return here
			if (!path) {
				return true;
			}

			path = path.reverse();

			// iterate over the path, create new dom nodes for every element and move
			// the contents right of the split to the new element
			for (i = 0, pathLength = path.length; i < pathLength; ++i) {
				element = path[i];
				if (i === pathLength - 1) {
					// last element in the path -> we have to split it

					// split the last part into two parts
					if (element.nodeType === 3) {
						// text node
						secondPart = document.createTextNode(element.data.substring(splitPosition, element.data.length));
						element.data = element.data.substring(0, splitPosition);
						// this is done to make sure that empty block elements are visible.
						if (this.isBlockLevelElement(element.parentElement) && this.isEmpty(secondPart) && jQuery('br', newDom).length === 0) {
							secondPart = jQuery('<br/>').addClass('aloha-end-br');
						}
					} else {
						// other nodes
						jqelement = jQuery(element);
						children = jqelement.contents();
						newElement = jqelement.clone(false).empty();
						secondPart = newElement.append(children.slice(splitPosition, children.length)).get(0);
						jQuery(secondPart).addClass('aloha-editing-p');
						if (secondPart.childNodes.length === 1 && secondPart.childNodes.item(0).nodeName.toLowerCase() === 'br') {
							jQuery(secondPart.childNodes.item(0)).addClass('aloha-end-br');
						}
					}

					// update the range if necessary
					if (updateRange && range.endContainer === element) {
						range.endContainer = secondPart;
						range.endOffset -= splitPosition;
						range.clearCaches();
					}

					// add the second part
					if (insertElement) {
						insertElement.prepend(secondPart);
					} else {
						jQuery(element).after(secondPart);
					}
				} else {
					// create the new element of the same type and prepend it to the previously created element
					newElement = jQuery(element).clone(false).empty();

					if (!newDom) {
						newDom = newElement;
					} else {
						insertElement.prepend(newElement);
					}
					insertElement = newElement;

					// move all contents right of the split to the new element
					while (true) {
						next = path[i + 1].nextSibling;
						if (!next) {
							break;
						}
						insertElement.append(next);
					}

					// update the range if necessary
					if (updateRange && range.endContainer === element) {
						range.endContainer = newElement.get(0);
						prev = path[i + 1];
						offset = 0;
						while (true) {
							prev = prev.previousSibling;
							if (!prev) {
								break;
							}
							offset++;
						}
						range.endOffset -= offset;
						range.clearCaches();
					}
				}
			}

			// append the new dom
			jQuery(path[0]).after(newDom);

			return jQuery([path[0], newDom ? newDom.get(0) : secondPart]);
		},

		/**
		 * Check whether the HTML 5 specification allows direct nesting of the given DOM
		 * objects.
		 * @param {object} outerDOMObject
		 *            outer (nesting) DOM Object
		 * @param {object} innerDOMObject
		 *            inner (nested) DOM Object
		 * @return {boolean} true when the nesting is allowed, false if not
		 * @method
		 */
		allowsNesting: function (outerDOMObject, innerDOMObject) {
			if (!outerDOMObject || !outerDOMObject.nodeName || !innerDOMObject || !innerDOMObject.nodeName) {
				return false;
			}

			var outerNodeName = outerDOMObject.nodeName.toLowerCase(),
				innerNodeName = innerDOMObject.nodeName.toLowerCase();

			if (!this.children[outerNodeName]) {
				return false;
			}

			// check whether the nesting is configured by node names (like for table)
			if (this.children[outerNodeName] == innerNodeName) {
				return true;
			}
			if (jQuery.isArray(this.children[outerNodeName]) && jQuery.inArray(innerNodeName, this.children[outerNodeName]) >= 0) {
				return true;
			}

			if (jQuery.isArray(this.tags[this.children[outerNodeName]])
				    && jQuery.inArray(innerNodeName, this.tags[this.children[outerNodeName]]) >= 0) {
				return true;
			}

			return false;
		},

		/**
		 * Apply the given markup additively to the given range. The given rangeObject will be modified if necessary
		 * @param {GENTICS.Utils.RangeObject} rangeObject range to which the markup shall be added
		 * @param {jQuery} markup markup to be applied as jQuery object
		 * @param {boolean} allownesting true when nesting of the added markup is allowed, false if not (default: false)
		 * @method
		 */
		addMarkup: function (rangeObject, markup, nesting) {
			// split partially contained text nodes at the start and end of the range
			if (rangeObject.startContainer.nodeType === 3
				    && rangeObject.startOffset > 0
				    && rangeObject.startOffset < rangeObject.startContainer.data.length) {
				this.split(rangeObject, jQuery(rangeObject.startContainer).parent(), false);
			}
			if (rangeObject.endContainer.nodeType === 3 && rangeObject.endOffset > 0 && rangeObject.endOffset < rangeObject.endContainer.data.length) {
				this.split(rangeObject, jQuery(rangeObject.endContainer).parent(), true);
			}

			// get the range tree
			var rangeTree = rangeObject.getRangeTree();
			this.recursiveAddMarkup(rangeTree, markup, rangeObject, nesting);

			// cleanup DOM
			this.doCleanup({
				'merge': true,
				'removeempty': true
			}, rangeObject);
		},

		/**
		 * Recursive helper method to add the given markup to the range
		 * @param rangeTree rangetree at the current level
		 * @param markup markup to be applied
		 * @param rangeObject range object, which eventually is updated
		 * @param nesting true when nesting of the added markup is allowed, false if not
		 * @hide
		 */
		recursiveAddMarkup: function (rangeTree, markup, rangeObject, nesting) {
			var i, innerRange, rangeLength;

			// iterate through all rangetree objects of that level
			for (i = 0, rangeLength = rangeTree.length; i < rangeLength; ++i) {
				// check whether the rangetree object is fully contained and the markup may be wrapped around the object
				if (rangeTree[i].type == 'full' && this.allowsNesting(markup.get(0), rangeTree[i].domobj)) {
					// we wrap the object, when
					// 1. nesting of markup is allowed or the node is not of the markup to be added
					// 2. the node an element node or a non-empty text node
					if ((nesting || rangeTree[i].domobj.nodeName != markup.get(0).nodeName) && (rangeTree[i].domobj.nodeType !== 3 || jQuery.trim(rangeTree[i].domobj.data).length !== 0)) {
						// wrap the object
						jQuery(rangeTree[i].domobj).wrap(markup);

						// TODO eventually update the range (if it changed)

						// when nesting is not allowed, we remove the markup from the inner element
						if (!nesting && rangeTree[i].domobj.nodeType !== 3) {
							innerRange = new GENTICS.Utils.RangeObject();
							innerRange.startContainer = innerRange.endContainer = rangeTree[i].domobj.parentNode;
							innerRange.startOffset = 0;
							innerRange.endOffset = innerRange.endContainer.childNodes.length;
							this.removeMarkup(innerRange, markup, jQuery(rangeTree[i].domobj.parentNode));
						}
					}
				} else {
					// TODO check whether the object may be replaced by the given markup
					//if (false) {
					// TODO replace
					//} else {
					// recurse into the children (if any), but not if nesting is not
					// allowed and the object is of the markup to be added
					if ((nesting || (rangeTree[i].domobj && rangeTree[i].domobj.nodeName !== markup.get(0).nodeName)) && rangeTree[i].children && rangeTree[i].children.length > 0) {
						this.recursiveAddMarkup(rangeTree[i].children, markup);
					}
				}
			}
		},

		/**
		 * Find the highest occurrence of a node with given nodename within the parents
		 * of the start. When limit objects are given, the search stops there.
		 * The limiting object is of the found type, it won't be considered
		 * @param {DOMObject} start start object
		 * @param {String} nodeName name of the node to search for (case-insensitive)
		 * @param {jQuery} limit Limiting node(s) as jQuery object (if none given, the search will stop when there are no more parents)
		 * @return {DOMObject} the found DOM object or undefined
		 * @method
		 */
		findHighestElement: function (start, nodeName, limit) {
			nodeName = nodeName.toLowerCase();

			// this will be the highest found markup object (up to a limit object)
			var highestObject,
			// blah
			    testObject = start,
				// helper function to stop when we reach a limit object
				isLimit = limit ? function () {
					return limit.filter(
						function () {
							return testObject == this;
						}
					).length;
				} : function () {
					return false;
				};

			// now get the highest parent that has the given markup (until we reached
			// one of the limit objects or there are no more parent nodes)
			while (!isLimit() && testObject) {
				if (testObject.nodeName.toLowerCase() === nodeName) {
					highestObject = testObject;
				}
				testObject = testObject.parentNode;
			}

			return highestObject;
		},

		/**
		 * Remove the given markup from the given range. The given rangeObject will be modified if necessary
		 * TODO: add parameter deep/shallow
		 * @param {GENTICS.Utils.RangeObject} rangeObject range from which the markup shall be removed
		 * @param {jQuery} markup markup to be removed as jQuery object
		 * @param {jQuery} limit Limiting node(s) as jQuery object
		 * @param {boolean} removeNonEditables Whether to remove nodes which are
		 *		not content editable (default: true)
		 * @method
		 */
		removeMarkup: function (rangeObject, markup, limit, removeNonEditables) {
			if (typeof removeNonEditables == 'undefined') {
				removeNonEditables = true;
			}

			var nodeName = markup.get(0).nodeName,
				startSplitLimit = this.findHighestElement(rangeObject.startContainer, nodeName, limit),
				endSplitLimit = this.findHighestElement(rangeObject.endContainer, nodeName, limit),
				didSplit = false,
				highestObject,
			    root,
			    rangeTree;

			if (startSplitLimit && rangeObject.startOffset > 0) {
				// when the start is in the start of its container, we don't split
				this.split(rangeObject, jQuery(startSplitLimit).parent(), false);
				didSplit = true;
			}

			if (endSplitLimit) {
				// when the end is in the end of its container, we don't split
				if (rangeObject.endContainer.nodeType === 3 && rangeObject.endOffset < rangeObject.endContainer.data.length) {
					this.split(rangeObject, jQuery(endSplitLimit).parent(), true);
					didSplit = true;
				}
				if (rangeObject.endContainer.nodeType === 1 && rangeObject.endOffset < rangeObject.endContainer.childNodes.length) {
					this.split(rangeObject, jQuery(endSplitLimit).parent(), true);
					didSplit = true;
				}
			}

			// when we split the DOM, we maybe need to correct the range
			if (didSplit) {
				rangeObject.correctRange();
			}

			// find the highest occurrence of the markup
			highestObject = this.findHighestElement(rangeObject.getCommonAncestorContainer(), nodeName, limit);
			root = highestObject ? highestObject.parentNode : rangeObject.getCommonAncestorContainer();

			if (root) {
				// construct the range tree
				rangeTree = rangeObject.getRangeTree(root);

				// remove the markup from the range tree
				this.recursiveRemoveMarkup(rangeTree, markup, removeNonEditables);

				// cleanup DOM
				this.doCleanup({
					'merge': true,
					'removeempty': true
				}, rangeObject, root);
			}
		},

		/**
		 * TODO: pass the range itself and eventually update it if necessary
		 * Recursive helper method to remove the given markup from the range
		 * @param rangeTree rangetree at the current level
		 * @param markup markup to be applied
		 * @param removeNonEditables whether to remove nodes which are not
		 *		content editable.
		 * @hide
		 */
		recursiveRemoveMarkup: function (rangeTree, markup, removeNonEditables) {
			var i, rangeLength, content;
			var nodeName = markup[0].nodeName;

			// iterate over the rangetree objects of this level
			for (i = 0, rangeLength = rangeTree.length; i < rangeLength; ++i) {
				var curTree = rangeTree[i];
				var obj = curTree.domobj;

				// check whether the object is the markup to be removed and is fully into the range
				if (curTree.type == 'full'
						&& obj.nodeName == nodeName
						&& (removeNonEditables || this.isEditable(obj))) {
					var $obj = jQuery(obj);

					// found the markup, so remove it
					content = $obj.contents();
					if (content.length > 0) {
						// when the object has children, we unwrap them
						content.first().unwrap();
					} else {
						// obj has no children, so just remove it
						$obj.remove();
					}
				}

				// if the object has children, we do the recurTreesion now
				if (curTree.children) {
					this.recursiveRemoveMarkup(curTree.children, markup, removeNonEditables);
				}
			}
		},

		/**
		 * Cleanup the DOM, starting with the given startobject (or the common ancestor container of the given range)
		 * ATTENTION: If range is a selection you need to update the selection after doCleanup
		 * Cleanup modes (given as properties in 'cleanup'):
		 * <pre>
		 * - merge: merges multiple successive nodes of same type, if this is allowed, starting at the children of the given node (defaults to false)
		 * - removeempty: removes empty element nodes (defaults to false)
		 * - mergeable: Custom function to predicate whether or not a given
		 *              element should be mergeable. Overrides `mergeableTags'.
		 * </pre>
		 * Example for calling this method:<br/>
		 * <code>GENTICS.Utils.Dom.doCleanup({merge:true,removeempty:false}, range)</code>
		 * @param {object} cleanup type of cleanup to be done
		 * @param {GENTICS.Utils.RangeObject} rangeObject range which is eventually updated
		 * @param {DOMObject} start start object, if not given, the commonancestorcontainer is used as startobject insted
		 * @return {boolean} true when the range (startContainer/startOffset/endContainer/endOffset) was modified, false if not
		 * @method
		 */
		doCleanup: function (cleanup, rangeObject, start) {
			var that = this,
				prevNode,
			    modifiedRange,
			    startObject,
			    startOffset,
			    endOffset;

			if (typeof cleanup === 'undefined') {
				cleanup = {};
			}
			if (typeof cleanup.merge === 'undefined') {
				cleanup.merge = false;
			}
			if (typeof cleanup.removeempty === 'undefined') {
				cleanup.removeempty = false;
			}

			if (typeof start === 'undefined' && rangeObject) {
				start = rangeObject.getCommonAncestorContainer();
			}
			// remember the previous node here (successive nodes of same type will be merged into this)
			prevNode = false;
			// check whether the range needed to be modified during merging
			modifiedRange = false;
			// get the start object
			startObject = jQuery(start);
			startOffset = rangeObject.startOffset;
			endOffset = rangeObject.endOffset;

			// iterate through all sub nodes
			startObject.contents().each(function () {
				var nodeType;

				// Try to read the nodeType property and return if we do not have permission
				// ie.: frame document to an external URL
				try {
					nodeType = this.nodeType;
				} catch (e) {
					return;
				}

				// decide further actions by node type
				switch (nodeType) {
				// found a non-text node
				case 1:
					var thisNodeName = this.nodeName;
					if (prevNode && prevNode.nodeName === thisNodeName) {
						// found a successive node of same type

						// now we check whether the selection starts or ends in the mother node after the current node
						if (rangeObject.startContainer === startObject && startOffset > getIndexInParent(this)) {
							// there will be one less object, so reduce the startOffset by one
							rangeObject.startOffset -= 1;
							// set the flag for range modification
							modifiedRange = true;
						}
						if (rangeObject.endContainer === startObject && endOffset > getIndexInParent(this)) {
							// there will be one less object, so reduce the endOffset by one
							rangeObject.endOffset -= 1;
							// set the flag for range modification
							modifiedRange = true;
						}

						// merge the contents of this node into the previous one
						jQuery(prevNode).append(jQuery(this).contents());

						// after merging, we eventually need to cleanup the prevNode again
						modifiedRange |= that.doCleanup(cleanup, rangeObject, prevNode);

						// remove this node
						jQuery(this).remove();

					} else {
						// do the recursion step here
						modifiedRange |= that.doCleanup(cleanup, rangeObject, this);

						// eventually remove empty elements
						var removed = false;
						if (cleanup.removeempty) {
							if (GENTICS.Utils.Dom.isBlockLevelElement(this) && this.childNodes.length === 0) {
								//							jQuery(this).remove();
								removed = true;
							}
							if (jQuery.inArray(thisNodeName.toLowerCase(), that.mergeableTags) >= 0 && jQuery(this).text().length === 0 && this.childNodes.length === 0) {
								//							jQuery(this).remove();
								removed = true;
							}
						}

						// when the current node was not removed, we eventually store it as previous (mergeable) tag
						if (!removed) {
							if (cleanup.mergeable ? cleanup.mergeable(this) : jQuery.inArray(thisNodeName.toLowerCase(), that.mergeableTags) >= 0) {
								prevNode = this;
							} else {
								prevNode = false;
							}
						} else {
							// now we check whether the selection starts or ends in the mother node of this
							if (rangeObject.startContainer === this.parentNode && startOffset > getIndexInParent(this)) {
								// there will be one less object, so reduce the startOffset by one
								rangeObject.startOffset = rangeObject.startOffset - 1;
								// set the flag for range modification
								modifiedRange = true;
							}
							if (rangeObject.endContainer === this.parentNode && endOffset > getIndexInParent(this)) {
								// there will be one less object, so reduce the endOffset by one
								rangeObject.endOffset = rangeObject.endOffset - 1;
								// set the flag for range modification
								modifiedRange = true;
							}

							// remove this text node
							jQuery(this).remove();

						}
					}

					break;
				// found a text node
				case 3:
					// found a text node
					if (prevNode && prevNode.nodeType === 3 && cleanup.merge) {
						// the current text node will be merged into the last one, so
						// check whether the selection starts or ends in the current
						// text node
						var prevNodeValue = prevNode.nodeValue,
							prevNodeValueLength = prevNodeValue.length;

						if (rangeObject.startContainer === this) {
							// selection starts in the current text node

							// update the start container to the last node
							rangeObject.startContainer = prevNode;

							// update the start offset
							rangeObject.startOffset += prevNodeValueLength;

							// set the flag for range modification
							modifiedRange = true;

						} else if (rangeObject.startContainer === prevNode.parentNode && rangeObject.startOffset === getIndexInParent(prevNode) + 1) {
							// selection starts right between the previous and current text nodes (which will be merged)

							// update the start container to the previous node
							rangeObject.startContainer = prevNode;

							// set the start offset
							rangeObject.startOffset = prevNodeValueLength;

							// set the flag for range modification
							modifiedRange = true;
						}

						if (rangeObject.endContainer === this) {
							// selection ends in the current text node

							// update the end container to be the last node
							rangeObject.endContainer = prevNode;

							// update the end offset
							rangeObject.endOffset += prevNodeValueLength;

							// set the flag for range modification
							modifiedRange = true;

						} else if (rangeObject.endContainer === prevNode.parentNode && rangeObject.endOffset === getIndexInParent(prevNode) + 1) {
							// selection ends right between the previous and current text nodes (which will be merged)

							// update the end container to the previous node
							rangeObject.endContainer = prevNode;

							// set the end offset
							rangeObject.endOffset = prevNodeValueLength;

							// set the flag for range modification
							modifiedRange = true;
						}

						// now append the contents of the current text node into the previous
						prevNode.data += this.data;

						// remove empty text nodes
					} else if (!(this.nodeValue === '' && cleanup.removeempty)) {
						prevNode = this;
						// we are finish here don't delete this node
						break;
					}

					// now we check whether the selection starts or ends in the mother node of this
					if (rangeObject.startContainer === this.parentNode && rangeObject.startOffset > getIndexInParent(this)) {
						// there will be one less object, so reduce the startOffset by one
						rangeObject.startOffset = rangeObject.startOffset - 1;
						// set the flag for range modification
						modifiedRange = true;
					}
					if (rangeObject.endContainer === this.parentNode && rangeObject.endOffset > getIndexInParent(this)) {
						// there will be one less object, so reduce the endOffset by one
						rangeObject.endOffset = rangeObject.endOffset - 1;
						// set the flag for range modification
						modifiedRange = true;
					}

					// remove this text node
					jQuery(this).remove();

					// if this is the last text node in a sequence, we remove any zero-width spaces in the text node,
					// unless it is the only character
					var prevNodeNextSibling = prevNode.nextSibling;
					if (prevNode && (!prevNodeNextSibling || prevNodeNextSibling.nodeType !== 3)) {
						var pos;
						var prevNodeData = prevNode.data;
						var prevNodeDataLength = prevNodeData.length;
						for (pos = prevNodeDataLength - 1; pos >= 0 && prevNodeDataLength > 1; pos--) {
							if (prevNodeData.charAt(pos) === '\u200b') {
								prevNode.deleteData(pos, 1);
								if (rangeObject.startContainer === prevNode && rangeObject.startOffset > pos) {
									rangeObject.startOffset--;
									modifiedRange = true;
								}
								if (rangeObject.endContainer === prevNode && rangeObject.endOffset > pos) {
									rangeObject.endOffset--;
									modifiedRange = true;
								}
							}
						}
					}

					break;
				}
			});

			// eventually remove the startnode itself
			//		if (cleanup.removeempty
			//				&& GENTICS.Utils.Dom.isBlockLevelElement(start)
			//				&& (!start.childNodes || start.childNodes.length === 0)) {
			//			if (rangeObject.startContainer == start) {
			//				rangeObject.startContainer = start.parentNode;
			//				rangeObject.startOffset = GENTICS.Utils.Dom.getIndexInParent(start);
			//			}
			//			if (rangeObject.endContainer == start) {
			//				rangeObject.endContainer = start.parentNode;
			//				rangeObject.endOffset = GENTICS.Utils.Dom.getIndexInParent(start);
			//			}
			//			startObject.remove();
			//			modifiedRange = true;
			//		}

			if (modifiedRange) {
				rangeObject.clearCaches();
			}

			return modifiedRange;
		},

		/**
		 * Check whether the given node is a blocklevel element
		 * @param {DOMObject} node node to check
		 * @return {boolean} true if yes, false if not (or null)
		 * @method
		 */
		isBlockLevelElement: function (node) {
			if (!node) {
				return false;
			}
			if (node.nodeType === 1 && jQuery.inArray(node.nodeName.toLowerCase(), this.blockLevelElements) >= 0) {
				return true;
			}
			return false;
		},

		/**
		 * Check whether the given node is a table element
		 * @param {DOMObject} node node to check
		 * @return {boolean} true if yes, false if not (or null)
		 * @method
		 */
		isTableElement: function (node) {
			if (!node) {
				return false;
			}
			if (node.nodeType === 1 && jQuery.inArray(node.nodeName.toLowerCase(), this.tableElements) >= 0) {
				return true;
			}
			return false;
		},

		/**
		 * Check whether the given node is a linebreak element
		 * @param {DOMObject} node node to check
		 * @return {boolean} true for linebreak elements, false for everything else
		 * @method
		 */
		isLineBreakElement: function (node) {
			if (!node) {
				return false;
			}
			return node.nodeType === 1 && node.nodeName.toLowerCase() == 'br';
		},

		/**
		 * Check whether the given node is a hr element
		 * @param {DOMObject} node node to check
		 * @return {boolean} true for hr elements, false for everything else
		 * @method
		 */
		isHorizontalRulerElement: function (node) {
			if (!node) {
				return false;
			}
			return node.nodeType === 1 && node.nodeName.toLowerCase() == 'hr';
		},

		/**
		 * Check whether the given node is a list element
		 * @param {DOMObject} node node to check
		 * @return {boolean} true for list elements (li, ul, ol), false for everything else
		 * @method
		 */
		isListElement: function (node) {
			if (!node) {
				return false;
			}
			return node.nodeType === 1 && jQuery.inArray(node.nodeName.toLowerCase(), this.listElements) >= 0;
		},

		/**
		 * This method checks, whether the passed dom object is a dom object, that would
		 * be split in cases of pressing enter. This currently is true for paragraphs
		 * and headings
		 * @param {DOMObject} el
		 *            dom object to check
		 * @return {boolean} true for split objects, false for other
		 * @method
		 */
		isSplitObject: function (el) {
			return el.nodeType === 1 && blockElementNames.hasOwnProperty(el.nodeName);
		},

		/**
		 * Starting with the given position (between nodes), search in the given direction to an adjacent notempty text node
		 * @param {DOMObject} parent parent node containing the position
		 * @param {Integer} index index of the position within the parent node
		 * @param {boolean} searchleft true when search direction is 'left' (default), false for 'right'
		 * @param {object} stopat define at which types of element we shall stop, may contain the following properties
		 * <pre>
		 * - blocklevel (default: true)
		 * - list (default: true)
		 * - linebreak (default: true)
		 * - table (default: true)
		 * </pre>
		 * @param {object} options additional options to guide the search. May contain the following properties
		 * <pre>
		 * - acceptUntrimmed (default: false): accept a text node even if it only contains whitespace
		 * </pre>
		 * @return {DOMObject} the found text node or false if none found
		 * @method
		 */
		searchAdjacentTextNode: function (parent, index, searchleft, stopat, options) {
			if (!parent || parent.nodeType !== 1 || index < 0 || index > parent.childNodes.length) {
				return false;
			}

			if (typeof stopat === 'undefined') {
				stopat = {
					'blocklevel': true,
					'list': true,
					'linebreak': true,
					'table': true
				};
			}

			if (typeof stopat.blocklevel === 'undefined') {
				stopat.blocklevel = true;
			}
			if (typeof stopat.list === 'undefined') {
				stopat.list = true;
			}
			if (typeof stopat.linebreak === 'undefined') {
				stopat.linebreak = true;
			}
			if (typeof stopat.table === 'undefined') {
				stopat.table = true;
			}

			if (typeof searchleft === 'undefined') {
				searchleft = true;
			}

			options = jQuery.extend({ acceptUntrimmed: false }, options);

			var nextNode,
			    currentParent = parent;

			// start at the node left/right of the given position
			if (searchleft && index > 0) {
				nextNode = parent.childNodes[index - 1];
			}
			if (!searchleft && index < parent.childNodes.length) {
				nextNode = parent.childNodes[index];
			}

			//currentParent is not a number therefore it is sufficient to directly test for it with while(currentParent)
			//otherwise there would be an error if the object is null
			while (currentParent) {
				//while (typeof currentParent !== 'undefined') {
				if (!nextNode) {
					// no next node found, check whether the parent is a blocklevel element
					if (stopat.blocklevel && this.isBlockLevelElement(currentParent)) {
						// do not leave block level elements
						return false;
					}
					if (stopat.table && this.isTableElement(currentParent)) {
						// do not leave table level elements
						return false;
					}
					if (stopat.list && this.isListElement(currentParent)) {
						// do not leave list elements
						return false;
					}
					// continue with the parent
					nextNode = searchleft ? currentParent.previousSibling : currentParent.nextSibling;
					currentParent = currentParent.parentNode;
					continue;
				} else if (nextNode.nodeType === 3
						&& (options.acceptUntrimmed ? nextNode.data : jQuery.trim(nextNode.data)).length > 0) {
					// we are lucky and found a notempty text node
					return nextNode;
				}
				if (stopat.blocklevel && this.isBlockLevelElement(nextNode)) {
					// we found a blocklevel element, stop here
					return false;
				}
				if (stopat.table && this.isTableElement(nextNode)) {
					// we found a table element, stop here
					return false;
				}
				if (stopat.linebreak && (this.isLineBreakElement(nextNode) || this.isHorizontalRulerElement(nextNode))) {
					// we found a linebreak, stop here
					return false;
				}
				if (stopat.list && this.isListElement(nextNode)) {
					// we found a linebreak, stop here
					return false;
				}
				if (nextNode.nodeType === 3) {
					// we found an empty text node, so step to the next
					nextNode = searchleft ? nextNode.previousSibling : nextNode.nextSibling;
				} else {
					// we found a non-blocklevel element, step into
					currentParent = nextNode;
					nextNode = searchleft ? nextNode.lastChild : nextNode.firstChild;
				}
			}
		},

		/**
		 * Insert the given DOM Object into the start/end of the given range. The method
		 * will find the appropriate place in the DOM tree for inserting the given
		 * object, and will eventually split elements in between. The given range will
		 * be updated if necessary. The updated range will NOT embrace the inserted
		 * object, which means that the object is actually inserted before or after the
		 * given range (depending on the atEnd parameter)
		 *
		 * @param {jQuery}
		 *				object object to insert into the DOM
		 * @param {GENTICS.Utils.RangeObject}
		 *				range range where to insert the object (at start or end)
		 * @param {jQuery}
		 *				limit limiting object(s) of the DOM modification
		 * @param {boolean}
		 *				atEnd true when the object shall be inserted at the end, false for
		 *				insertion at the start (default)
		 * @param {boolean}
		 *				true when the insertion shall be done, even if inserting the element
		 *				would not be allowed, false to deny inserting unallowed elements (default)
		 * @return true if the object could be inserted, false if not.
		 * @method
		 */
		insertIntoDOM: function (object, range, limit, atEnd, force) {
			// first find the appropriate place to insert the given object
			var parentElements = range.getContainerParents(limit, atEnd),
				that = this,
				newParent,
				container,
			    offset,
			    splitParts,
			    contents;

			if (!limit) {
				limit = jQuery(document.body);
			}

			// if no parent elements exist (up to the limit), the new parent will be the
			// limiter itself
			if (parentElements.length === 0) {
				newParent = limit.get(0);
			} else {
				jQuery.each(parentElements, function (index, parent) {
					if (that.allowsNesting(parent, object.get(0))) {
						newParent = parent;
						return false;
					}
				});
			}

			if (typeof newParent === 'undefined' && limit.length > 0) {
				// found no possible new parent, so split up to the limit object
				newParent = limit.get(0);
			}

			// check whether it is allowed to insert the element at all
			if (!this.allowsNesting(newParent, object.get(0)) && !force) {
				return false;
			}

			if (typeof newParent !== 'undefined') {
				// we found a possible new parent, so we split the DOM up to the new parent
				splitParts = this.split(range, jQuery(newParent), atEnd);
				if (splitParts === true) {
					// DOM was not split (there was no need to split it), insert the new object anyway
					container = range.startContainer;
					offset = range.startOffset;
					if (atEnd) {
						container = range.endContainer;
						offset = range.endOffset;
					}
					// Sometimes the offset can be greater than the length of the
					// container contents due to a bug. In that case adjust the offset.
					if (offset > jQuery(container).contents().length) {
						offset = jQuery(container).contents().length;
					}
					if (offset === 0) {
						// insert right before the first element in the container
						contents = jQuery(container).contents();
						if (contents.length > 0) {
							contents.eq(0).before(object);
						} else {
							jQuery(container).append(object);
						}
						return true;
					}
					// insert right after the element at offset-1
					jQuery(container).contents().eq(offset - 1).after(object);
					return true;
				}
				if (splitParts) {
					// ASSERT(splitParts.length === 2)
					var head = splitParts[0];
					var tail = splitParts[1];
					insertAfterSplit(head, object);
					if (isPhantomNode(tail)) {
						var afterTail = tail.nextSibling
						             || (tail.parentNode && tail.parentNode.nextSibling);
						if (findNodeForward(afterTail, isNotVisiblyEmpty)) {
							// Because the tail element that is generated from
							// the splitting is superfluous since there is
							// already a visible element in which to place the
							// selection.
							jQuery(tail).remove();
						}
					}
					return true;
				}
				// could not split, so could not insert
				return false;
			}
			// found no possible new parent, so we shall not insert
			return false;
		},

		/**
		 * Remove the given DOM object from the DOM and modify the given range to reflect the user expected range after the object was removed
		 * TODO: finish this
		 * @param {DOMObject} object DOM object to remove
		 * @param {GENTICS.Utils.RangeObject} range range which eventually be modified
		 * @param {boolean} preserveContent true if the contents of the removed DOM object shall be preserved, false if not (default: false)
		 * @return true if the DOM object could be removed, false if not
		 * @hide
		 */
		removeFromDOM: function (object, range, preserveContent) {
			if (preserveContent) {
				// check whether the range will need modification
				var indexInParent = getIndexInParent(object),
					numChildren = jQuery(object).contents().length,
					parent = object.parentNode;

				if (range.startContainer == parent && range.startOffset > indexInParent) {
					range.startOffset += numChildren - 1;
				} else if (range.startContainer == object) {
					range.startContainer = parent;
					range.startOffset = indexInParent + range.startOffset;
				}

				if (range.endContainer == parent && range.endOffset > indexInParent) {
					range.endOffset += numChildren - 1;
				} else if (range.endContainer == object) {
					range.endContainer = parent;
					range.endOffset = indexInParent + range.endOffset;
				}

				// we simply unwrap the children of the object
				jQuery(object).contents().unwrap();

				// optionally do cleanup
				this.doCleanup({
					'merge': true
				}, range, parent);
			}
		},

		/**
		 * Remove the content defined by the given range from the DOM. Update the given
		 * range object to be a collapsed selection at the place of the previous
		 * selection.
		 * @param rangeObject range object
		 * @return true if the range could be removed, false if not
		 */
		removeRange: function (rangeObject) {
			if (!rangeObject) {
				// no range given
				return false;
			}
			if (rangeObject.isCollapsed()) {
				// the range is collapsed, nothing to delete
				return false;
			}

			// split partially contained text nodes at the start and end of the range
			if (rangeObject.startContainer.nodeType == 3
				    && rangeObject.startOffset > 0
				    && rangeObject.startOffset < rangeObject.startContainer.data.length) {
				this.split(rangeObject, jQuery(rangeObject.startContainer).parent(), false);
			}
			if (rangeObject.endContainer.nodeType == 3 && rangeObject.endOffset > 0 && rangeObject.endOffset < rangeObject.endContainer.data.length) {
				this.split(rangeObject, jQuery(rangeObject.endContainer).parent(), true);
			}

			// construct the range tree
			var rangeTree = rangeObject.getRangeTree();

			// collapse the range
			rangeObject.endContainer = rangeObject.startContainer;
			rangeObject.endOffset = rangeObject.startOffset;

			// remove the markup from the range tree
			this.recursiveRemoveRange(rangeTree, rangeObject);

			// do some cleanup
			this.doCleanup({
				'merge': true
			}, rangeObject);
			//		this.doCleanup({'merge' : true, 'removeempty' : true}, rangeObject);

			// clear the caches of the range object
			rangeObject.clearCaches();
		},

		recursiveRemoveRange: function (rangeTree, rangeObject) {
			// iterate over the rangetree objects of this level
			var i;
			for (i = 0; i < rangeTree.length; ++i) {
				// check for nodes fully in the range
				if (rangeTree[i].type == 'full') {
					// if the domobj is the startcontainer, or the startcontainer is inside the domobj, we need to update the rangeObject
					if (jQuery(rangeObject.startContainer).parents().andSelf().filter(rangeTree[i].domobj).length > 0) {
						rangeObject.startContainer = rangeObject.endContainer = rangeTree[i].domobj.parentNode;
						rangeObject.startOffset = rangeObject.endOffset = getIndexInParent(rangeTree[i].domobj);
					}

					// remove the object from the DOM
					jQuery(rangeTree[i].domobj).remove();
				} else if (rangeTree[i].type == 'partial' && rangeTree[i].children) {
					// node partially selected and has children, so do recursion
					this.recursiveRemoveRange(rangeTree[i].children, rangeObject);
				}
			}
		},

		/**
		 * Extend the given range to have start and end at the nearest word boundaries to the left (start) and right (end)
		 * @param {GENTICS.Utils.RangeObject} range range to be extended
		 * @param {boolean} fromBoundaries true if extending will also be done, if one or both ends of the range already are at a word boundary, false if not, default: false
		 * @method
		 */
		extendToWord: function (range, fromBoundaries) {
			// search the word boundaries to the left and right
			var leftBoundary = this.searchWordBoundary(range.startContainer, range.startOffset, true),
				rightBoundary = this.searchWordBoundary(range.endContainer, range.endOffset, false);

			// check whether we must not extend the range from word boundaries
			if (!fromBoundaries) {
				// we only extend the range if both ends would be different
				if (range.startContainer == leftBoundary.container && range.startOffset == leftBoundary.offset) {
					return;
				}
				if (range.endContainer == rightBoundary.container && range.endOffset == rightBoundary.offset) {
					return;
				}
			}

			// set the new boundaries
			range.startContainer = leftBoundary.container;
			range.startOffset = leftBoundary.offset;
			range.endContainer = rightBoundary.container;
			range.endOffset = rightBoundary.offset;

			// correct the range
			range.correctRange();

			// clear caches
			range.clearCaches();
		},

		/**
		 * Helper method to check whether the given DOM object is a word boundary.
		 * @param {DOMObject} object DOM object in question
		 * @return {boolean} true when the DOM object is a word boundary, false if not
		 * @hide
		 */
		isWordBoundaryElement: function (object) {
			if (!object || !object.nodeName) {
				return false;
			}
			return jQuery.inArray(object.nodeName.toLowerCase(), this.nonWordBoundaryTags) == -1;
		},

		/**
		 * Search for the next word boundary, starting at the given position
		 * @param {DOMObject} container container of the start position
		 * @param {Integer} offset offset of the start position
		 * @param {boolean} searchleft true for searching to the left, false for searching to the right (default: true)
		 * @return {object} object with properties 'container' and 'offset' marking the found word boundary
		 * @method
		 */
		searchWordBoundary: function (container, offset, searchleft) {
			if (typeof searchleft === 'undefined') {
				searchleft = true;
			}
			var boundaryFound = false,
				wordBoundaryPos,
			    tempWordBoundaryPos,
			    textNode;
			while (!boundaryFound) {
				// check the node type
				var containerIndex = getIndexInParent(container);
				if (container.nodeType === 3) {
					// we are currently in a text node

					// find the nearest word boundary character
					if (!searchleft) {
						// search right
						wordBoundaryPos = container.data.substring(offset).search(this.nonWordRegex);
						if (wordBoundaryPos != -1) {
							// found a word boundary
							offset = offset + wordBoundaryPos;
							boundaryFound = true;
						} else {
							// found no word boundary, so we set the position after the container
							offset = containerIndex + 1;
							container = container.parentNode;
						}
					} else {
						// search left
						wordBoundaryPos = container.data.substring(0, offset).search(this.nonWordRegex);
						tempWordBoundaryPos = wordBoundaryPos;
						while (tempWordBoundaryPos != -1) {
							wordBoundaryPos = tempWordBoundaryPos;
							tempWordBoundaryPos = container.data.substring(wordBoundaryPos + 1, offset).search(this.nonWordRegex);
							if (tempWordBoundaryPos != -1) {
								tempWordBoundaryPos = tempWordBoundaryPos + wordBoundaryPos + 1;
							}
						}

						if (wordBoundaryPos != -1) {
							// found a word boundary
							offset = wordBoundaryPos + 1;
							boundaryFound = true;
						} else {
							// found no word boundary, so we set the position before the container
							offset = containerIndex;
							container = container.parentNode;
						}
					}
				} else if (container.nodeType === 1) {
					// we are currently in an element node (between nodes)

					if (!searchleft) {
						// check whether there is an element to the right
						if (offset < container.childNodes.length) {
							// there is an element to the right, check whether it is a word boundary element
							if (this.isWordBoundaryElement(container.childNodes[offset])) {
								// we are done
								boundaryFound = true;
							} else {
								// element to the right is no word boundary, so enter it
								container = container.childNodes[offset];
								offset = 0;
							}
						} else {
							// no element to the right, check whether the element itself is a boundary element
							if (this.isWordBoundaryElement(container)) {
								// we are done
								boundaryFound = true;
							} else {
								// element itself is no boundary element, so go to parent
								offset = containerIndex + 1;
								container = container.parentNode;
							}
						}
					} else {
						// check whether there is an element to the left
						if (offset > 0) {
							// there is an element to the left, check whether it is a word boundary element
							if (this.isWordBoundaryElement(container.childNodes[offset - 1])) {
								// we are done
								boundaryFound = true;
							} else {
								// element to the left is no word boundary, so enter it
								container = container.childNodes[offset - 1];
								offset = container.nodeType === 3 ? container.data.length : container.childNodes.length;
							}
						} else {
							// no element to the left, check whether the element itself is a boundary element
							if (this.isWordBoundaryElement(container)) {
								// we are done
								boundaryFound = true;
							} else {
								// element itself is no boundary element, so go to parent
								offset = containerIndex;
								container = container.parentNode;
							}
						}
					}
				}
			}

			if (container.nodeType !== 3) {
				textNode = this.searchAdjacentTextNode(container, offset, !searchleft);
				if (textNode) {
					container = textNode;
					offset = searchleft ? 0 : container.data.length;
				}
			}

			return {
				'container': container,
				'offset': offset
			};
		},

		/**
		 * Check whether the given dom object is empty
		 * @param {DOMObject} domObject object to check
		 * @return {boolean} true when the object is empty, false if not
		 * @method
		 */
		isEmpty: function (domObject) {
			// a non dom object is considered empty
			if (!domObject) {
				return true;
			}

			// some tags are considered to be non-empty
			if (jQuery.inArray(domObject.nodeName.toLowerCase(), this.nonEmptyTags) != -1) {
				return false;
			}

			// text nodes are not empty, if they contain non-whitespace characters
			if (domObject.nodeType === 3) {
				if (domObject.data.search(/\S/) == -1) {
					return true;
				} else if (domObject.data.length === 1 // Fix FOR IE no width chars
						&& domObject.data.charCodeAt(0) >= 0x2000) {
					return true;
				}

				return false;
			}

			// all other nodes are not empty if they contain at least one child which is not empty
			var i, childNodes;
			for (i = 0, childNodes = domObject.childNodes.length; i < childNodes; ++i) {
				if (!this.isEmpty(domObject.childNodes[i])) {
					return false;
				}
			}

			// found no contents, so the element is empty
			return true;
		},

		/**
		 * Set the cursor (collapsed selection) right after the given DOM object
		 * @param domObject DOM object
		 * @method
		 */
		setCursorAfter: function (domObject) {
			var newRange = new GENTICS.Utils.RangeObject(),
				index = getIndexInParent(domObject),
				targetNode,
				offset;

			// selection cannot be set between to TEXT_NODEs
			// if domOject is a Text node set selection at last position in that node
			if (domObject.nodeType == 3) {
				targetNode = domObject;
				offset = targetNode.nodeValue.length;

				// if domOject is a Text node set selection at last position in that node
			} else if (domObject.nextSibling && domObject.nextSibling.nodeType == 3) {
				targetNode = domObject.nextSibling;
				offset = 0;
			} else {
				targetNode = domObject.parentNode;
				offset = getIndexInParent(domObject) + 1;
			}

			newRange.startContainer = newRange.endContainer = targetNode;
			newRange.startOffset = newRange.endOffset = offset;

			// select the range
			newRange.select();

			return newRange;
		},

		/**
		 * Select a DOM node
		 * will create a new range which spans the provided dom node and selects it afterwards
		 * @param domObject DOM object
		 * @method
		 */
		selectDomNode: function (domObject) {
			var newRange = new GENTICS.Utils.RangeObject();
			newRange.startContainer = newRange.endContainer = domObject.parentNode;
			newRange.startOffset = getIndexInParent(domObject);
			newRange.endOffset = newRange.startOffset + 1;
			newRange.select();
		},

		/**
		 * Set the cursor (collapsed selection) at the start into the given DOM object
		 * @param domObject DOM object
		 * @method
		 */
		setCursorInto: function (domObject) {
			// set a new range into the given dom object
			var newRange = new GENTICS.Utils.RangeObject();
			newRange.startContainer = newRange.endContainer = domObject;
			newRange.startOffset = newRange.endOffset = 0;

			// select the range
			newRange.select();
		},


		/**
		 * "An editing host is a node that is either an Element with a contenteditable
		 * attribute set to the true state, or the Element child of a Document whose
		 * designMode is enabled."
		 * @param domObject DOM object
		 * @method
		 */
		isEditingHost: function (node) {
			return node
				&& node.nodeType == 1 //ELEMENT_NODE
				&& (node.contentEditable == "true" || (node.parentNode && node.parentNode.nodeType == 9 //DOCUEMENT_NODE
													   && node.parentNode.designMode == "on"));
		},

		/**
		 * "Something is editable if it is a node which is not an editing host, does
		 * not have a contenteditable attribute set to the false state, and whose
		 * parent is an editing host or editable."
		 * @param domObject DOM object
		 * @method
		 */
		isEditable: function (node) {
			// This is slightly a lie, because we're excluding non-HTML elements with
			// contentEditable attributes.
			return node
				&& !this.isEditingHost(node)
				&& (node.nodeType != 1 || node.contentEditable != "false") // ELEMENT_NODE
				&& (this.isEditingHost(node.parentNode) || this.isEditable(node.parentNode));
		},

		/**
		 * "The editing host of node is null if node is neither editable nor an editing
		 * host; node itself, if node is an editing host; or the nearest ancestor of
		 * node that is an editing host, if node is editable."
		 * @param domObject DOM object
		 * @method
		 */
		getEditingHostOf: function (node) {
			if (this.isEditingHost(node)) {
				return node;
			}
			if (this.isEditable(node)) {
				var ancestor = node.parentNode;
				while (!this.isEditingHost(ancestor)) {
					ancestor = ancestor.parentNode;
				}
				return ancestor;
			}
			return null;
		},

		/**
		 *
		 * "Two nodes are in the same editing host if the editing host of the first is
		 * non-null and the same as the editing host of the second."
		 * @param node1 DOM object
		 * @param node2 DOM object
		 * @method
		 */
		inSameEditingHost: function (node1, node2) {
			return this.getEditingHostOf(node1) && this.getEditingHostOf(node1) == this.getEditingHostOf(node2);
		},

		// "A block node is either an Element whose "display" property does not have
		// resolved value "inline" or "inline-block" or "inline-table" or "none", or a
		// Document, or a DocumentFragment."
		isBlockNode: function (node) {
			return node && ((node.nodeType == $_.Node.ELEMENT_NODE && $_(["inline", "inline-block", "inline-table", "none"]).indexOf($_.getComputedStyle(node).display) == -1) || node.nodeType == $_.Node.DOCUMENT_NODE || node.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE);
		},

		/**
		 * Get the first visible child of the given node.
		 * @param node node
		 * @param includeNode when set to true, the node itself may be returned, otherwise only children are allowed
		 * @param stopElements optional list of node names (upper case) of elements where to stop searching
		 * @return first visible child or null if none found
		 */
		getFirstVisibleChild: function (node, includeNode, stopElements) {
			// no node -> no child
			if (!node) {
				return null;
			}

			// check whether the node itself is visible
			if ((node.nodeType == $_.Node.TEXT_NODE && this.isEmpty(node)) || (node.nodeType == $_.Node.ELEMENT_NODE && this.getOffsetHeight(node) === 0 && jQuery.inArray(node.nodeName.toLowerCase(), this.nonEmptyTags) === -1)) {
				return null;
			}

			// if the node is a text node, or does not have children, or is not editable, it is the first visible child
			if (node.nodeType == $_.Node.TEXT_NODE || (node.nodeType == $_.Node.ELEMENT_NODE && node.childNodes.length == 0) || !jQuery(node).contentEditable()) {
				return includeNode ? node : null;
			}

			// when we find one of the stop elements, we return it
			if (jQuery.isArray(stopElements) && jQuery.inArray(node.nodeName, stopElements) >= 0) {
				return includeNode ? node : null;
			}

			// otherwise traverse through the children
			var i;
			for (i = 0; i < node.childNodes.length; ++i) {
				var visibleChild = this.getFirstVisibleChild(node.childNodes[i], true, stopElements);
				if (visibleChild != null) {
					return visibleChild;
				}
			}

			return null;
		},

		/**
		 * Get the last visible child of the given node.
		 * @param node node
		 * @param includeNode when set to true, the node itself may be returned, otherwise only children are allowed
		 * @param stopElements optional list of node names (upper case) of elements where to stop searching
		 * @return last visible child or null if none found
		 */
		getLastVisibleChild: function (node, includeNode, stopElements) {
			// no node -> no child
			if (!node) {
				return null;
			}

			// check whether the node itself is visible
			if ((node.nodeType == $_.Node.TEXT_NODE && this.isEmpty(node)) || (node.nodeType == $_.Node.ELEMENT_NODE && this.getOffsetHeight(node) === 0 && jQuery.inArray(node.nodeName.toLowerCase(), this.nonEmptyTags) === -1)) {
				return null;
			}

			// if the node is a text node, or does not have children, or is not editable, it is the first visible child
			if (node.nodeType == $_.Node.TEXT_NODE || (node.nodeType == $_.Node.ELEMENT_NODE && node.childNodes.length == 0) || !jQuery(node).contentEditable()) {
				return includeNode ? node : null;
			}

			// when we find one of the stop elements, we return it
			if (jQuery.isArray(stopElements) && jQuery.inArray(node.nodeName, stopElements) >= 0) {
				return includeNode ? node : null;
			}

			// otherwise traverse through the children
			var i;
			for (i = node.childNodes.length - 1; i >= 0; --i) {
				var visibleChild = this.getLastVisibleChild(node.childNodes[i], true, stopElements);
				if (visibleChild != null) {
					return visibleChild;
				}
			}

			return null;
		},

		/**
		 * Workaround to get the offsetHeight from a DOM node. IE7 (and all IEs in IE7 mode) have a very weird bug that
		 * returns 0 when reading the offsetHeight from a DOM node for the first time. When reading again (without any
		 * modification in between), the correct value would be returned.
		 * Therefore, this method will get the offsetHeight and if it is 0, read it again.
		 * @param {DOMObject} node
		 * @return {number} offsetHeight
		 */
		getOffsetHeight: function (node) {
			if (!node) {
				return 0;
			}
			var offsetHeight = node.offsetHeight;
			if (offsetHeight === 0) {
				offsetHeight = node.offsetHeight;
			}
			return offsetHeight;
		},

		/**
		 * Check whether the given node is an empty block level element.
		 * A block level element is considered empty, if it does not contain other children than
		 * <ol>
		 * <li>empty text nodes</li>
		 * <li>a final break</li>
		 * </ol>
		 * @param {DOMObject} node to check
		 * @return {boolean} true for empty block level elements
		 */
		isEmptyBlockLevelElement: function (node) {
			if (!this.isBlockLevelElement(node)) {
				return false;
			}

			if (!node.childNodes) {
				return true;
			}

			var len = node.childNodes.length, breakFound = false, i, child;
			for (i = 0; i < len; i++) {
				child = node.childNodes[i];
				if (child.nodeName === 'BR') {
					if (breakFound) {
						// we found another break, so the element is not empty
						return false;
					} else {
						// first break found, no harm done (so far)
						breakFound = true;
					}
				} else if (!this.isEmpty(child)) {
					return false;
				}
			}

			return true;
		}
	});


	/**
	 * Create the singleton object
	 * @hide
	 */
	GENTICS.Utils.Dom = new Dom();

	return GENTICS.Utils.Dom;

});

/* pluginmanager.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
// Do not add dependencies that require depend on aloha/core
define('aloha/pluginmanager',[
	'jquery',
	'util/class'
], function (
	$,
	Class
) {
	

	var Aloha = window.Aloha;

	/**
	 * For each plugin setting, assigns it into its corresponding plugin.
	 *
	 * @param {Array.<Plugin>} plugins
	 * @param {object<string, object>} settings
	 */
	function mapSettingsIntoPlugins(plugins, settings) {
		var plugin;
		for (plugin in settings) {
			if (settings.hasOwnProperty(plugin) && plugins[plugin]) {
				plugins[plugin].settings = settings[plugin] || {};
			}
		}
	}

	/**
	 * Retrieves a set of plugins or the given `names' list, from among those
	 * specified in `plugins'.
	 *
	 * @param {object<string, object>} plugins
	 * @param {Array.<string>} names List of plugins names.
	 * @return {Array.<Plugins>} List of available plugins.
	 */
	function getPlugins(plugins, names) {
		var available = [];
		var plugin;
		var i;
		for (i = 0; i < names.length; i++) {
			plugin = plugins[names[i]];
			if (plugin) {
				available.push(plugin);
			}
		}
		return available;
	}

	/**
	 * Initializes the plugins in the given list.
	 *
	 * @param {Array.<Plugins>} plugins Plugins to initialize.
	 * @param {function} callback Function to invoke once all plugins have been
	 *                            successfully initialized.
	 */
	function initializePlugins(plugins, callback) {
		if (0 === plugins.length) {
			if (callback) {
				callback();
			}
			return;
		}
		var numToEnable = plugins.length;
		var onInit = function () {
			if (0 === --numToEnable && callback) {
				callback();
			}
		};
		var i;
		var ret;
		var plugin;
		for (i = 0; i < plugins.length; i++) {
			plugin = plugins[i];
			plugin.settings = plugin.settings || {};
			if (typeof plugin.settings.enabled === 'undefined') {
				plugin.settings.enabled = true;
			}
			if (plugin.settings.enabled && plugin.checkDependencies()) {
				ret = plugin.init();
				if (ret && typeof ret.done === 'function') {
					ret.done(onInit);
				} else {
					onInit();
				}
			} else {
				onInit();
			}
		}
	}

	/**
	 * The Plugin Manager controls the lifecycle of all Aloha Plugins.
	 *
	 * @namespace Aloha
	 * @class PluginManager
	 * @singleton
	 */
	return new (Class.extend({

		plugins: {},

		/**
		 * Initialize all registered plugins.
		 *
		 * @param {function} next Callback to invoke after plugins have
		 *                        succefully initialized.
		 * @param {Array.<string>} enabled A list of plugin names which are to
		 *                                 be enable.
		 */
		init: function (next, enabled) {
			var manager = this;
			var plugins = manager.plugins;

			mapSettingsIntoPlugins(plugins,
					Aloha && Aloha.settings && Aloha.settings.plugins);

			// Because all plugins are enabled by default if specific plugins
			// are not specified.
			var plugin;
			if (plugins && 0 === enabled.length) {
				enabled = [];
				for (plugin in plugins) {
					if (plugins.hasOwnProperty(plugin)) {
						enabled.push(plugin);
					}
				}
			}

			initializePlugins(getPlugins(plugins, enabled), next);
		},

		/**
		 * Register a plugin
		 * @param {Plugin} plugin plugin to register
		 */
		register: function (plugin) {

			if (!plugin.name) {
				throw new Error('Plugin does not have an name.');
			}

			if (this.plugins[plugin.name]) {
				throw new Error('Already registered the plugin "' + plugin.name + '"!');
			}

			this.plugins[plugin.name] = plugin;
		},

		/**
		 * Pass the given jQuery object, which represents an editable to all plugins, so that they can make the content clean (prepare for saving)
		 * @param obj jQuery object representing an editable
		 * @return void
		 * @hide
		 */
		makeClean: function (obj) {
			var i, plugin;
			// iterate through all registered plugins
			for (plugin in this.plugins) {
				if (this.plugins.hasOwnProperty(plugin)) {
					if (Aloha.Log.isDebugEnabled()) {
						Aloha.Log.debug(this, 'Passing contents of HTML Element with id { ' + obj.attr('id') + ' } for cleaning to plugin { ' + plugin + ' }');
					}
					this.plugins[plugin].makeClean(obj);
				}
			}
		},

		/**
		 * Expose a nice name for the Plugin Manager
		 * @hide
		 */
		toString: function () {
			return 'pluginmanager';
		}

	}))();
});

/*core.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/core',[
	'jquery',
	'aloha/pluginmanager'
], function (
	$,
	PluginManager
) {
	

	var Aloha = window.Aloha;

	/**
	 * Checks whether the current user agent is supported.
	 *
	 * @return {boolean} True if Aloha supports the current browser.
	 */
	function isBrowserSupported() {
		var browser = Aloha.browser;
		var version = browser.version;
		return !(
			// Chrome 21
			(browser.chrome && parseFloat(version) < 21) ||
			// Safari 4
			(browser.webkit && !browser.chrome && parseFloat(version) < 532.5) ||
			// FF 3.5
			(browser.mozilla && parseFloat(version) < 1.9) ||
			// IE 7
			(browser.msie && version < 7) ||
			// Right now Opera needs some work
			(browser.opera && version < 11)
		);
	}

	/**
	 * Checks whether the given jQuery event originates from an Aloha dialog
	 * element.
	 *
	 * This is used to facilitate a hackish way of preventing blurring
	 * editables when interacting with Aloha UI modals.
	 *
	 * @param {jQuery<Event>} $event
	 * @return {boolean} True if $event is initiated from within an Aloha
	 *                   dialog element.
	 */
	function originatesFromDialog($event) {
		return $($event.target).closest('.aloha-dialog').length > 0;
	}

	/**
	 * Registers events on the documents to cause editables to be blurred when
	 * clicking outside of editables.
	 *
	 * Hack: Except when the click originates from a modal dialog.
	 */
	function registerEvents() {
		$('html').mousedown(function ($event) {
			if (Aloha.activeEditable && !Aloha.eventHandled
					&& !originatesFromDialog($event)) {
				Aloha.deactivateEditable();
			}
		}).mouseup(function () {
			Aloha.eventHandled = false;
		});
	}

	/**
	 * Initialize Aloha.
	 *
	 * @param {function} event Event to trigger after completing tasks.
	 * @param {function} next Function to call after completing tasks.
	 */
	function initAloha(event, next) {
		if (!isBrowserSupported()) {
			var console = window.console;
			if (console) {
				var fn = console.error ? 'error' : console.log ? 'log' : null;
				if (fn) {
					console[fn]('This browser is not supported');
				}
			}
			return;
		}

		// Because different css is to be applied based on what the user-agent
		// supports.  For example: outlines do not render in IE7.
		if (Aloha.browser.webkit) {
			$('html').addClass('aloha-webkit');
		} else if (Aloha.browser.opera) {
			$('html').addClass('aloha-opera');
		} else if (Aloha.browser.msie) {
			$('html').addClass('aloha-ie' + parseInt(Aloha.browser.version, 10));
		} else if (Aloha.browser.mozilla) {
			$('html').addClass('aloha-mozilla');
		}

		if (navigator.appVersion.indexOf('Win') !== -1) {
			Aloha.OSName = 'Win';
		} else if (navigator.appVersion.indexOf('Mac') !== -1) {
			Aloha.OSName = 'Mac';
		} else if (navigator.appVersion.indexOf('X11') !== -1) {
			Aloha.OSName = 'Unix';
		} else if (navigator.appVersion.indexOf('Linux') !== -1) {
			Aloha.OSName = 'Linux';
		}

		// Fix inconsistent browser behavior in Internet Explorer:
		// per default the Internet Explorer's own feature AutoUrlDetect is turned on.
		// When this feature is active the "editor will automatically create a hyperlink
		// for any text that is formatted as a URL."
		// https://msdn.microsoft.com/en-us/library/aa769893(v=vs.85).aspx
		// http://blogs.msdn.com/b/ieinternals/archive/2010/09/15/ie9-beta-minor-change-list.aspx
		//
		// Since this behavior is different to all other browsers we will
		// turn this feature off in Internet Explorer >= 9 (turning it off is supported
		// starting from IE9)
		if (Aloha.browser.msie && parseFloat(Aloha.browser.version) >= 9.0 && typeof document.execCommand === 'function') {
			document.execCommand('AutoUrlDetect', false, false);
		}

		registerEvents();
		Aloha.settings.base = Aloha.getAlohaUrl();
		Aloha.Log.init();

		// Initialize error handler for general javascript errors.
		if (Aloha.settings.errorhandling) {
			window.onerror = function (msg, url, line) {
				Aloha.Log.error(Aloha, 'Error message: ' + msg + '\nURL: ' +
				                       url + '\nLine Number: ' + line);
				return true;
			};
		}

		event();
		next();
	}

	/**
	 * Initialize managers
	 *
	 * @param {function} event Event to trigger after completing tasks.
	 * @param {function} next Function to call after completing tasks.
	 */
	function initRepositoryManager(event, next) {
		Aloha.RepositoryManager.init();
		event();
		next();
	}

	/**
	 * Initialize Aloha plugins.
	 *
	 *
	 * @param {function} event Event to trigger after completing tasks.
	 * @param {function} next Callback that will be invoked after all plugins
	 *                        have been initialized.  Whereas plugins are loaded
	 *                        synchronously, plugins may initialize
	 *                        asynchronously.
	 */
	function initPluginManager(event, next) {
		// Because if there are no loadedPlugins specified, then the default is
		// to initialized all available plugins.
		if (0 === Aloha.loadedPlugins.length) {
			var plugins = PluginManager.plugins;
			var plugin;
			for (plugin in plugins) {
				if (plugins.hasOwnProperty(plugin)) {
					Aloha.loadedPlugins.push(plugin);
				}
			}
		}

		var fired = false;

		PluginManager.init(function () {
			if (!fired) {
				event();
				fired = true;
			}
			next();
		}, Aloha.loadedPlugins);

		if (!fired) {
			event();
			fired = true;
		}
	}

	/**
	 * Begin initialize editables.
	 *
	 * @param {function} event Event to trigger after completing tasks.
	 * @param {function} next Function to call after completing tasks.
	 */
	function initEditables(event, next) {
		var i;
		for (i = 0; i < Aloha.editables.length; i++) {
			if (!Aloha.editables[i].ready) {
				Aloha.editables[i].init();
			}
		}
		event();
		next();
	}

	/**
	 * Initialization phases.
	 *
	 * These stages denote the initialization states which Aloha will go
	 * through from loading to ready.
	 *
	 * Each phase object contains the following properties:
	 *        fn : The process that is to be invoked during this phase.
	 *             Will take two functions: event() and next().
	 *     event : The event name, which if provided, will be fired after the
	 *             phase has been started (optional).
	 *  deferred : A $.Deferred() object to hold event handlers until that
	 *             initialization phase has been done (optional).
	 *
	 * @type {Array.<phase>}
	 */
	var phases = [
		// Phase 0: Waiting for initialization to begin.  This is the state at
		//          load-time.
		{
			fn: null,
			event: null,
			deferred: null
		},

		// Phase 1: DOM is ready; performing compatibility checks, registering
		//          basic events, and initializing logging.
		{
			fn: initAloha,
			event: null,
			deferred: null
		},

		// Phase 2: Initial checks have passed; Initializing repository manger.
		{
			fn: initRepositoryManager,
			event: null,
			deferred: null
		},

		// Phase 3: Repository manager is ready for use; commence
		//          initialization of configured or default plugins.
		{
			fn: initPluginManager,
			event: 'aloha-plugins-loaded',
			deferred: null
		},

		// Phase 4: Plugins have all begun their initialization process, but it
		//          is not necessary that they have completed.  Start
		//          initializing editable, along with their scaffolding UI.
		//          Editables will not be fully initialized however, until
		//          plugins have finished initialization.
		{
			fn: initEditables,
			event: null,
			deferred: null
		},

		// Phase 5: The "ready" state.  Notify the system that Aloha is fully
		//          loaded, and ready for use.
		{
			fn: null,
			event: 'aloha-ready',
			deferred: null
		}
	];


	/**
	 * Base Aloha Object
	 * @namespace Aloha
	 * @class Aloha The Aloha base object, which contains all the core functionality
	 * @singleton
	 */
	$.extend(true, Aloha, {

		/**
		 * The Aloha Editor Version we are using
		 * It should be set by us and updated for the particular branch
		 * @property
		 */
		version: '${version}',

		/**
		 * Array of editables that are managed by Aloha
		 * @property
		 * @type Array
		 */
		editables: [],

		/**
		 * The currently active editable is referenced here
		 * @property
		 * @type Aloha.Editable
		 */
		activeEditable: null,

		/**
		 * settings object, which will contain all Aloha settings
		 * @cfg {Object} object Aloha's settings
		 */
		settings: {},

		/**
		 * defaults object, which will contain all Aloha defaults
		 * @cfg {Object} object Aloha's settings
		 */
		defaults: {},

		/**
		 * Namespace for ui components
		 */
		ui: {},

		/**
		 * This represents the name of the users OS. Could be:
		 * 'Mac', 'Linux', 'Win', 'Unix', 'Unknown'
		 * @property
		 * @type string
		 */
		OSName: 'Unknown',

		/**
		 * A list of loaded plugin names, available after the STAGES.PLUGINS
		 * initialization phase.
		 *
		 * @type {Array.<string>}
		 * @internal
		 */
		loadedPlugins: [],

		/**
		 * Maps names of plugins (link) to the base URL (../plugins/common/link).
		 */
		_pluginBaseUrlByName: {},

		/**
		 * Start the initialization process.
		 */
		init: function () {
			Aloha.initialize(phases);
		},

		/**
		 * Returns list of loaded plugins (without Bundle name)
		 *
		 * @return array
		 */
		getLoadedPlugins: function () {
			return this.loadedPlugins;
		},

		/**
		 * Returns true if a certain plugin is loaded, false otherwise.
		 *
		 * @param {string} plugin Name of plugin
		 * @return {boolean} True if plugin with given name is load.
		 */
		isPluginLoaded: function (name) {
			var loaded = false;
			$.each(this.loadedPlugins, function (i, plugin) {
				if (name === plugin.toString()) {
					loaded = true;
					return false;
				}
			});
			return loaded;
		},

		/**
		 * Activates editable and deactivates all other Editables.
		 *
		 * @param {Editable} editable the Editable to be activated
		 */
		activateEditable: function (editable) {
			// Because editables may be removed on blur, Aloha.editables.length
			// is not cached.
			var editables = Aloha.editables;
			var i;
			for (i = 0; i < editables.length; i++) {
				if (editables[i] !== editable && editables[i].isActive) {
					editables[i].blur();
				}
			}
			Aloha.activeEditable = editable;
		},

		/**
		 * Returns the current Editable
		 * @return {Editable} returns the active Editable
		 */
		getActiveEditable: function () {
			return Aloha.activeEditable;
		},

		/**
		 * Deactivates the active Editable.
		 *
		 * TODO: Would be better named "deactivateActiveEditable".
		 */
		deactivateEditable: function () {
			if (Aloha.activeEditable) {
				Aloha.activeEditable.blur();
				Aloha.activeEditable = null;
			}
		},

		/**
		 * Gets an editable by an ID or null if no Editable with that ID
		 * registered.
		 *
		 * @param {string} id The element id to look for.
		 * @return {Aloha.Editable|null} An editable, or null if none if found
		 *                               for the given id.
		 */
		getEditableById: function (id) {
			// Because if the element is a textarea, then it's necessary to
			// route to the editable div.
			var $editable = $('#' + id);
			if ($editable.length
					&& 'textarea' === $editable[0].nodeName.toLowerCase()) {
				id = id + '-aloha';
			}
			var i;
			for (i = 0; i < Aloha.editables.length; i++) {
				if (Aloha.editables[i].getId() === id) {
					return Aloha.editables[i];
				}
			}
			return null;
		},

		/**
		 * Checks whether an object is a registered Aloha Editable.
		 * @param {jQuery} obj the jQuery object to be checked.
		 * @return {boolean}
		 */
		isEditable: function (obj) {
			var i, editablesLength;

			for (i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
				if (Aloha.editables[i].originalObj.get(0) === obj) {
					return true;
				}
			}
			return false;
		},

		/**
		 * Gets the nearest editable parent of the DOM element contained in the
		 * given jQuery object.
		 *
		 * @param {jQuery} $element jQuery unit set containing DOM element.
		 * @return {Aloha.Editable} Editable, or null if none found.
		 */
		getEditableHost: (function () {
			var getEditableOf = function (editable) {
				var i;
				for (i = 0; i < Aloha.editables.length; i++) {
					if (Aloha.editables[i].originalObj[0] === editable) {
						return Aloha.editables[i];
					}
				}
				return null;
			};

			return function ($element) {
				if (!$element || 0 === $element.length) {
					return null;
				}
				var editable = getEditableOf($element[0]);
				if (!editable) {
					$element.parents().each(function (__unused__, node) {
						editable = getEditableOf(node);
						if (editable) {
							return false;
						}
					});
				}
				return editable;
			};
		}()),

		/**
		 * Logs a message to the console.
		 *
		 * @param {string} level Level of the log
		 *                       ("error", "warn" or "info", "debug").
		 * @param {object} component Component that calls the log.
		 * @param {string} message Log message.
		 * @hide
		 */
		log: function (level, component, message) {
			if (typeof Aloha.Log !== 'undefined') {
				Aloha.Log.log(level, component, message);
			}
		},

		/**
		 * Register the given editable.
		 *
		 * @param {Editable} editable to register.
		 * @hide
		 */
		registerEditable: function (editable) {
			Aloha.editables.push(editable);
		},

		/**
		 * Unregister the given editable. It will be deactivated and removed
		 * from editables.
		 *
		 * @param {Editable} editable The editable to unregister.
		 * @hide
		 */
		unregisterEditable: function (editable) {
			var index = $.inArray(editable, Aloha.editables);
			if (index !== -1) {
				Aloha.editables.splice(index, 1);
			}
		},

		/**
		 * Check whether at least one editable was modified.
		 *
		 * @return {boolean} True when at least one editable was modified,
		 *                   false otherwise.
		 */
		isModified: function () {
			var i;
			for (i = 0; i < Aloha.editables.length; i++) {
				if (Aloha.editables[i].isModified
						&& Aloha.editables[i].isModified()) {
					return true;
				}
			}
			return false;
		},

		/**
		 * Determines the Aloha Url.
		 *
		 * @return {String} Aloha's baseUrl setting or "" if not set.
		 */
		getAlohaUrl: function (suffix) {
			return Aloha.settings.baseUrl || '';
		},

		/**
		 * Gets the plugin's url.
		 *
		 * @param {string} name The name with which the plugin was registered
		 *                      with.
		 * @return {string} The fully qualified url of this plugin.
		 */
		getPluginUrl: function (name) {
			if (!name) {
				return null;
			}
			var url = Aloha.settings._pluginBaseUrlByName[name];
			if (url) {
				// Check if url is absolute and attach base url if it is not.
				if (!url.match("^(\/|http[s]?:).*")) {
					url = Aloha.getAlohaUrl() + '/' + url;
				}
			}
			return url;
		},

		/**
		 * Disable object resizing by executing command 'enableObjectResizing',
		 * if the browser supports this.
		 */
		disableObjectResizing: function () {
			try {
				// This will disable browsers image resizing facilities in
				// order disable resize handles.
				var supported;
				try {
					supported = document.queryCommandSupported('enableObjectResizing');
				} catch (e) {
					supported = false;
					Aloha.Log.log('enableObjectResizing is not supported.');
				}
				if (supported) {
					document.execCommand('enableObjectResizing', false, false);
					Aloha.Log.log('enableObjectResizing disabled.');
				}
			} catch (e2) {
				Aloha.Log.error(e2, 'Could not disable enableObjectResizing');
				// this is just for others, who will not support disabling enableObjectResizing
			}
		},

		/**
		 * Disable native table editing
		 */
		disableInlineTableEditing: function () { // enableInlineTableEditing
			try {
				// This will disable browsers native table editing facilities in
				// order to disable resize handles.
				var supported;
				try {
					supported = document.queryCommandSupported('enableInlineTableEditing');
				} catch (e) {
					supported = false;
					Aloha.Log.log('enableInlineTableEditing is not supported.');
				}
				if (supported) {
					document.execCommand('enableInlineTableEditing', false, false);
					Aloha.Log.log('enableInlineTableEditing disabled.');
				}
			} catch (e2) {
				Aloha.Log.error(e2, 'Could not disable enableInlineTableEditing');
				// this is just for others, who will not support disabling enableInlineTableEditing
			}
		},

		/**
		 * Human-readable string representation of this.
		 *
		 * @hide
		 */
		toString: function () {
			return 'Aloha';
		},

		/**
		 * Shim to replace $.browser
		 *
		 * @hide
		 */
		browser: (function () {
			function uaMatch(ua) {
				ua = ua.toLowerCase();

				var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
					/(webkit)[ \/]([\w.]+)/.exec(ua) ||
					/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
					/(msie) ([\w.]+)/.exec(ua) ||
					// IE11 will only identify itself via its Trident rendering engine
					/(trident)(?:.*? rv:([\w.]+))/.exec(ua) ||
					(ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)) || [];

				return {
					browser: match[1] || "",
					version: match[2] || "0"
				};
			}

			var matched = uaMatch(navigator.userAgent);
			var browser = {};

			if (matched.browser) {
				browser[matched.browser] = true;
				browser.version = matched.version;
			}

			// Chrome is Webkit, but Webkit is also Safari.
			if (browser.chrome) {
				browser.webkit = true;
			} else if (browser.webkit) {
				browser.safari = true;
			}
			// Browsers using the Trident rendering engine are also Internet Explorer
			if (browser.trident) {
				browser.msie = true;
			}

			return browser;
		}())
	});

	return Aloha;
});

/* console.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/console',[
	'aloha/core',
	'util/class',
	'jquery'
], function (
	Aloha,
	Class,
	jQuery
) {
	

	/**
	 * This is the aloha Log
	 * @namespace Aloha
	 * @class Log
	 * @singleton
	 */
	var AlohaConsole = Class.extend({
		/**
		 * Initialize the logging
		 * @hide
		 */
		init: function () {

			// initialize the logging settings (if not present)
			if (typeof Aloha.settings.logLevels === 'undefined' || !Aloha.settings.logLevels) {
				Aloha.settings.logLevels = {
					'error': true,
					'warn': true
				};
			}

			// initialize the logHistory settings (if not present)
			if (typeof Aloha.settings.logHistory === 'undefined' || !Aloha.settings.logHistory) {
				Aloha.settings.logHistory = {};
			}
			// set the default values for the loghistory
			if (!Aloha.settings.logHistory.maxEntries) {
				Aloha.settings.logHistory.maxEntries = 100;
			}
			if (!Aloha.settings.logHistory.highWaterMark) {
				Aloha.settings.logHistory.highWaterMark = 90;
			}
			if (!Aloha.settings.logHistory.levels) {
				Aloha.settings.logHistory.levels = {
					'error': true,
					'warn': true
				};
			}
			this.flushLogHistory();

			Aloha.trigger('aloha-logger-ready');
		},

		/**
		 * Log History as array of Message Objects. Every object has the properties
		 * 'level', 'component' and 'message'
		 * @property
		 * @type Array
		 * @hide
		 */
		logHistory: [],

		/**
		 * Flag, which is set as soon as the highWaterMark for the log history is reached.
		 * This flag is reset on every call of flushLogHistory()
		 * @hide
		 */
		highWaterMarkReached: false,

		/**
		 * Logs a message to the console
		 * @method
		 * @param {String} level Level of the log ('error', 'warn' or 'info', 'debug')
		 * @param {String} component Component that calls the log
		 * @param {String} message log message
		 */
		log: function (level, component, message) {


			// log ('Logging message');
			if (typeof component === 'undefined') {
				message = level;
			}
			if (typeof component !== 'string' && component && component.toString) {
				component = component.toString();
			}

			// log ('warn', 'Warning message');
			if (typeof message === 'undefined') {
				message = component;
				component = undefined;
			}

			if (typeof level === 'undefined' || !level) {
				level = 'log';
			}

			level = level.toLowerCase();

			if (typeof Aloha.settings.logLevels === "undefined") {
				return;
			}

			// now check whether the log level is activated
			if (!Aloha.settings.logLevels[level]) {
				return;
			}

			component = component || "Unkown Aloha Component";

			this.addToLogHistory({
				'level': level,
				'component': component,
				'message': message,
				'date': new Date()
			});

			var console = window.console;

			switch (level) {
			case 'error':
				if (window.console && console.error) {
					// FIXME:
					// Using console.error rather than throwing an error is very
					// problematic because we get not stack.
					// We ought to consider doing the following:
					// throw component + ': ' + message;
					if (!component && !message) {
						console.error("Error occured without message and component");
					} else {
						console.error(component + ': ' + message);
					}
				}
				break;
			case 'warn':
				if (window.console && console.warn) {
					console.warn(component + ': ' + message);
				}
				break;
			case 'info':
				if (window.console && console.info) {
					console.info(component + ': ' + message);
				}
				break;
			case 'debug':
				if (window.console && console.log) {
					console.log(component + ' [' + level + ']: ' + message);
				}
				break;
			default:
				if (window.console && console.log) {
					console.log(component + ' [' + level + ']: ' + message);
				}
				break;
			}
		},

		/**
		 * Log a message of log level 'error'
		 * @method
		 * @param {String} component Component that calls the log
		 * @param {String} message log message
		 */
		error: function (component, message) {
			this.log('error', component, message);
		},

		/**
		 * Log a message of log level 'warn'
		 * @method
		 * @param {String} component Component that calls the log
		 * @param {String} message log message
		 */
		warn: function (component, message) {
			this.log('warn', component, message);
		},

		/**
		 * Log a message of log level 'info'
		 * @method
		 * @param {String} component Component that calls the log
		 * @param {String} message log message
		 */
		info: function (component, message) {
			this.log('info', component, message);
		},

		/**
		 * Log a message of log level 'debug'
		 * @param {String} component Component that calls the log
		 * @param {String} message log message
		 */
		debug: function (component, message) {
			this.log('debug', component, message);
		},

		/**
		 * Methods to mark function as deprecated for developers.
		 * @param {String} component String that calls the log
		 * @param {String} message log message
		 */
		deprecated: function (component, message) {
			this.log('warn', component, message);
			// help the developer to locate the call.
			if (Aloha.settings.logLevels.deprecated) {
				throw new Error(message);
			}
		},

		/**
		 * Check whether the given log level is currently enabled
		 * @param {String} level
		 * @return true when log level is enabled, false if not
		 */
		isLogLevelEnabled: function (level) {
			return Aloha.settings && Aloha.settings.logLevels && Aloha.settings.logLevels[level];
		},

		/**
		 * Check whether error logging is enabled
		 * @return true if error logging is enabled, false if not
		 */
		isErrorEnabled: function () {
			return this.isLogLevelEnabled('error');
		},

		/**
		 * Check whether warn logging is enabled
		 * @return true if warn logging is enabled, false if not
		 */
		isWarnEnabled: function () {
			return this.isLogLevelEnabled('warn');
		},

		/**
		 * Check whether info logging is enabled
		 * @return true if info logging is enabled, false if not
		 */
		isInfoEnabled: function () {
			return this.isLogLevelEnabled('info');
		},

		/**
		 * Check whether debug logging is enabled
		 * @return true if debug logging is enabled, false if not
		 */
		isDebugEnabled: function () {
			return this.isLogLevelEnabled('debug');
		},

		/**
		 * Add the given entry to the log history. Check whether the highWaterMark has been reached, and fire an event if yes.
		 * @param {Object} entry entry to be added to the log history
		 * @hide
		 */
		addToLogHistory: function (entry) {

			if (!Aloha.settings.logHistory) {
				this.init();
			}

			// when maxEntries is set to something illegal, we do nothing (log history is disabled)
			// check whether the level is one we like to have logged
			if (Aloha.settings.logHistory.maxEntries <= 0 || !Aloha.settings.logHistory.levels[entry.level]) {

				return;
			}

			// first add the entry as last element to the history array
			this.logHistory.push(entry);

			// check whether the highWaterMark was reached, if so, fire an event
			if (!this.highWaterMarkReached) {

				if (this.logHistory.length >= Aloha.settings.logHistory.maxEntries * Aloha.settings.logHistory.highWaterMark / 100) {

					// fire the event
					Aloha.trigger('aloha-log-full');
					// set the flag (so we will not fire the event again until the logHistory is flushed)
					this.highWaterMarkReached = true;
				}
			}

			// check whether the log is full and eventually remove the oldest entries
			// @todo remove old entries when aloha-log-full event is triggered
			while (this.logHistory.length > Aloha.settings.logHistory.maxEntries) {
				this.logHistory.shift();
			}
		},

		/**
		 * Get the log history
		 * @return log history as array of objects
		 * @hide
		 */
		getLogHistory: function () {
			return this.logHistory;
		},

		/**
		 * Flush the log history. Remove all log entries and reset the flag for the highWaterMark
		 * @return void
		 * @hide
		 */
		flushLogHistory: function () {
			this.logHistory = [];
			this.highWaterMarkReached = false;
		}
	});

	/**
	 * Create the Log object
	 * @hide
	 */
	AlohaConsole = new AlohaConsole();

	// add to log namespace for compatiblility.
	Aloha.Log = Aloha.Console = AlohaConsole;
	return AlohaConsole;
});

/* range.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
// Ensure GENTICS Namespace
window.GENTICS = window.GENTICS || {};
window.GENTICS.Utils = window.GENTICS.Utils || {};

define('util/range',[
	'jquery',
	'util/dom',
	'util/class',
	'aloha/console',
	'aloha/rangy-core'
], function (jQuery, Dom, Class, console, rangy) {
	

	var GENTICS = window.GENTICS;
	var Aloha = window.Aloha;

	function selfAndParentsUntil(container, limit) {
		var parents = [],
			cur;
		if (1 === container.nodeType) {
			cur = container;
		} else {
			cur = container.parentNode;
		}
		for (;;) {
			if (!cur || cur === limit || 9 === cur.nodeType) {
				break;
			}
			if (1 === cur.nodeType) {
				parents.push(cur);
			}
			cur = cur.parentNode;
		}
		return parents;
	}

	/**
	 * Find tag with tag name 'TagName' by recursion in the RangeTree
	 * of a Range Selection
	 *
	 * @param {RangeTree []} array Range Tree class
	 * @param {String} tagName Element Tag Name
	 * @param {DOMObject []} objs arrays containing the elements whidh Tag Name
	 * is 'tagName'
	 */
	function findTagInRangeTreeRecursion(array, tagName, objs) {
		var element,
			i,
			len = array.length;
		for (i = 0; i < len; i++) {
			element = array[i];
			if (element.type !== 'none' && element.type !== 'collapsed') {
				if (element.domobj.nodeName === tagName) {
					objs.push(element.domobj);
				}
				if (element.children.length > 0) {
					findTagInRangeTreeRecursion(element.children, tagName, objs);
				}
			}
		}
	}

	/**
	 * @namespace GENTICS.Utils
	 * @class RangeObject
	 * Represents a selection range in the browser that
	 * has some advanced features like selecting the range.
	 * @param {object} param if boolean true is passed, the range will be deducted from the current browser selection.
	 * If another rangeObject is passed, it will be cloned.
	 * If nothing is passed, the rangeObject will be empty.
	 * @constructor
	 */
	GENTICS.Utils.RangeObject = Class.extend({
		_constructor: function (param) {
			// Take the values from the passed object
			if (typeof param === 'object') {
				if (typeof param.startContainer !== 'undefined') {
					this.startContainer = param.startContainer;
				}
				if (typeof param.startOffset !== 'undefined') {
					this.startOffset = param.startOffset;
				}
				if (typeof param.endContainer !== 'undefined') {
					this.endContainer = param.endContainer;
				}
				if (typeof param.endOffset !== 'undefined') {
					this.endOffset = param.endOffset;
				}
			} else if (param === true) {
				this.initializeFromUserSelection();
			}
		},

		/**
		 * DOM object of the start container of the selection.
		 * This is always has to be a DOM text node.
		 * @property startContainer
		 * @type {DOMObject}
		 */
		startContainer: undefined,

		/**
		 * Offset of the selection in the start container
		 * @property startOffset
		 * @type {Integer}
		 */
		startOffset: undefined,

		/**
		 * DOM object of the end container of the selection.
		 * This is always has to be a DOM text node.
		 * @property endContainer
		 * @type {DOMObject}
		 */
		endContainer: undefined,

		/**
		 * Offset of the selection in the end container
		 * @property endOffset
		 * @type {Integer}
		 */
		endOffset: undefined,

		/**
		 * Delete all contents selected by the current range
		 * @param rangeTree a GENTICS.Utils.RangeTree object may be provided to start from. This parameter is optional
		 */
		deleteContents: function () {

			Dom.removeRange(this);

		},

		/**
		 * Output some log
		 * TODO: move this to Aloha.Log
		 * @param message log message to output
		 * @return void
		 * @deprecated
		 * @hide
		 */
		log: function (message) {
			console.deprecated('Utils.RangeObject', 'log() is deprecated. use ' + 'console.log() from module "aloha/console" instead: ' + message);
		},

		/**
		 * Method to test if a range object is collapsed.
		 * A range is considered collapsed if either no endContainer exists or the endContainer/Offset equal startContainer/Offset
		 * @return {boolean} true if collapsed, false otherwise
		 * @method
		 */
		isCollapsed: function () {
			return (!this.endContainer || (this.startContainer === this.endContainer && this.startOffset === this.endOffset));
		},

		/**
		 * Method to (re-)calculate the common ancestor container and to get it.
		 * The common ancestor container is the DOM Object which encloses the
		 * whole range and is nearest to the start and end container objects.
		 * @return {DOMObject} get the common ancestor container
		 * @method
		 */
		getCommonAncestorContainer: function () {
			if (this.commonAncestorContainer) {
				// sometimes it's cached (or was set)
				return this.commonAncestorContainer;
			}
			// if it's not cached, calculate and then cache it
			this.updateCommonAncestorContainer();

			// now return it anyway
			return this.commonAncestorContainer;
		},

		/**
		 * Get the parent elements of the startContainer/endContainer up to the given limit. When the startContainer/endContainer
		 * is no text element, but a node, the node itself is returned as first element.
		 * @param {jQuery} limit limit object (default: body)
		 * @param {boolean} fromStart true to fetch the parents from the startContainer, false for the endContainer
		 * @return {jQuery} parent elements of the startContainer/endContainer as jQuery objects
		 * @method
		 */
		getContainerParents: function (limit, fromEnd) {
			// TODO cache the calculated parents
			var container = fromEnd ? this.endContainer : this.startContainer;
			if (!container) {
				return false;
			}
			return jQuery(selfAndParentsUntil(container, limit ? limit[0] : null));
		},

		/**
		 * Get the parent elements of the startContainer up to the given limit. When the startContainer
		 * is no text element, but a node, the node itself is returned as first element.
		 * @param {jQuery} limit limit object (default: body)
		 * @return {jQuery} parent elements of the startContainer as jQuery objects
		 * @method
		 */
		getStartContainerParents: function (limit) {
			return this.getContainerParents(limit, false);
		},

		/**
		 * Get the parent elements of the endContainer up to the given limit. When the endContainer is
		 * no text element, but a node, the node itself is returned as first element.
		 * @param {jQuery} limit limit object (default: body)
		 * @return {jQuery} parent elements of the endContainer as jQuery objects
		 * @method
		 */
		getEndContainerParents: function (limit) {
			return this.getContainerParents(limit, true);
		},

		/**
		 * TODO: the commonAncestorContainer is not calculated correctly, if either the start or
		 * the endContainer would be the cac itself (e.g. when the startContainer is a textNode
		 * and the endContainer is the startContainer's parent <p>). in this case the cac will be set
		 * to the parent div
		 * Method to update a range object internally
		 * @param commonAncestorContainer (DOM Object); optional Parameter; if set, the parameter
		 * will be used instead of the automatically calculated CAC
		 * @return void
		 * @hide
		 */
		updateCommonAncestorContainer: function (commonAncestorContainer) {
			// if no parameter was passed, calculate it
			if (!commonAncestorContainer) {
				// this will be needed either right now for finding the CAC or later for the crossing index
				var parentsStartContainer = this.getStartContainerParents(),
					parentsEndContainer = this.getEndContainerParents(),
					i;

				// find the crossing between startContainer and endContainer parents (=commonAncestorContainer)
				if (!(parentsStartContainer.length > 0 && parentsEndContainer.length > 0)) {
					console.warn('aloha/range', 'could not find commonAncestorContainer');
					return false;
				}

				for (i = 0; i < parentsStartContainer.length; i++) {
					if (parentsEndContainer.index(parentsStartContainer[i]) != -1) {
						this.commonAncestorContainer = parentsStartContainer[i];
						break;
					}
				}
			} else {
				this.commonAncestorContainer = commonAncestorContainer;
			}

			// if everything went well, return true :-)
			console.debug(commonAncestorContainer ? 'commonAncestorContainer was set successfully' : 'commonAncestorContainer was calculated successfully');
			return true;
		},

		/**
		 * Helper function for selection in IE. Creates a collapsed text range at the given position
		 * @param container container
		 * @param offset offset
		 * @return collapsed text range at that position
		 * @hide
		 */
		getCollapsedIERange: function (container, offset) {
			// create a text range
			var ieRange = document.body.createTextRange(),
				tmpRange,
			    right,
			    parent,
			    left;

			// search to the left for the next element
			left = this.searchElementToLeft(container, offset);
			if (left.element) {
				// found an element, set the start to the end of that element
				tmpRange = document.body.createTextRange();
				tmpRange.moveToElementText(left.element);
				ieRange.setEndPoint('StartToEnd', tmpRange);

				// and correct the start
				if (left.characters !== 0) {
					ieRange.moveStart('character', left.characters);
				} else {
					// this is a hack, when we are at the start of a text node, move the range anyway
					ieRange.moveStart('character', 1);
					ieRange.moveStart('character', -1);
				}
			} else {
				// found nothing to the left, so search right
				right = this.searchElementToRight(container, offset);
				// also found no element to the right, use the container itself
				parent = container.nodeType == 3 ? container.parentNode : container;
				tmpRange = document.body.createTextRange();
				tmpRange.moveToElementText(parent);
				ieRange.setEndPoint('StartToStart', tmpRange);

				// and correct the start
				if (left.characters !== 0) {
					ieRange.moveStart('character', left.characters);
				}
			}
			ieRange.collapse();

			return ieRange;
		},

		/**
		 * Sets the visible selection in the Browser based on the range object.
		 * If the selection is collapsed, this will result in a blinking cursor,
		 * otherwise in a text selection.
		 * @method
		 */
		select: function () {
			var ieRange, endRange, startRange, range, sel;

			if (typeof this.startContainer === 'undefined' || typeof this.endContainer === 'undefined') {
				console.warn('can not select an empty range');
				return false;
			}

			// create a range
			range = rangy.createRange();
			// set start and endContainer
			range.setStart(this.startContainer, this.startOffset);
			range.setEnd(this.endContainer, this.endOffset);

			// update the selection
			sel = rangy.getSelection();
			// Selection type 'Control' (elements selected), 'Text' (text selected), 'None' (nothing selected) 
			// (http://help.dottoro.com/ljitmswc.php)
			// If the selection is 'Text' or 'Control' and the range is collapsed then there is nothing selected, and 
			// this can produce a Javascript Error only in IE. (Reproducible: Just by dragging and drop block elements)
			// catching the exception so the execution can continue
			try {
				sel.setSingleRange(range);
			} catch (e) {
				console.warn(e);
			}
		},

		/**
		 * Starting at the given position, search for the next element to the left and count the number of characters are in between
		 * @param container container of the startpoint
		 * @param offset offset of the startpoint in the container
		 * @return object with 'element' (null if no element found) and 'characters'
		 * @hide
		 */
		searchElementToLeft: function (container, offset) {
			var checkElement,
			    characters = 0;

			if (container.nodeType === 3) {
				// start is in a text node
				characters = offset;
				// begin check at the element to the left (if any)
				checkElement = container.previousSibling;
			} else {
				// start is between nodes, begin check at the element to the left (if any)
				if (offset > 0) {
					checkElement = container.childNodes[offset - 1];
				}
			}

			// move to the right until we find an element
			while (checkElement && checkElement.nodeType === 3) {
				characters += checkElement.data.length;
				checkElement = checkElement.previousSibling;
			}

			return {
				'element': checkElement,
				'characters': characters
			};
		},

		/**
		 * Starting at the given position, search for the next element to the right and count the number of characters that are in between
		 * @param container container of the startpoint
		 * @param offset offset of the startpoint in the container
		 * @return object with 'element' (null if no element found) and 'characters'
		 * @hide
		 */
		searchElementToRight: function (container, offset) {
			var checkElement,
			    characters = 0;

			if (container.nodeType === 3) {
				// start is in a text node
				characters = container.data.length - offset;

				// begin check at the element to the right (if any)
				checkElement = container.nextSibling;
			} else {
				// start is between nodes, begin check at the element to the right (if any)
				if (offset < container.childNodes.length) {
					checkElement = container.childNodes[offset];
				}
			}

			// move to the right until we find an element
			while (checkElement && checkElement.nodeType === 3) {
				characters += checkElement.data.length;
				checkElement = checkElement.nextSibling;
			}

			return {
				'element': checkElement,
				'characters': characters
			};
		},

		/**
		 * Method which updates the rangeObject including all extending properties like commonAncestorContainer etc...
		 * TODO: is this method needed here? or should it contain the same code as Aloha.Selection.prototype.SelectionRange.prototype.update?
		 * @return void
		 * @hide
		 */
		update: function (event) {
			console.debug('now updating rangeObject');

			this.initializeFromUserSelection(event);
			this.updateCommonAncestorContainer();
		},

		/**
		 * Initialize the current range object from the user selection of the browser.
		 * @param event which calls the method
		 * @return void
		 * @hide
		 */
		initializeFromUserSelection: function (event) {
			var selection = rangy.getSelection(),
				browserRange;

			if (!selection) {
				return false;
			}

			// check if a ragne exists
			if (!selection.rangeCount) {
				return false;
			}

			// getBrowserRange
			browserRange = selection.getRangeAt(0);
			if (!browserRange) {
				return false;
			}

			// initially set the range to what the browser tells us
			this.startContainer = browserRange.startContainer;
			this.endContainer = browserRange.endContainer;
			this.startOffset = browserRange.startOffset;
			this.endOffset = browserRange.endOffset;

			// now try to correct the range
			this.correctRange();
			return;
		},

		/**
		 * Correct the current range. The general goal of the algorithm is to have start
		 * and end of the range in text nodes if possible and the end of the range never
		 * at the beginning of an element or text node. Details of the algorithm can be
		 * found in the code comments
		 * @method
		 */
		correctRange: function () {
			var adjacentTextNode,
			    textNode,
			    checkedElement,
			    parentNode,
			    offset;

			this.clearCaches();
			if (this.isCollapsed()) {
				// collapsed ranges are treated specially

				// first check if the range is not in a text node
				if (this.startContainer.nodeType === 1) {
					if (this.startOffset > 0 && this.startContainer.childNodes[this.startOffset - 1].nodeType === 3) {
						// when the range is between nodes (container is an element
						// node) and there is a text node to the left -> move into this text
						// node (at the end)
						this.startContainer = this.startContainer.childNodes[this.startOffset - 1];
						this.startOffset = this.startContainer.data.length;
						this.endContainer = this.startContainer;
						this.endOffset = this.startOffset;
						return;
					}

					if (this.startOffset > 0 && this.startContainer.childNodes[this.startOffset - 1].nodeType === 1) {
						// search for the next text node to the left
						adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(this.startContainer, this.startOffset, true);
						if (adjacentTextNode && jQuery(adjacentTextNode.parentNode).contentEditable()) {
							this.startContainer = this.endContainer = adjacentTextNode;
							this.startOffset = this.endOffset = adjacentTextNode.data.length;
							return;
						}
						// search for the next text node to the right
						adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(this.startContainer, this.startOffset, false);
						if (adjacentTextNode && jQuery(adjacentTextNode.parentNode).contentEditable()) {
							this.startContainer = this.endContainer = adjacentTextNode;
							this.startOffset = this.endOffset = 0;
							return;
						}
					}

					if (this.startOffset < this.startContainer.childNodes.length && this.startContainer.childNodes[this.startOffset].nodeType === 3) {
						// when the range is between nodes and there is a text node
						// to the right -> move into this text node (at the start)
						this.startContainer = this.startContainer.childNodes[this.startOffset];
						this.startOffset = 0;
						this.endContainer = this.startContainer;
						this.endOffset = 0;
						return;
					}
				}

				// when the selection is in a text node at the start, look for an adjacent text node and if one found, move into that at the end
				if (this.startContainer.nodeType === 3 && this.startOffset === 0) {
					adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(this.startContainer.parentNode, GENTICS.Utils.Dom.getIndexInParent(this.startContainer), true);
					//only move the selection if the adjacentTextNode is inside the current editable
					//the cursor should not be outside the editable
					if (adjacentTextNode && Aloha.activeEditable && jQuery(adjacentTextNode.parentNode).contentEditable() && jQuery(adjacentTextNode).closest(Aloha.activeEditable.obj).length > 0) {
						this.startContainer = this.endContainer = adjacentTextNode;
						this.startOffset = this.endOffset = adjacentTextNode.data.length;
					}
				} else if (this.startContainer.nodeType === 3 && (!jQuery(this.startContainer.parentNode).contentEditable())) {
					adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(this.startContainer.parentNode, GENTICS.Utils.Dom.getIndexInParent(this.startContainer) + 1, false, {}, { acceptUntrimmed: true });
					if (adjacentTextNode && Aloha.activeEditable && jQuery(adjacentTextNode.parentNode).contentEditable() && jQuery(adjacentTextNode).closest(Aloha.activeEditable.obj).length > 0) {
						this.startContainer = this.endContainer = adjacentTextNode;
						this.startOffset = this.endOffset = 0;
					}
				}
			} else {
				// expanded range found

				// correct the start, but only if between nodes
				if (this.startContainer.nodeType === 1) {
					// if there is a text node to the right, move into this
					if (this.startOffset < this.startContainer.childNodes.length && this.startContainer.childNodes[this.startOffset].nodeType === 3) {
						this.startContainer = this.startContainer.childNodes[this.startOffset];
						this.startOffset = 0;
					} else if (this.startOffset < this.startContainer.childNodes.length && this.startContainer.childNodes[this.startOffset].nodeType === 1) {
						// there is an element node to the right, so recursively check all first child nodes until we find a text node
						textNode = false;
						checkedElement = this.startContainer.childNodes[this.startOffset];
						while (textNode === false && checkedElement.childNodes && checkedElement.childNodes.length > 0) {
							// go to the first child of the checked element
							checkedElement = checkedElement.childNodes[0];
							// when this element is a text node, we are done
							if (checkedElement.nodeType === 3) {
								textNode = checkedElement;
							}
						}

						// found a text node, so move into it
						if (textNode !== false) {
							this.startContainer = textNode;
							this.startOffset = 0;
						}
					}
				}

				// check whether the start is inside a text node at the end
				if (this.startContainer.nodeType === 3 && this.startOffset === this.startContainer.data.length) {
					// check whether there is an adjacent text node to the right and if
					// yes, move into it
					adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(this.startContainer.parentNode, GENTICS.Utils.Dom.getIndexInParent(this.startContainer) + 1, false);
					if (adjacentTextNode) {
						this.startContainer = adjacentTextNode;
						this.startOffset = 0;
					}
				}

				// now correct the end
				if (this.endContainer.nodeType === 3 && this.endOffset === 0) {
					// we are in a text node at the start
					if (this.endContainer.previousSibling && this.endContainer.previousSibling.nodeType === 3) {
						// found a text node to the left -> move into it (at the end)
						this.endContainer = this.endContainer.previousSibling;
						this.endOffset = this.endContainer.data.length;
					} else if (this.endContainer.previousSibling && this.endContainer.previousSibling.nodeType === 1 && this.endContainer.parentNode) {
						// found an element node to the left -> move in between
						parentNode = this.endContainer.parentNode;
						for (offset = 0; offset < parentNode.childNodes.length; ++offset) {
							if (parentNode.childNodes[offset] == this.endContainer) {
								this.endOffset = offset;
								break;
							}
						}
						this.endContainer = parentNode;
					}
				}

				if (this.endContainer.nodeType == 1 && this.endOffset === 0) {
					// we are in an element node at the start, possibly move to the previous sibling at the end
					if (this.endContainer.previousSibling) {
						if (this.endContainer.previousSibling.nodeType === 3) {
							// previous sibling is a text node, move end into here (at the end)
							this.endContainer = this.endContainer.previousSibling;
							this.endOffset = this.endContainer.data.length;
						} else if (this.endContainer.previousSibling.nodeType === 1 && this.endContainer.previousSibling.childNodes && this.endContainer.previousSibling.childNodes.length > 0) {
							// previous sibling is another element node with children,
							// move end into here (at the end)
							this.endContainer = this.endContainer.previousSibling;
							this.endOffset = this.endContainer.childNodes.length;
						}
					}
				}

				// correct the end, but only if between nodes
				if (this.endContainer.nodeType == 1) {
					// if there is a text node to the left, move into this
					if (this.endOffset > 0 && this.endContainer.childNodes[this.endOffset - 1].nodeType === 3) {
						this.endContainer = this.endContainer.childNodes[this.endOffset - 1];
						this.endOffset = this.endContainer.data.length;
					} else if (this.endOffset > 0 && this.endContainer.childNodes[this.endOffset - 1].nodeType === 1) {
						// there is an element node to the left, so recursively check all last child nodes until we find a text node
						textNode = false;
						checkedElement = this.endContainer.childNodes[this.endOffset - 1];
						while (textNode === false && checkedElement.childNodes && checkedElement.childNodes.length > 0) {
							// go to the last child of the checked element
							checkedElement = checkedElement.childNodes[checkedElement.childNodes.length - 1];
							// when this element is a text node, we are done
							if (checkedElement.nodeType === 3) {
								textNode = checkedElement;
							}
						}

						// found a text node, so move into it
						if (textNode !== false) {
							this.endContainer = textNode;
							this.endOffset = this.endContainer.data.length;
						}
					}
				}
			}
		},

		/**
		 * Clear the caches for this range. This method must be called when the range itself (start-/endContainer or start-/endOffset) is modified.
		 * @method
		 */
		clearCaches: function () {
			this.commonAncestorContainer = undefined;
		},

		/**
		 * Get the range tree of this range.
		 * The range tree will be cached for every root object. When the range itself is modified, the cache should be cleared by calling GENTICS.Utils.RangeObject.clearCaches
		 * @param {DOMObject} root root object of the range tree, if non given, the common ancestor container of the start and end containers will be used
		 * @return {RangeTree} array of RangeTree object for the given root object
		 * @method
		 */
		getRangeTree: function (root) {
			// TODO cache rangeTrees
			if (typeof root === 'undefined') {
				root = this.getCommonAncestorContainer();
			}

			this.inselection = false;
			return this.recursiveGetRangeTree(root);
		},

		/**
		 * Recursive inner function for generating the range tree.
		 * @param currentObject current DOM object for which the range tree shall be generated
		 * @return array of Tree objects for the children of the current DOM object
		 * @hide
		 */
		recursiveGetRangeTree: function (currentObject) {
			// get all direct children of the given object
			var jQueryCurrentObject = jQuery(currentObject),
				childCount = 0,
				that = this,
				currentElements = [];

			jQueryCurrentObject.contents().each(function (index) {
				var type = 'none',
					startOffset = false,
					endOffset = false,
					collapsedFound = false,
					noneFound = false,
					partialFound = false,
					fullFound = false,
					i;

				// check for collapsed selections between nodes
				if (that.isCollapsed() && currentObject === that.startContainer && that.startOffset === index) {
					// insert an extra rangetree object for the collapsed range here
					currentElements[childCount] = new GENTICS.Utils.RangeTree();
					currentElements[childCount].type = 'collapsed';
					currentElements[childCount].domobj = undefined;
					that.inselection = false;
					collapsedFound = true;
					childCount++;
				}

				if (!that.inselection && !collapsedFound) {
					// the start of the selection was not yet found, so look for it now
					// check whether the start of the selection is found here

					// check is dependent on the node type
					switch (this.nodeType) {
					case 3:
						// text node
						if (this === that.startContainer) {
							// the selection starts here
							that.inselection = true;

							// when the startoffset is > 0, the selection type is only partial
							type = that.startOffset > 0 ? 'partial' : 'full';
							startOffset = that.startOffset;
							endOffset = this.length;
						}
						break;
					case 1:
						// element node
						if (this === that.startContainer && that.startOffset === 0) {
							// the selection starts here
							that.inselection = true;
							type = 'full';
						}
						if (currentObject === that.startContainer && that.startOffset === index) {
							// the selection starts here
							that.inselection = true;
							type = 'full';
						}
						break;
					}
				}

				if (that.inselection && !collapsedFound) {
					if (type == 'none') {
						type = 'full';
					}
					// we already found the start of the selection, so look for the end of the selection now
					// check whether the end of the selection is found here

					switch (this.nodeType) {
					case 3:
						// text node
						if (this === that.endContainer) {
							// the selection ends here
							that.inselection = false;

							// check for partial selection here
							if (that.endOffset < this.length) {
								type = 'partial';
							}
							if (startOffset === false) {
								startOffset = 0;
							}
							endOffset = that.endOffset;
						}
						break;
					case 1:
						// element node
						if (this === that.endContainer && that.endOffset === 0) {
							that.inselection = false;
						}
						break;
					}
					if (currentObject === that.endContainer && that.endOffset <= index) {
						that.inselection = false;
						type = 'none';
					}
				}

				// create the current selection tree entry
				currentElements[childCount] = new GENTICS.Utils.RangeTree();
				currentElements[childCount].domobj = this;
				currentElements[childCount].type = type;
				if (type == 'partial') {
					currentElements[childCount].startOffset = startOffset;
					currentElements[childCount].endOffset = endOffset;
				}

				// now do the recursion step into the current object
				currentElements[childCount].children = that.recursiveGetRangeTree(this);

				// check whether a selection was found within the children
				if (currentElements[childCount].children.length > 0) {
					for (i = 0; i < currentElements[childCount].children.length; ++i) {
						switch (currentElements[childCount].children[i].type) {
						case 'none':
							noneFound = true;
							break;
						case 'full':
							fullFound = true;
							break;
						case 'partial':
							partialFound = true;
							break;
						}
					}

					if (partialFound || (fullFound && noneFound)) {
						// found at least one 'partial' DOM object in the children, or both 'full' and 'none', so this element is also 'partial' contained
						currentElements[childCount].type = 'partial';
					} else if (fullFound && !partialFound && !noneFound) {
						// only found 'full' contained children, so this element is also 'full' contained
						currentElements[childCount].type = 'full';
					}
				}

				childCount++;
			});

			// extra check for collapsed selections at the end of the current element
			if (this.isCollapsed() && currentObject === this.startContainer && this.startOffset == currentObject.childNodes.length) {
				currentElements[childCount] = new GENTICS.Utils.RangeTree();
				currentElements[childCount].type = 'collapsed';
				currentElements[childCount].domobj = undefined;
			}

			return currentElements;
		},

		/**
		 * Find certain the first occurrence of some markup within the parents of either the start or the end of this range.
		 * The markup can be identified by means of a given comparator function. The function will be passed every parent (up to the eventually given limit object, which itself is not considered) to the comparator function as this.
		 * When the comparator function returns boolean true, the markup found and finally returned from this function as dom object.<br/>
		 * Example for finding an anchor tag at the start of the range up to the active editable object:<br/>
		 * <pre>
		 * range.findMarkup(
		 *   function() {
		 *     return this.nodeName.toLowerCase() == 'a';
		 *   },
		 *   jQuery(Aloha.activeEditable.obj)
		 * );
		 * </pre>
		 * @param {function} comparator comparator function to find certain markup
		 * @param {jQuery} limit limit objects for limit the parents taken into consideration
		 * @param {boolean} atEnd true for searching at the end of the range, false for the start (default: false)
		 * @return {DOMObject} the found dom object or false if nothing found.
		 * @method
		 */
		findMarkup: function (comparator, limit, atEnd) {
			var container = atEnd ? this.endContainer : this.startContainer,
				parents,
				i,
				len;
			limit = limit ? limit[0] : null;
			if (!container) {
				return;
			}
			parents = selfAndParentsUntil(container, limit);
			for (i = 0, len = parents.length; i < len; i++) {
				if (comparator.apply(parents[i])) {
					return parents[i];
				}
			}
			return false;
		},

		/**
		 * Find all markups by Tag name
		 *
		 * @param {String} tagName Tag name
		 * @param {Range Object} range Selection Range
		 * @returns {DOMObject[]} All DOM objects matching the tag Name
		 */
		findAllMarkupByTagName: function (tagName, range) {
			var rangeTree = range.getRangeTree(),
				objs = [];
			tagName = tagName.toUpperCase();

			findTagInRangeTreeRecursion(rangeTree, tagName, objs);
			return objs;
		},


		/**
		 * Get the text enclosed by this range
		 * @return {String} the text of the range
		 * @method
		 */
		getText: function () {
			if (this.isCollapsed()) {
				return '';
			}
			return this.recursiveGetText(this.getRangeTree());
		},

		recursiveGetText: function (tree) {
			if (!tree) {
				return '';
			}
			var that = this,
			    text = '';
			jQuery.each(tree, function () {
				if (this.type == 'full') {
					// fully selected element/text node
					text += jQuery(this.domobj).text();
				} else if (this.type == 'partial' && this.domobj.nodeType === 3) {
					// partially selected text node
					text += jQuery(this.domobj).text().substring(this.startOffset, this.endOffset);
				} else if (this.type == 'partial' && this.domobj.nodeType === 1 && this.children) {
					// partially selected element node
					text += that.recursiveGetText(this.children);
				}
			});
			return text;
		}
	});

	/**
	 * @namespace GENTICS.Utils
	 * @class RangeTree
	 * Class definition of a RangeTree, which gives a tree view of the DOM objects included in this range
	 * Structure:
	 * <pre>
	 * +
	 * |-domobj: <reference to the DOM Object> (NOT jQuery)
	 * |-type: defines if this node is marked by user [none|partial|full|collapsed]
	 * |-children: recursive structure like this
	 * </pre>
	 */
	GENTICS.Utils.RangeTree = Class.extend({
		/**
		 * DOMObject, if the type is one of [none|partial|full], undefined if the type is [collapsed]
		 * @property domobj
		 * @type {DOMObject}
		 */
		domobj: {},

		/**
		 * type of the participation of the dom object in the range. Is one of:
		 * <pre>
		 * - none the DOMObject is outside of the range
		 * - partial the DOMObject partially in the range
		 * - full the DOMObject is completely in the range
		 * - collapsed the current RangeTree element marks the position of a collapsed range between DOM nodes
		 * </pre>
		 * @property type
		 * @type {String}
		 */
		type: null,

		/**
		 * Array of RangeTree objects which reflect the status of the child elements of the current DOMObject
		 * @property children
		 * @type {Array}
		 */
		children: []
	});

	return GENTICS.Utils.RangeObject;
});

/*!
 * Aloha Editor
 * Author & Copyright (c) 2012 Gentics Software GmbH
 * aloha-sales@gentics.com
 * Licensed under the terms of http://www.aloha-editor.com/license.html
 *
 * @overview Provides methods to broker publish/subscribe facilities.
 */
define('PubSub', [], function () {
	

	/**
	 * A hash of channel names mapped to an array of ids of subscriptions that
	 * are listening on that channel.
	 *
	 * @type {Object<String, Array.<Number>>}
	 */
	var channels = {};

	/**
	 * A hash of subscription tuples (channel, callback), mapped against unique
	 * ids assigned to each subscription.
	 * As subscriptions are removed from this object via `unsub()' this object
	 * will become a sparse array.
	 *
	 * @type {Object<Number, Object>}
	 */
	var subscriptions = {};

	/**
	 * The last used subscription id.  This values is only used and modified in
	 * `sub().'
	 *
	 * @type {number}
	 */
	var sid = 0;

	/**
	 * Returns the channel to which a subscription matching the given sid is
	 * listening on.
	 *
	 * @param {Number} sid Id of subscription.
	 * @return {Array.<Object>} sid Id of subscription.
	 */
	function getSubscriptionChannel(sid) {
		return subscriptions[sid] && channels[subscriptions[sid].channel];
	}

	/**
	 * Publishes a message `message' on the given channel.
	 * All callbacks that have sub()scribed to listen on this channel will be
	 * invoked and receive `message' as their only argument.
	 *
	 * @private
	 * @param {String} channel Name of channel to publish the message on.
	 * @param {*} message Variable to pass to all callbacks listening on the
	 *                    given channel.
	 * @return {Number} The number of subscribed callbacks that were invoked.
	 */
	function pub(channel, message) {
		if (!channels[channel]) {
			return 0;
		}

		if (!message) {
			message = {};
		} else if (typeof message !== 'object') {
			message = {
				data: message
			};
		}

		message.channel = channel;

		// Clone a immutable snapshot of the subscription ids that we can
		// safetly iterate over.
		var sids = channels[channel].slice();

		// NB: It is necessary to read the size of the `sids' array on each
		// iteration, in case the size changes (via unsubscription) between
		// iterations.
		var i;
		for (i = 0; i < sids.length; ++i) {
			subscriptions[sids[i]].callback(message);
		}

		return i;
	}

	var PubSub = {

		/**
		 * Subscribes a callback function to a channel.  Whenever this channel
		 * publishes, this function will be invoked.  The return value is an id
		 * which identifies this subscription (a channel, and callback tuple).
		 * This id can be used to unsubscribe this subscription from the given
		 * channel.
		 *
		 * @param {String} channel Name of channel to listen on.
		 * @param {Function(Object)} callback Function to be invoked when
		 *                                    messages are published on the
		 *                                    given channel.
		 * @return {Number} Positive integer representing the sid of this
		 *                  subscription, that can be used with unsub() if
		 *                  subscription succeeds.  Otherwise the return value
		 *                  is -1;
		 */
		sub: function (channel, callback) {
			if (typeof callback !== 'function') {
				return -1;
			}

			var subscriptionIds = channels[channel];

			if (!subscriptionIds) {
				subscriptionIds = channels[channel] = [];
			}

			subscriptionIds.push(++sid);
			subscriptions[sid] = {
				channel  : channel,
				callback : callback
			};

			return sid;
		},

		/**
		 * Unsubscribes callback using an sid which was returned by sub() when
		 * the callback was subscribed.  Returns true if a subscription for
		 * this sid was found and removed, otherwise returns false.
		 *
		 * @param {Number} sid Id of subscription.
		 * @return {Boolean} True if a a subscription matching this sid was
		 *                   removed.
		 */
		unsub: function (sid) {
			if (-1 === sid || !subscriptions[sid]) {
				return false;
			}

			var subscriptionIds = getSubscriptionChannel(sid);

			// assert(typeof subscriptionIds === 'array')

			delete subscriptions[sid];
			var j = subscriptionIds.length;

			while (j) {
				if (subscriptionIds[--j] === sid) {
					subscriptionIds.splice(j, 1);
					return true;
				}
			}

			return false;
		},

		/**
		 * Publishes a message `message' on all channels that can be derived
		 * from the given channel name.
		 *
		 * @param {String} channel Name of channel to publish the message on.
		 * @param {*} message Variable to pass to all callbacks listening on
		 *                    the given channel.
		 * @return {Number} The number of subscribed callbacks that were
		 *                  invoked.
		 */
		pub: function (channel, message) {
			var segments = channel.split('.');
			var i;
			var len = segments.length;
			var channelName = '';
			var tally = 0;

			for (i = 0; i < len; ++i) {
				channelName += (0 === i ? '' : '.') + segments[i];
				tally += pub(channelName, message);
			}

			return tally;
		}

	};

	return PubSub;
});

/* functions.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/functions',[], function () {
	

	/**
	 * The identity function returns its single argument.
	 * Useful for composition when some default behaviour is needed.
	 */
	function identity(arg) {
		return arg;
	}

	function noop() {
	}

	function returnTrue() {
		return true;
	}

	function returnFalse() {
		return false;
	}

	function complement(fn) {
		return function () {
			return !fn.apply(this, arguments);
		};
	}

	return {
		identity: identity,
		noop: noop,
		returnTrue: returnTrue,
		returnFalse: returnFalse,
		complement: complement
	};
});

/* maps.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/maps',[], function () {
	

	/**
	 * Checks whether the given object has no own or inherited properties.
	 *
	 * @param {!Object} obj The object to check.
	 * @return {boolean} True if the object is empty. eg: isEmpty({}) == true
	 */
	function isEmpty(obj) {
		var name;
		for (name in obj) {
			if (obj.hasOwnProperty(name)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Fill the given map with the given keys mapped to the given value.
	 *
	 * @param map
	 *        The given map will have one entry added for each given key.
	 * @param keys
	 *        An array of string keys. Javascript maps can only
	 *        contain string keys, so these must be strings or
	 *        or they will be cast to string.
	 * @param value
	 *        A single value that each given key will map to.
	 * @return
	 *        The given map.
	 */
	function fillKeys(map, keys, value) {
		var i = keys.length;
		while (i--) {
			map[keys[i]] = value;
		}
		return map;
	}

	/**
	 * Fill the given map with entries from the given tuples.
	 *
	 * @param map
	 *        The given map will have one entry added for each item in
	 *        the given array.
	 * @param tuples
	 *        An array of [key, value] tuples. Javascript maps can only
	 *        contain string keys, so the keys must be strings or
	 *        or they will be cast to string.
	 * @return
	 *        The given map.
	 */
	function fillTuples(map, tuples) {
		var i = tuples.length,
			tuple;
		while (i--) {
			tuple = tuples[i];
			map[tuple[0]] = tuple[1];
		}
		return map;
	}

	/**
	 * Returns an array of the map's keys.
	 */
	function keys(map) {
		var ks = [],
			k;
		for (k in map) {
			if (map.hasOwnProperty(k)) {
				ks.push(k);
			}
		}
		return ks;
	}

	/**
	 * For each mapping, call cb(value, key, map).
	 *
	 * Emulates ECMAScript edition 5 Array.forEach.
	 *
	 * Contrary to "for (key in map)" iterates only over the
	 * "hasOwnProperty" properties of the map, which is usually what you
	 * want.
	 */
	function forEach(map, cb) {
		var key;
		for (key in map) {
			if (map.hasOwnProperty(key)) {
				cb(map[key], key, map);
			}
		}
	}

	return {
		isEmpty: isEmpty,
		fillTuples: fillTuples,
		fillKeys: fillKeys,
		keys: keys,
		forEach: forEach
	};
});

/* arrays.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/arrays',['util/functions'], function (Fn) {
	

	/**
	 * Implements unique() using the browser's sort().
	 *
	 * @param a
	 *        The array to sort and strip of duplicate values.
	 *        Warning: this array will be modified in-place.
	 * @param compFn
	 *        A custom comparison function that accepts two values a and
	 *        b from the given array and returns -1, 0, 1 depending on
	 *        whether a < b, a == b, a > b respectively.
	 *
	 *        If no compFn is provided, the algorithm will use the
	 *        browsers default sort behaviour and loose comparison to
	 *        detect duplicates.
	 * @return
	 *        The given array.
	 */
	function sortUnique(a, compFn) {
		var i;
		if (compFn) {
			a.sort(compFn);
			for (i = 1; i < a.length; i++) {
				if (0 === compFn(a[i], a[i - 1])) {
					a.splice(i--, 1);
				}
			}
		} else {
			a.sort();
			for (i = 1; i < a.length; i++) {
				// Use loosely typed comparsion if no compFn is given
				// to avoid sortUnique([6, "6", 6]) => [6, "6", 6]
				if (a[i] == a[i - 1]) {
					a.splice(i--, 1);
				}
			}
		}
		return a;
	}

	/**
	 * Shallow comparison of two arrays.
	 *
	 * @param a, b
	 *        The arrays to compare.
	 * @param equalFn
	 *        A custom comparison function that accepts two values a and
	 *        b from the given arrays and returns true or false for
	 *        equal and not equal respectively.
	 *
	 *        If no equalFn is provided, the algorithm will use the strict
	 *        equals operator.
	 * @return
	 *        True if all items in a and b are equal, false if not.
	 */
	function equal(a, b, equalFn) {
		var i,
			len = a.length;
		if (len !== b.length) {
			return false;
		}
		if (equalFn) {
			for (i = 0; i < len; i++) {
				if (!equalFn(a[i], b[i])) {
					return false;
				}
			}
		} else {
			for (i = 0; i < len; i++) {
				if (a[i] !== b[i]) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Emulates ECMAScript edition 5 Arrays.map
	 * See https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map
	 * And http://es5.github.com/#x15.4.4.19
	 * It's not exactly according to standard, but it does what one expects.
	 */
	function map(a, fn) {
		var i, len, result = [];
		for (i = 0, len = a.length; i < len; i++) {
			result.push(fn(a[i]));
		}
		return result;
	}

	function mapNative(a, fn) {
		// Call map directly on the object instead of going through
		// Array.prototype.map. This avoids the problem that we may get
		// passed an array-like object (NodeList) which may cause an
		// error if the implementation of Array.prototype.map can only
		// deal with arrays (Array.prototype.map may be native or
		// provided by a javscript framework).
		return a.map(fn);
	}

	/**
	 * Returns a new array that contains all values in the given a for
	 * which pred returns true.
	 */
	function filter(a, pred) {
		var i,
		    len,
		    value,
		    result = [];
		for (i = 0, len = a.length; i < len; i++) {
			value = a[i];
			if (pred(value)) {
				result.push(value);
			}
		}
		return result;
	}

	/**
	 * Finds a value in the given array.
	 * Strict comparison is used to find the value.
	 * Returns the index of the first occurrence of the given value in
	 * the given a, or -1 if a contains no such value.
	 */
	function indexOf(a, value) {
		var i,
		    len;
		for (i = 0, len = a.length; i < len; i++) {
			if (value === a[i]) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * Reduces an array of values to a single value.
	 *
	 * For example:
	 * Arrays.reduce([2, 3, 4], 1, function (a, b) { return a + b; });
	 * returns the result of (((1 + 2) + 3) + 4)
	 *
	 * Emulates ECMAScript edition 5 Array.reduce.
	 *
	 * @param a
	 *        An array of values.
	 * @param init
	 *        An initial value.
	 * @param fn
	 *        A function that takes two values and returns the reduction
	 *        of both.
	 */
	function reduce(a, init, fn) {
		var i,
		    len;
		for (i = 0, len = a.length; i < len; i++) {
			init = fn(init, a[i]);
		}
		return init;
	}

	/**
	 * Returns true if the given xs contains the given x.
	 */
	function contains(xs, x) {
		return -1 !== indexOf(xs, x);
	}

	/**
	 * Applies the given value to the given function unless the value is
	 * null, in which case just returns null.
	 *
	 * This is a utility function to be used with reduce().
	 */
	function applyNotNull(value, fn) {
		return value == null ? null : fn(value);
	}

	/**
	 * For each item in xs, call cb(item, index, xs).
	 *
	 * Emulates ECMAScript edition 5 Array.forEach.
	 */
	function forEach(xs, cb) {
		var i,
		    len;
		for (i = 0, len = xs.length; i < len; i++) {
			cb(xs[i], i, xs);
		}
	}

	/**
	 * Returns true if the given predicate function returns true for at
	 * least one item.
	 *
	 * Emulates ECMAScript edition 5 Array.some.
	 */
	function some(xs, pred) {
		var i,
		    len;
		for (i = 0, len = xs.length; i < len; i++) {
			if (pred(xs[i])) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Returns true if the given predicate function returns true for all
	 * items in xs.
	 *
	 * Emulates ECMAScript edition 5 Array.every.
	 */
	function every(xs, pred) {
		return !some(xs, Fn.complement(pred));
	}

	/**
	 * Returns concatenation of two arrays.
	 * @param {Array} xs
	 * @param {Array} zx
	 * @returns {Array}
	 */
	function concat(xs, zx) {
		return xs.concat(zx);
	}

	/**
	 * Returns all items in xs that are also contained in zs.
	 */
	function intersect(xs, zs) {
		return filter(xs, function (x) {
			return contains(zs, x);
		});
	}

	/**
	 * Returns all items in xs that are not contained in zs.
	 */
	function subtract(xs, zs) {
		return filter(xs, function (x) {
			return !contains(zs, x);
		});
	}

	/**
	 * Returns the last item in xs or null.
	 */
	function last(xs) {
		return xs.length ? xs[xs.length - 1] : null;
	}

	/**
	 * Returns the second item in xs.
	 */
	function second(xs) {
		return xs[1];
	}

	/**
	 * Coerces the given object (NodeList, arguments) to an array.
	 * @param  {*} list
	 * @return {Array}
	 */
	function coerce(list) {
		var i;
		var len;
		var resultArray = [];

		for (i = 0, len = list.length; i < len; i++) {
			resultArray.push(list[i]);
		}

		return resultArray;
	}

	/**
	 * Checks if `array` is empty.
	 * @param {Array.<*>} array
	 * @return {boolean}
	 */
	function isEmpty(array) {
		return array.length === 0;
	}

	return {
		filter: filter,
		indexOf: indexOf,
		reduce: reduce,
		forEach: forEach,
		some: some,
		every: every,
		map: Array.prototype.map ? mapNative : map,
		contains: contains,
		equal: equal,
		applyNotNull: applyNotNull,
		sortUnique: sortUnique,
		intersect: intersect,
		subtract: subtract,
		second: second,
		last: last,
		coerce: coerce,
		isEmpty: isEmpty,
		concat: concat
	};
});

/* strings.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/strings',['jquery'], function ($) {
	

	var spacesRx = /\s+/;

	/**
	 * Splits a string into individual words.
	 *
	 * Words are non-empty sequences of non-space characaters.
	 */
	function words(str) {
		var list = str.split(spacesRx);
		// "  x  ".split(/\s+/) => ["", "x", ""] (Chrome)
		// "".split(/\s+/) => [""] (Chrome)
		if (list.length && list[0] === "") {
			list.shift();
		}
		if (list.length && list[list.length - 1] === "") {
			list.pop();
		}
		return list;
	}

	/**
	 * Converst a dashes form into camel cased form.
	 *
	 * For example 'data-my-attr' becomes 'dataMyAttr'.
	 *
	 * @param {string} s
	 *        Should be all lowercase and should not begin with a dash
	 */
	function dashesToCamelCase(s) {
		return s.replace(/[\-]([a-z])/gi, function (all, upper) {
			return upper.toUpperCase();
		});
	}

	/**
	 * Converts a camel cased form into dashes form.
	 *
	 * For example
	 * 'dataMyAttr' becomes 'data-my-attr',
	 * 'dataAB'     becomes 'data-a-b'.
	 *
	 * @param {string} s
	 *        Should begin with a lowercase letter and should not contain dashes.
	 */
	function camelCaseToDashes(s) {
		return s.replace(/[A-Z]/g, function (match) {
			return '-' + match.toLowerCase();
		});
	}

	/**
	 * Split str along pattern, including matches in the result.
	 *
	 * Necssary because, although "xzx".split(/(z)/) results in
	 * ["x", "z", "x"] on most modern browsers, it results in
	 * ["x", "x"] on IE.
	 *
	 * @param pattern must include the g flag, otherwise will result in
	 * an endless loop.
	 */
	function splitIncl(str, pattern) {
		var result = [];
		var lastIndex = 0;
		var match;
		while (null != (match = pattern.exec(str))) {
			if (lastIndex < match.index) {
				result.push(str.substring(lastIndex, match.index));
				lastIndex = match.index;
			}
			lastIndex += match[0].length;
			result.push(match[0]);
		}
		if (lastIndex < str.length) {
			result.push(str.substring(lastIndex, str.length));
		}
		return result;
	}

	/**
	 * Returns true for the empty string, null and undefined.
	 */
	function empty(str) {
		return "" === str || null == str;
	}

	/**
	 * Parse a boolean value from a string or any other type
	 * this function returns true when the passed value is either
	 * 'true', 'TRUE', '1', 1 (number) or true (boolean)
	 *
	 * @param  mixed value the value to parse
	 * @return boolean     true if the value is considerd as "true"
	 */
	function parseBoolean(value) {
		if (value === true || value === 1) {
			return true;
		} else if (typeof value === 'string' || value instanceof String) {
			value = value.toLowerCase();
			return value === 'true' || value === '1';
		}
		return false;
	}

	return {
		words: words,
		dashesToCamelCase: dashesToCamelCase,
		camelCaseToDashes: camelCaseToDashes,
		splitIncl: splitIncl,
		empty: empty,
		parseBoolean: parseBoolean
	};
});

/* ephemera.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2014 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/browser',['aloha/core'], function (Aloha) {
	
	return {
		mozilla : Aloha.browser.mozilla,
		ie8     : Aloha.browser.msie && parseInt(Aloha.browser.version, 10) < 9,
		ie7     : Aloha.browser.msie && parseInt(Aloha.browser.version, 10) < 8,
		ie      : Aloha.browser.msie
	};
});

/* dom2.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/dom2',[
	'aloha/core',
	'jquery',
	'util/functions',
	'util/maps',
	'util/arrays',
	'util/strings',
	'util/browser',
	'util/dom',
	'util/range'
], function (
	Aloha,
	$,
	Fn,
	Maps,
	Arrays,
	Strings,
	Browser,
	Dom1,
	RangeObject
) {
	

	var spacesRx = /\s+/;
	var attrRegex = /\s([^\/<>\s=]+)(?:=(?:"[^"]*"|'[^']*'|[^>\/\s]+))?/g;

	/**
	 * Like insertBefore, inserts firstChild into parent before
	 * refChild, except also inserts all the following siblings of
	 * firstChild.
	 */
	function moveNextAll(parent, firstChild, refChild) {
		while (firstChild) {
			var nextChild = firstChild.nextSibling;
			parent.insertBefore(firstChild, refChild);
			firstChild = nextChild;
		}
	}

	/**
	 * Used to serialize outerHTML of DOM elements in older (pre-HTML5) Gecko,
	 * Safari, and Opera browsers.
	 *
	 * Beware that XMLSerializer generates an XHTML string (<div class="team" />
	 * instead of <div class="team"></div>).  It is noted here:
	 * http://stackoverflow.com/questions/1700870/how-do-i-do-outerhtml-in-firefox
	 * that some browsers (like older versions of Firefox) have problems with
	 * XMLSerializer, and an alternative, albeit more expensive option, is
	 * described.
	 *
	 * @type {XMLSerializer|null}
	 */
	var Serializer = window.XMLSerializer && new window.XMLSerializer();

	/**
	 * Gets the serialized HTML that describes the given DOM element and its
	 * innerHTML.
	 *
	 * Polyfill for older versions of Gecko, Safari, and Opera browsers.
	 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=92264 for background.
	 *
	 * @param {HTMLElement} node DOM Element.
	 * @return {String}
	 */
	function outerHtml(node) {
		var html = node.outerHTML;
		if (typeof html !== 'undefined') {
			return html;
		}
		try {
			return Serializer ? Serializer.serializeToString(node) : node.xml;
		} catch (e) {
			return node.xml;
		}
	}

	/**
	 * Retrieves the names of all attributes from the given elmenet.
	 *
	 * Correctly handles the case that IE7 and IE8 have approx 70-90
	 * default attributes on each and every element.
	 *
	 * This implementation does not iterate over the elem.attributes
	 * property since that is much slower on IE7 (even when
	 * checking the attrNode.specified property). Instead it parses the
	 * HTML of the element. For elements with few attributes the
	 * performance on IE7 is improved by an order of magnitued.
	 *
	 * On IE7, when you clone a <button disabled="disabled"/> or an
	 * <input checked="checked"/> element the boolean properties will
	 * not be set on the cloned node. We choose the speed optimization
	 * over correctness in this case. The dom-to-xhtml plugin has a
	 * workaround for this case.
	 */
	function attrNames(elem) {
		var names = [];
		var html = outerHtml(elem.cloneNode(false));
		var match;
		while (null != (match = attrRegex.exec(html))) {
			names.push(match[1]);
		}
		return names;
	}

	/**
	 * Gets the attributes of the given element.
	 *
	 * See attrNames() for an edge case on IE7.
	 *
	 * @param elem
	 *        An element to get the attributes for.
	 * @return
	 *        An array containing [name, value] tuples for each attribute.
	 *        Attribute values will always be strings, but possibly empty strings.
	 */
	function attrs(elem) {
		var as = [];
		var visited = {};
		var names = attrNames(elem);
		var i;
		var len;
		for (i = 0, len = names.length; i < len; i++) {
			var name = names[i];
			var value = $.attr(elem, name);
			//IE9 Fix, "lang" value not in attributes
			if (name === "lang") {
				if (elem.lang) {
					value = elem.lang;
				}
			}
			if (null == value) {
				value = "";
			} else {
				value = value.toString();
			}
			//filter duplicates. IE 10 doesn't take care of duplicates
			if (!visited.hasOwnProperty(name)) {
				as.push([name, value]);
				visited[name] = true;
			}
		}
		return as;
	}

	/**
	 * Like indexByClass() but operates on a list of elements instead.
	 * The given list may be a NodeList, HTMLCollection, or an array.
	 */
	function indexByClassHaveList(elems, classMap) {
		var index = {},
		    indexed,
		    classes,
		    elem,
		    cls,
		    len,
		    i,
		    j;
		for (i = 0, len = elems.length; i < len; i++) {
			elem = elems[i];
			if (elem.className) {
				classes = Strings.words(elem.className);
				for (j = 0; j < classes.length; j++) {
					cls = classes[j];
					if (classMap[cls]) {
						indexed = index[cls];
						if (indexed) {
							indexed.push(elem);
						} else {
							index[cls] = [elem];
						}
					}
				}
			}
		}
		return index;
	}

	/**
	 * Indexes descendant elements based on the individual classes in
	 * the class attribute.
	 *
	 * Based on these observations;
	 * 
	 * * $('.class1, .class2') takes twice as long as $('.class1') on IE7.
	 *
	 * * $('.class1, .class2') is fast on IE8 (approx the same as
	 *   $('.class'), no matter how many classes), but if the individual
	 *   elements in the result set should be handled differently, the
	 *   subsequent hasClass('.class1') and hasClass('.class2') calls
	 *   slow things down again.
	 *
	 * * DOM traversal with elem.firstChild elem.nextSibling is very
	 *   slow on IE7 compared to just iterating over
	 *   root.getElementsByTagName('*').
	 *
	 * * $('name.class') is much faster than just $('.class'), but as
	 *   soon as you need a single class in classMap that may be present
	 *   on any element, that optimization doesn't gain anything since
	 *   then you have to examine every element.
	 *
	 * This function will always take approx. the same amount of time
	 * (on IE7 approx. equivalent to a single call to $('.class')) no
	 * matter how many entries there are in classMap to index.
	 *
	 * This function only makes sense for multiple entries in
	 * classMap. For a single class lookup, $('.class') or
	 * $('name.class') is fine (even better in the latter case).
	 *
	 * @param root
	 *        The root element to search for elements to index
	 *        (will not be included in search).
	 * @param classMap
	 *        A map from class name to boolean true.
	 * @return
	 *        A map from class name to an array of elements with that class.
	 *        Every entry in classMap for which elements have been found
	 *        will have a corresponding entry in the returned
	 *        map. Entries for which no elements have been found, may or
	 *        may not have an entry in the returned map.
	 */
	function indexByClass(root, classMap) {
		var elems;
		if (Browser.ie7) {
			elems = root.getElementsByTagName('*');
		} else {
			// Optimize for browsers that support querySelectorAll/getElementsByClassName.
			// On IE8 for example, if there is a relatively high
			// elems/resultSet ratio, performance can improve by a factor of 2.
			elems = $(root).find('.' + Maps.keys(classMap).join(',.'));
		}
		return indexByClassHaveList(elems, classMap);
	}

	/**
	 * Indexes descendant elements based on elem.nodeName.
	 *
	 * Based on these observations:
	 *
	 * * On IE8, for moderate values of names.length, individual calls to
	 *   getElementsByTagName is just as fast as $root.find('name, name,
	 *   name, name').
	 *
	 * * On IE7, $root.find('name, name, name, name') is extemely slow
	 *   (can be an order of magnitude slower than individual calls to
	 *    getElementsByTagName, why is that?).
	 *
	 * * Although getElementsByTagName is very fast even on IE7, when
	 *   names.length > 7 an alternative implementation that iterates
	 *   over all tags and checks names from a hashmap (similar to how
	 *   indexByClass does it) may become interesting, but
	 *   names.length > 7 is unlikely.
	 *
	 * This function only makes sense if the given names array has many
	 * entries. For only one or two different names, calling $('name')
	 * or context.getElementsByTagName(name) directly is fine (but
	 * beware of $('name, name, ...') as explained above).
	 *
	 * The signature of this function differs from indexByClass by not
	 * taking a map but instead an array of names.
	 *
	 * @param root
	 *        The root element to search for elements to index
	 *        (will not be included in search).
	 * @param names
	 *        An array of element names to look for.
	 *        Names must be in all-uppercase (the same as elem.nodeName).
	 * @return
	 *        A map from element name to an array of elements with that name.
	 *        Names will be all-uppercase.
	 *        Arrays will be proper arrays, not NodeLists.
	 *        Every entry in classMap for which elements have been found
	 *        will have a corresponding entry in the returned
	 *        map. Entries for which no elements have been found, may or
	 *        may not have an entry in the returned map.
	 */
	function indexByName(root, names) {
		var i,
		    index = {},
		    len;
		for (i = 0, len = names.length; i < len; i++) {
			var name = names[i];
			index[name] = $.makeArray(root.getElementsByTagName(name));
		}
		return index;
	}

	function nodeIndex(node) {
		var ret = 0;
		while (node.previousSibling) {
			ret++;
			node = node.previousSibling;
		}
		return ret;
	}

	/**
	 * Can't use elem.childNodes.length because
	 * http://www.quirksmode.org/dom/w3c_core.html
	 * "IE up to 8 does not count empty text nodes."
	 */
	function numChildren(elem) {
		var count = 0;
		var child = elem.firstChild;
		while (child) {
			count += 1;
			child = child.nextSibling;
		}
		return count;
	}

	function nodeLength(node) {
		if (1 === node.nodeType) {
			return numChildren(node);
		}
		if (3 === node.nodeType) {
			return node.length;
		}
		return 0;
	}

	function isAtEnd(node, offset) {
		return (1 === node.nodeType
				&& offset >= numChildren(node))
			|| (3 === node.nodeType
				&& offset === node.length
				&& !node.nextSibling);
	}

	/**
	 * @param node if a text node, should have a parent node.
	 */
	function nodeAtOffset(node, offset) {
		if (1 === node.nodeType && offset < numChildren(node)) {
			node = node.childNodes[offset];
		} else if (3 === node.nodeType && offset === node.length) {
			node = node.nextSibling || node.parentNode;
		}
		return node;
	}

	function removeShallow(node) {
		var parent = node.parentNode;
		moveNextAll(parent, node.firstChild, node);
		parent.removeChild(node);
	}

	/**
	 * Removes `node`.
	 * @param {Node} node
	 */
	function remove(node) {
		var parent = node.parentNode;
		if (parent) {
			parent.removeChild(node);
		}
	}

	function wrap(node, wrapper) {
		node.parentNode.replaceChild(wrapper, node);
		wrapper.appendChild(node);
	}

	function insert(node, ref, atEnd) {
		if (atEnd) {
			ref.appendChild(node);
		} else {
			ref.parentNode.insertBefore(node, ref);
		}
	}

	function Cursor(node, atEnd) {
		this.node = node;
		this.atEnd = atEnd;
	}

	/**
	 * A cursor has the added utility over other iteration methods of
	 * iterating over the end position of an element. The start and end
	 * positions of an element are immediately before the element and
	 * immediately after the last child respectively. All node positions
	 * except end positions can be identified just by a node. To
	 * distinguish between element start and end positions, the
	 * additional atEnd boolean is necessary.
	 */
	function cursor(node, atEnd) {
		return new Cursor(node, atEnd);
	}

	Cursor.prototype.next = function () {
		var node = this.node;
		var next;
		if (this.atEnd || 1 !== node.nodeType) {
			next = node.nextSibling;
			if (next) {
				this.atEnd = false;
			} else {
				next = node.parentNode;
				if (!next) {
					return false;
				}
				this.atEnd = true;
			}
			this.node = next;
		} else {
			next = node.firstChild;
			if (next) {
				this.node = next;
			} else {
				this.atEnd = true;
			}
		}
		return true;
	};

	Cursor.prototype.prev = function () {
		var node = this.node;
		var prev;
		if (this.atEnd) {
			prev = node.lastChild;
			if (prev) {
				this.node = prev;
			} else {
				this.atEnd = false;
			}
		} else {
			prev = node.previousSibling;
			if (prev) {
				if (1 === node.nodeType) {
					this.atEnd = true;
				}
			} else {
				prev = node.parentNode;
				if (!prev) {
					return false;
				}
			}
			this.node = prev;
		}
		return true;
	};

	Cursor.prototype.equals = function (cursor) {
		return cursor.node === this.node && cursor.atEnd === this.atEnd;
	};

	Cursor.prototype.clone = function (cursor) {
		return cursor(cursor.node, cursor.atEnd);
	};

	Cursor.prototype.insert = function (node) {
		return insert(node, this.node, this.atEnd);
	};

	/**
	 * @param offset if node is a text node, the offset will be ignored.
	 * @param node if a text node, should have a parent node.
	 */
	function cursorFromBoundaryPoint(node, offset) {
		return cursor(nodeAtOffset(node, offset), isAtEnd(node, offset));
	}

	function parentsUntil(node, pred) {
		var parents = [];
		var parent = node.parentNode;
		while (parent && !pred(parent)) {
			parents.push(parent);
			parent = parent.parentNode;
		}
		return parents;
	}

	function parentsUntilIncl(node, pred) {
		var parents = parentsUntil(node, pred);
		var topmost = parents.length ? parents[parents.length - 1] : node;
		if (topmost.parentNode) {
			parents.push(topmost.parentNode);
		}
		return parents;
	}

	function childAndParentsUntil(node, pred) {
		if (pred(node)) {
			return [];
		}
		var parents = parentsUntil(node, pred);
		parents.unshift(node);
		return parents;
	}

	function childAndParentsUntilIncl(node, pred) {
		if (pred(node)) {
			return [node];
		}
		var parents = parentsUntilIncl(node, pred);
		parents.unshift(node);
		return parents;
	}

	function childAndParentsUntilNode(node, untilNode) {
		return childAndParentsUntil(node, function (nextNode) {
			return nextNode === untilNode;
		});
	}

	function childAndParentsUntilInclNode(node, untilInclNode) {
		return childAndParentsUntilIncl(node, function (nextNode) {
			return nextNode === untilInclNode;
		});
	}

	function next(node, until, arg) {
		while (node && !until(node, arg)) {
			node = node.nextSibling;
		}
		return node;
	}

	function parent(node, until, arg) {
		while (node && !until(node, arg)) {
			node = node.parentNode;
		}
		return node;
	}

	function isTextNode(node) {
		return 3 === node.nodeType;
	}

	function splitTextNode(node, offset) {
		// Because node.splitText() is buggy on IE, split it manually.
		// http://www.quirksmode.org/dom/w3c_core.html
		var parent = node.parentNode;
		var text = node.nodeValue;
		if (0 === offset || offset >= text.length) {
			return node;
		}
		var before = document.createTextNode(text.substring(0, offset));
		var after = document.createTextNode(text.substring(offset, text.length));
		parent.insertBefore(before, node);
		parent.insertBefore(after, node);
		parent.removeChild(node);
		return before;
	}

	function adjustRangeAfterSplit(range, container, offset, setProp, splitNode, newNodeBeforeSplit) {
		if (container !== splitNode) {
			return;
		}
		var newNodeLength = newNodeBeforeSplit.length;
		if (offset === 0) {
			container = newNodeBeforeSplit.parentNode;
			offset = nodeIndex(newNodeBeforeSplit);
		} else if (offset < newNodeLength) {
			container = newNodeBeforeSplit;
		} else if (offset === newNodeLength) {
			container = newNodeBeforeSplit.parentNode;
			offset = nodeIndex(newNodeBeforeSplit) + 1;
		} else {// offset > newNodeLength
			var newNodeAfterSplit = newNodeBeforeSplit.nextSibling;
			container = newNodeAfterSplit;
			offset -= newNodeLength;
		}
		range[setProp].call(range, container, offset);
	}

	/**
	 * Splits the given text node at the given offset and, if the given
	 * range happens to have start or end containers equal to the given
	 * text node, adjusts it such that start and end position will point
	 * at the same position in the new text nodes.
	 *
	 * It is guaranteed that an adjusted boundary point will not point
	 * to the end of a text node. Instead, it will point to the next
	 * node. This guarantee often happens to be useful.
	 *
	 * If splitNode is not a text node, does nothing.
	 */
	function splitTextNodeAdjustRange(splitNode, splitOffset, range) {
		if (3 !== splitNode.nodeType) {
			return;
		}
		var sc = range.startContainer;
		var so = range.startOffset;
		var ec = range.endContainer;
		var eo = range.endOffset;
		var newNodeBeforeSplit = splitTextNode(splitNode, splitOffset);
		adjustRangeAfterSplit(range, sc, so, 'setStart', splitNode, newNodeBeforeSplit);
		adjustRangeAfterSplit(range, ec, eo, 'setEnd', splitNode, newNodeBeforeSplit);
	}

	function splitTextContainers(range) {
		var sc = range.startContainer;
		var so = range.startOffset;
		splitTextNodeAdjustRange(sc, so, range);
		// Because the range may have been adjusted.
		var ec = range.endContainer;
		var eo = range.endOffset;
		splitTextNodeAdjustRange(ec, eo, range);
	}

	function walkUntil(node, fn, until, arg) {
		while (node && !until(node, arg)) {
			var next = node.nextSibling;
			fn(node, arg);
			node = next;
		}
	}

	function walk(node, fn, arg) {
		walkUntil(node, fn, Fn.returnFalse, arg);
	}

	/**
	 * Depth-first postwalk of the given DOM node.
	 */
	function walkRec(node, fn, arg) {
		if (1 === node.nodeType) {
			walk(node.firstChild, function (node) {
				walkRec(node, fn, arg);
			});
		}
		fn(node, arg);
	}

	function walkUntilNode(node, fn, untilNode, arg) {
		walkUntil(node, fn, function (nextNode) {
			return nextNode === untilNode;
		}, arg);
	}

	function StableRange(range) {
		if (!range) {
			return;
		}
		this.startContainer = range.startContainer;
		this.startOffset = range.startOffset;
		this.endContainer = range.endContainer;
		this.endOffset = range.endOffset;
		this.commonAncestorContainer = range.commonAncestorContainer;
		this.collapsed = range.collapsed;
	}

	StableRange.prototype.update = function () {
		if (!this.startContainer || !this.endContainer) {
			return;
		}
		this.collapsed = (this.startContainer === this.endContainer
						  && this.startOffset === this.endOffset);
		var start = childAndParentsUntil(this.startContainer, Fn.returnFalse);
		var end   = childAndParentsUntil(this.endContainer, Fn.returnFalse);
		this.commonAncestorContainer = Arrays.intersect(start, end)[0];
	};

	StableRange.prototype.setStart = function (sc, so) {
		this.startContainer = sc;
		this.startOffset = so;
		this.update();
	};

	StableRange.prototype.setEnd = function (ec, eo) {
		this.endContainer = ec;
		this.endOffset = eo;
		this.update();
	};

	function setRangeStartFromCursor(range, cursor) {
		if (cursor.atEnd) {
			range.setStart(cursor.node, numChildren(cursor.node));
		} else {
			range.setStart(cursor.node.parentNode, nodeIndex(cursor.node));
		}
	}

	function setRangeEndFromCursor(range, cursor) {
		if (cursor.atEnd) {
			range.setEnd(cursor.node, numChildren(cursor.node));
		} else {
			range.setEnd(cursor.node.parentNode, nodeIndex(cursor.node));
		}
	}

	function setRangeFromRef(range, ref) {
		range.setStart(ref.startContainer, ref.startOffset);
		range.setEnd(ref.endContainer, ref.endOffset);
	}

	/**
	 * A native range is live, which means that modifying the DOM may
	 * mutate the range. Also, using setStart/setEnd may not set the
	 * properties correctly (the browser may perform its own
	 * normalization of boundary points). The behaviour of a native
	 * range is very erratic and should be converted to a stable range
	 * as the first thing in any algorithm.
	 */
	function stableRange(range) {
		return new StableRange(range);
	}

	/**
	 * The dom cursor passed to ignoreLeft and ignoreRight does not
	 * traverse positions inside text nodes. The exact rules for when
	 * text node containers are passed are as follows: If the left
	 * boundary point is inside a text node, trimming will start before
	 * it. If the right boundary point is inside a text node, trimming
	 * will start after it.
	 */
	function trimRange(range, ignoreLeft, ignoreRight) {
		if (range.collapsed) {
			return;
		}
		var start = cursorFromBoundaryPoint(range.startContainer, range.startOffset);
		var end = cursorFromBoundaryPoint(range.endContainer, range.endOffset);
		var setStart = false;
		while (!start.equals(end) && ignoreLeft(start) && start.next()) {
			setStart = true;
		}
		ignoreRight = ignoreRight || ignoreLeft;
		var setEnd = false;
		// Because if the right boundary points is inside a text node,
		// trimming starts after it.
		if (3 === range.endContainer.nodeType
			    && range.endOffset > 0
			    // Because the cursor already normalizes
			    // endOffset == endContainer.length to the node next after it.
			    && range.endOffset < range.endContainer.length
			    && end.next()) {
			if (ignoreRight(end)) {
				end.prev();
			}
		}
		while (!end.equals(start) && ignoreRight(end) && end.prev()) {
			setEnd = true;
		}
		if (setStart) {
			setRangeStartFromCursor(range, start);
		}
		if (setEnd) {
			setRangeEndFromCursor(range, end);
		}
	}

	function trimRangeClosingOpening(range, ignoreLeft, ignoreRight) {
		ignoreRight = ignoreRight || ignoreLeft;
		trimRange(range, function (cursor) {
			return cursor.atEnd || ignoreLeft(cursor.node);
		}, function (cursor) {
			var prev = cursor.atEnd ? cursor.node.lastChild : cursor.node.previousSibling;
			return !prev || ignoreRight(prev);
		});
	}

	function areRangesEq(a, b) {
		return a.startContainer === b.startContainer
			&& a.startOffset    === b.startOffset
			&& a.endContainer   === b.endContainer
			&& a.endOffset      === b.endOffset;
	}

	function insertSelectText(text, range) {
		// Because empty text nodes are generally not nice and even
		// cause problems with IE8 (elem.childNodes).
		if (!text.length) {
			return;
		}
		splitTextNodeAdjustRange(range.startContainer, range.startOffset, range);
		var node = nodeAtOffset(range.startContainer, range.startOffset);
		var atEnd = isAtEnd(range.startContainer, range.startOffset);
		// Because if the node following the insert position is already
		// a text node we can just reuse it.
		if (!atEnd && 3 === node.nodeType) {
			node.insertData(0, text);
			range.setStart(node, 0);
			range.setEnd(node, text.length);
			return;
		}
		// Because if the node preceding the insert position is already
		// a text node we can just reuse it.
		var prev;
		if (!atEnd) {
			prev = node.previousSibling;
		} else {
			prev = node.lastChild;
		}
		if (prev && 3 === prev.nodeType) {
			prev.insertData(prev.length, text);
			range.setStart(prev, prev.length - text.length);
			range.setEnd(prev, prev.length);
			return;
		}
		// Because if we can't reuse any text nodes, we have to insert a
		// new one.
		var textNode = document.createTextNode(text);
		insert(textNode, node, atEnd);
		range.setStart(textNode, 0);
		range.setEnd(textNode, textNode.length);
	}

	function collapseToEnd(range) {
		range.setStart(range.endContainer, range.endOffset);
	}

	function rangeFromRangeObject(alohaRange) {
		var range = Aloha.createRange();
		range.setStart(alohaRange.startContainer, alohaRange.startOffset);
		range.setEnd(alohaRange.endContainer, alohaRange.endOffset);
		return range;
	}

	function extendToWord(range) {
		var rangeObject = new RangeObject(range);
		Dom1.extendToWord(rangeObject);
		setRangeFromRef(range, rangeObject);
	}

	function cloneShallow(node) {
		return node.cloneNode(false);
	}

	/**
	 * Sets a style on the given element by modifying it's style attribute.
	 */
	function setStyle(node, name, value) {
		// Because only the empty string removes a style.
		$(node).css(name, null == value ? '' : value);
	}

	/**
	 * Gets a style from the given element's style attribute.
	 * Note that this is different from the computed/inherited style.
	 */
	function getStyle(node, name) {
		// Because IE7 needs dashesToCamelCase().
		name = Strings.dashesToCamelCase(name);
		return node.nodeType === 1 ? node.style[name] : null;
	}

	/**
	 * Gets the computed/inherited style of the given node.
	 * @param node may be a text node.
	 */
	function getComputedStyle(node, name) {
		if (node.currentStyle) {
			return node.currentStyle[name];
		}
		var doc = node.ownerDocument;
		if (doc.defaultView && doc.defaultView.getComputedStyle) {
			var styles = doc.defaultView.getComputedStyle(node, null);
			if (styles) {
				return styles[name] || styles.getPropertyValue(name);
			}
		}
		return null;
	}

	/**
	 * Given a node, will return node that succeeds it in the document order.
	 *
	 * For example, if this function is called recursively, starting from the
	 * text node "one" in the below DOM tree:
	 *
	 *	"one"
	 *	<b>
	 *		"two"
	 *		<u>
	 *			<i>
	 *				"three"
	 *			</i>
	 *		</u>
	 *		"four"
	 *	</b>
	 *	"five"
	 *
	 * forward() will return nodes in the following order:
	 *
	 * <b>...</b>, "two", <u>...</u>, <i>...</i>, "three", "four", "five"
	 *
	 * @param {DOMObject} node
	 * @return {DOMObject}
	 *         The succeeding node or null if the given node has no previous
	 *         siblings and no parent.
	 */
	function forward(node) {
		if (node.firstChild) {
			return node.firstChild;
		}
		var next = node;
		while (next && !next.nextSibling) {
			next = next.parentNode;
		}
		return next && next.nextSibling;
	}

	/**
	 * Given a node, will return node that preceeds it in the document order.
	 *
	 * For example, if this function is called recursively, starting from the
	 * text node "five" in the below DOM tree:
	 *
	 *	"one"
	 *	<b>
	 *		"two"
	 *		<u>
	 *			<i>
	 *				"three"
	 *			</i>
	 *		</u>
	 *		"four"
	 *	</b>
	 *	"five"
	 *
	 * backward() will return nodes in the following order:
	 *
	 * "four", "three", <i>...</i>, <u>...</u>, "two", <b>...</b>, "one"
	 *
	 * @param {DOMObject} node
	 * @return {DOMObject}
	 *         The preceeding node or null if the given node has no previous
	 *         siblings and no parent.
	 */
	function backward(node) {
		var prev = node.previousSibling;
		while (prev && prev.lastChild) {
			prev = prev.lastChild;
		}
		return prev || node.parentNode;
	}

	/**
	 * Starting from the given node, and moving forwards through the DOM tree,
	 * searches for a node which returns `true` when applied to the predicate
	 * `match()`.
	 *
	 * @param {DOMObject} node
	 * @param {Function(DOMObject):Boolean} match
	 * @param {Function(DOMObject):Boolean} until
	 * @return {DOMObject}
	 */
	function findForward(node, match, until) {
		while (node && !until(node)) {
			if (match(node)) {
				return node;
			}
			node = forward(node);
		}
		return null;
	}

	/**
	 * Starting from the given node, and moving backwards through the DOM tree,
	 * searches for a node which returns `true` when applied to the predicate
	 * `match()`.
	 *
	 * @param {DOMObject} node
	 * @param {Function(DOMObject):Boolean} match
	 * @param {Function(DOMObject):Boolean} until
	 * @return {DOMObject}
	 */
	function findBackward(node, match, until) {
		while (node && !until(node)) {
			if (match(node)) {
				return node;
			}
			node = backward(node);
		}
		return null;
	}

	return {
		backward: backward,
		forward: forward,
		findForward: findForward,
		findBackward: findBackward,
		moveNextAll: moveNextAll,
		attrNames: attrNames,
		attrs: attrs,
		indexByClass: indexByClass,
		indexByName: indexByName,
		indexByClassHaveList: indexByClassHaveList,
		outerHtml: outerHtml,
		removeShallow: removeShallow,
		remove: remove,
		wrap: wrap,
		insert: insert,
		cursor: cursor,
		cursorFromBoundaryPoint: cursorFromBoundaryPoint,
		nodeAtOffset: nodeAtOffset,
		isAtEnd: isAtEnd,
		parentsUntil: parentsUntil,
		parentsUntilIncl: parentsUntilIncl,
		childAndParentsUntil: childAndParentsUntil,
		childAndParentsUntilIncl: childAndParentsUntilIncl,
		childAndParentsUntilNode: childAndParentsUntilNode,
		childAndParentsUntilInclNode: childAndParentsUntilInclNode,
		next: next,
		parent: parent,
		isTextNode: isTextNode,
		nodeIndex: nodeIndex,
		splitTextNode: splitTextNode,
		splitTextContainers: splitTextContainers,
		walk: walk,
		walkRec: walkRec,
		walkUntil: walkUntil,
		walkUntilNode: walkUntilNode,
		stableRange: stableRange,
		trimRange: trimRange,
		trimRangeClosingOpening: trimRangeClosingOpening,
		setRangeFromRef: setRangeFromRef,
		setRangeStartFromCursor: setRangeStartFromCursor,
		setRangeEndFromCursor: setRangeEndFromCursor,
		splitTextNodeAdjustRange: splitTextNodeAdjustRange,
		insertSelectText: insertSelectText,
		areRangesEq: areRangesEq,
		collapseToEnd: collapseToEnd,
		extendToWord: extendToWord,
		rangeFromRangeObject: rangeFromRangeObject,
		cloneShallow: cloneShallow,
		setStyle: setStyle,
		getStyle: getStyle,
		getComputedStyle: getComputedStyle,
		nodeLength: nodeLength
	};
});

/* content-rules.js is part of the Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2014 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 * License http://aloha-editor.org/license.php
 */
define('aloha/content-rules',[
	'PubSub',
	'aloha/core',
	'util/dom2',
	'util/arrays',
	'aloha/jquery'
], function (
	PubSub,
	Aloha,
	Dom,
	Arrays,
	$
) {
	

	/**
	 * White list node names for list elements.
	 * @type {Array.<string>}
	 */
	var LIST_WHITELIST_NODE_NAMES = ['li'];

	/**
	 * White list node names for table elements.
	 * @type {Array.<string>}
	 */
	var TABLE_WHITELIST_NODE_NAMES = ['caption', 'colgroup', 'col', 'thead', 'tbody', 'tfoot', 'td', 'th'];

	/**
	 * Add default rules for some elements.
	 * @param {Object.<string, Array.<string>>}
	 * @return {Object.<string, Array.<string>>}
	 */
	function addDefaultRules(mapRules) {
		var selector;
		for (selector in mapRules) {
			if (mapRules.hasOwnProperty(selector)) {
				if (Arrays.contains(mapRules[selector], 'ol') || Arrays.contains(mapRules[selector], 'ul')) {
					mapRules[selector] = Arrays.concat(mapRules[selector], LIST_WHITELIST_NODE_NAMES);
				}
				if (Arrays.contains(mapRules[selector], 'table')) {
					mapRules[selector] = Arrays.concat(mapRules[selector], TABLE_WHITELIST_NODE_NAMES);
				}
			}
		}
		return mapRules;
	}

	/**
	 * Whitelist rules.
	 *
	 * @private
	 * @type {Object.<string, Array.<string>>}
	 */
	var whitelist = addDefaultRules((
		Aloha.settings &&
		Aloha.settings.contentRules &&
		Aloha.settings.contentRules.whitelist
	) || {});

	/**
	 * Blacklist rules.
	 *
	 * @private
	 * @type {Object.<string, Array.<string>>}
	 */
	var blacklist = (
		Aloha.settings &&
		Aloha.settings.contentRules &&
		Aloha.settings.contentRules.blacklist
	) || {};

	/**
	 * Translation rules.
	 *
	 * @private
	 * @type {Object<string, string>}
	 */
	var translations = (
		Aloha.settings &&
		Aloha.settings.contentRules &&
		Aloha.settings.contentRules.translate
	) || {};

	/**
	 * Retrieves a list of all rules in a specified table that are applicable
	 * the given editable.
	 *
	 * @private
	 * @param {Element}                        editable
	 * @param {Object<string, Array.<string>}} table
	 */
	function getRules(editable, table) {
		var $editable = $(editable);
		var rules = [];
		var selector;
		for (selector in table) {
			if (table.hasOwnProperty(selector) && $editable.is(selector)) {
				rules.push(table[selector]);
			}
		}
		return rules;
	}

	/**
	 * Concatenates the given list of lists into a single set.
	 *
	 * @private
	 * @param  {Array.<Array<string>>} lists
	 * @return {Array.<string>}
	 */
	function setcat(lists) {
		var result = [];
		$.each(lists, function (index, item) {
			result = result.concat(item);
		});
		$.unique(result);
		return result;
	}

	/**
	 * These element's cannot be simply unwrapped because they have dependent
	 * children.
	 *
	 * @private
	 * @see  GROUPED_ELEMENTS
	 * @type {<string, boolean>}
	 */
	var GROUP_CONTAINERS = {
		FIELDSET : true,
		OBJECT   : true,
		FIGURE   : true,
		AUDIO    : true,
		SELECT   : true,
		COLGROUP : true,
		HGROUP   : true,
		TABLE    : true,
		TBODY    : true,
		TR       : true,
		OL       : true,
		UL       : true,
		DL       : true,
		MENU     : true
	};

	/**
	 * These element's cannot be simply unwrapped because their parents only
	 * allow these as their immediate child nodes.
	 *
	 * @private
	 * @see  GROUP_CONTAINERS
	 * @type {<string, Array.<string>}
	 */
	var GROUPED_ELEMENTS = {
		LI    : ['OL', 'UL', 'DL'],
		DT    : ['DL'],
		DD    : ['DL'],
		TBODY : ['TABLE'],
		TR    : ['TABLE', 'TBODY'],
		TH    : ['TABLE', 'TBODY'],
		TD    : ['TR', 'TH']
	};

	/**
	 * Checks whether nodes of the specified nodeName are allowed in the given
	 * editable.
	 *
	 * @param  {Element} editable
	 * @param  {string}  nodeName
	 * @return {boolean}
	 */
	function isAllowed(editable, nodeName) {
		var white = getRules(editable, whitelist);
		// Because if no rules are configured for this editable then permit all
		if (white.length > 0) {
			// Because textnode are always to be permitted by default. They
			// must be explicitly blacklisted if undesired
			if (!Arrays.contains(setcat(['#text'].concat(white)), nodeName.toLowerCase())) {
				return false;
			}
		}
		var black = getRules(editable, blacklist);
		if (black.length > 0) {
			return !Arrays.contains(setcat(black), nodeName.toLowerCase());
		}
		return true;
	}

	/**
	 * Translates nodes from one name to another (eg: i to em) if translation is
	 * configured for the given editable.
	 *
	 * @param  {Element} editable
	 * @param  {string}  nodeName
	 * @return {string}  Translated nodeName
	 */
	function translate(editable, nodeName) {
		var rules = $.extend.apply({}, getRules(editable, translations));
		return rules[nodeName.toLowerCase()] || nodeName;
	}

	/**
	 * Given a string of html content to be inserted in a specified editable
	 * element, will strip away any elements which are disallowed and translate
	 * any according to the Content Rules configuration.
	 *
	 * @param  {string}  content
	 * @param  {Element} editable
	 * @return {string}
	 */
	function applyRules(content, editable) {
		var doc = editable.ownerDocument;
		var container = doc.createElement('div');
		container.innerHTML = content;
		var node = Dom.forward(container);
		while (node) {
			//skip over node if it is a aloha-block, because we want to keep elements inside
			if ($(node).hasClass('aloha-block')) {
				node = node.nextSibling || Dom.forward(node.lastChild || node);
			} else {
				var translation = translate(editable, node.nodeName);
				if (translation !== node.nodeName) {
					var replacement = doc.createElement(translation);
					replacement.innerHTML = node.innerHTML;
					node.parentNode.replaceChild(replacement, node);
					node = replacement;
				}
				if (isAllowed(editable, node.nodeName)) {
					node = Dom.forward(node);
				} else if (GROUP_CONTAINERS[node.nodeName] || GROUPED_ELEMENTS[node.nodeName]) {
					// Because `node` is being entirely removed, we skip over, and
					// do not descend its subtree
					var prev = Dom.backward(node);
					node.parentNode.removeChild(node);
					node = Dom.forward(prev);
				} else {
					var next = Dom.forward(node);
					Dom.removeShallow(node);
					node = next;
				}
			}
		}
		return container.innerHTML;
	}

	return {
		isAllowed  : isAllowed,
		translate  : translate,
		applyRules : applyRules
	};
});

/* html.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/html',[
	'jquery',
	'util/dom',
	'util/maps',
	'util/arrays'
], function (
	jQuery,
	Dom,
	Maps,
	Arrays
) {
	

	var inlineFormattableMap = {
		'A': true,
		'B': true,
		'EM': true,
		'FONT': true,
		'I': true,
		'S': true,
		'SPAN': true,
		'STRIKE': true,
		'STRONG': true,
		'SUB': true,
		'SUP': true,
		'U': true
	};

	// NB: "block-level" is not technically defined for elements that are new in
	// HTML5.
	var BLOCKLEVEL_ELEMENTS = [
		'address',
		'article',    // HTML5
		'aside',      // HTML5
		'audio',      // HTML5
		'blockquote',
		'canvas',     // HTML5
		'dd',
		'div',
		'dl',
		'fieldset',
		'figcaption',
		'figure',
		'footer',
		'form',
		'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
		'header',
		'hgroup',
		'hr',
		'noscript',
		'ol',
		'output',
		'p',
		'pre',
		'section',   // HTML5
		'table',
		'tfoot',
		'ul',
		'video'      // HTML5
	];

	/**
	 * Void elements are elements which are not permitted to contain content.
	 * https://developer.mozilla.org/en-US/docs/Web/HTML/Element
	 *
	 * @type {Object}
	 */
	var VOID_ELEMENTS = [
		'area',
		'base',
		'br',
		'col',
		'command',
		'embed',
		'hr',
		'img',
		'input',
		'keygen',
		'link',
		'meta',
		'param',
		'source',
		'track',
		'wbr'
	];

	/**
	 * Text-level semantic and edit elements to be remove during
	 * copying or pasting.
	 *
	 * See:
	 * http://dev.w3.org/html5/spec/text-level-semantics.html#usage-summary
	 *
	 * Configurable.
	 *
	 * @type {Array.<string>}
	 */
	var TEXT_LEVEL_SEMANTIC_ELEMENTS = [
		'a',
		'abbr',
		'b',
		'bdi',
		'bdo',
		'cite',
		'code',
		'del',
		'dfn',
		'em',
		'i',
		'ins',
		'kbd',
		'mark',
		'q',
		'rp',
		'rt',
		'ruby',
		's',
		'samp',
		'small',
		'strong',
		'sub',
		'sup',
		'time',
		'u',
		'var'
	];

	/**
	 * Unicode zero width space characters:
	 * http://www.unicode.org/Public/UNIDATA/Scripts.txt
	 *
	 * @const
	 * @type {Array.<string>}
	 */
	var ZERO_WIDTH_CHARACTERS = [
		'\\u200B', // ZWSP
		'\\u200C',
		'\\u200D',
		'\\uFEFF'  // ZERO WIDTH NO-BREAK SPACE
	];

	/**
	 * Unicode White_Space characters are those that have the Unicode property
	 * "White_Space" in the Unicode PropList.txt data file.
	 *
	 * http://www.unicode.org/Public/UNIDATA/PropList.txt
	 *
	 * @const
	 * @type {Array.<string>}
	 */
	var WHITE_SPACE_CHARACTERS_UNICODES = [
		'\\u0009',
		'\\u000A',
		'\\u000B',
		'\\u000C',
		'\\u000D',
		'\\u0020',
		'\\u0085',
		'\\u00A0', // NON BREAKING SPACE ("&nbsp;")
		'\\u1680',
		'\\u180E',
		'\\u2000',
		'\\u2001',
		'\\u2002',
		'\\u2003',
		'\\u2004',
		'\\u2005',
		'\\u2006',
		'\\u2007',
		'\\u2008',
		'\\u2009',
		'\\u200A',
		'\\u2028',
		'\\u2029',
		'\\u202F',
		'\\u205F',
		'\\u3000'
	];

	var wspChars = WHITE_SPACE_CHARACTERS_UNICODES.join('');

	/**
	 * Regular expression that checks whether a string consists only of one or
	 * more white space characters.
	 *
	 * @type {RegExp}
	 */
	var WSP_CHARACTERS = new RegExp('^[' + wspChars + ']+$');
	var WSP_CHARACTERS_LEFT = new RegExp('^[' + wspChars + ']+');
	var WSP_CHARACTERS_RIGHT = new RegExp('[' + wspChars + ']+$');

	/**
	 * Regular expression that matches one or more sequences of zero width
	 * characters.
	 *
	 * @type {RegExp}
	 */
	var ZWSP_CHARACTERS = new RegExp('[' + ZERO_WIDTH_CHARACTERS.join('') + ']+');
	var ZWSP_CHARACTERS_LEFT = new RegExp('^[' + ZERO_WIDTH_CHARACTERS.join('') + ']+');
	var ZWSP_CHARACTERS_RIGHT = new RegExp('[' + ZERO_WIDTH_CHARACTERS.join('') + ']+$');

	function isWSPorZWSPText(text) {
		return WSP_CHARACTERS.test(text) || ZWSP_CHARACTERS.test(text);
	}

	function isWSPorZWSPNode(node) {
		return 3 === node.nodeType && isWSPorZWSPText(node.data);
	}

	/**
	 * Checks is `elem` has only White Spaces chilren.
	 * @paran {Element} elem
	 */
	function hasOnlyWhiteSpaceChildren(elem) {
		var children = elem.childNodes;
		var i, len;
		for (i = 0, len = children.length; i < len; i++) {
			if (!isWSPorZWSPNode(children[i])) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Map containing lowercase and uppercase tagnames of block element as keys
	 * mapped against true.
	 *
	 * @type {object<string, boolean>}
	 */
	var blocksTagnameMap = {};
	Maps.fillKeys(blocksTagnameMap, BLOCKLEVEL_ELEMENTS, true);
	Maps.fillKeys(
		blocksTagnameMap,
		Arrays.map(BLOCKLEVEL_ELEMENTS, function (str) {
			return str.toUpperCase();
		}),
		true
	);

	function isBlock(node) {
		return blocksTagnameMap[node.nodeName];
	}

	function isIgnorableWhitespace(node) {
		return 3 === node.nodeType && !node.length;
	}

	function isInlineFormattable(node) {
		return inlineFormattableMap[node.nodeName];
	}

	/**
	 * Checks whether the given element is a block that contains a "propping"
	 * <br> element.
	 *
	 * A propping <br> is one which is inserted into block element to ensure
	 * that the otherwise empty element will be rendered visibly.
	 *
	 * @param {HTMLElement} node
	 * @return {boolean} True if node contains a propping <br>
	 */
	function isProppedBlock(node) {
		if (!blocksTagnameMap[node.nodeName]) {
			return false;
		}
		var found = false;
		var kids = node.children;
		var len = kids.length;
		var i;
		for (i = 0; i < len; i++) {
			if (!found && 'br' === kids[i].nodeName.toLowerCase()) {
				found = true;
			} else if (!isIgnorableWhitespace(kids[i])) {
				return false;
			}
		}
		return found;
	}

	function isEditingHost(node) {
		return 1 === node.nodeType && "true" === node.contentEditable;
	}

	/**
	 * Starting from the given node, and working backwards through the siblings,
	 * find the node that satisfies the given condition.
	 *
	 * @param {HTMLElement} node The node at which to start the search.
	 * @param {function(HTMLElement):boolean} condition A predicate the receives
	 *                                        one of children of `node`.
	 *
	 * @return {HTMLElement} The first node that meets the given condition.
	 */
	function findNodeRight(node, condition) {
		while (node && !condition(node)) {
			node = node.previousSibling;
		}
		return node;
	}

	function findNodeLeft(node, condition) {
		while (node && !condition(node)) {
			node = node.nextSibling;
		}
		return node;
	}

	/**
	 * Checks if the given editable is a valid container for paragraphs.
	 *
	 * @param {Aloha.Editable} editable The editable to be checked
	 *
	 * @return {boolean} False if the editable may not contain paragraphs
	 */
	function allowNestedParagraph(editable) {
		if (editable.obj[0] && Dom.allowsNesting(editable.obj[0], jQuery("<p>")[0])) {
			return true;
		}
		return false;
	}

	/**
	 * Removes a strange characters from at the beginning and end of the string
	 * 
	 * @param {String} str A string to be trimmed
	 * 
	 * @return {String}
	 */
	function trimWhitespaceCharacters(str) {
		return str
			.replace(WSP_CHARACTERS_LEFT, '')
			.replace(WSP_CHARACTERS_RIGHT, '')
			.replace(ZWSP_CHARACTERS_LEFT, '')
			.replace(ZWSP_CHARACTERS_RIGHT, '');
	}

	/**
	 * Checks if `node` is unrendered.
	 * @param {Node} node Node to be checked
	 * @return {boolean} true if `node` is unrendered, false otherwise.
	 */
	function isUnrenderedNode(node) {
		if (3 === node.nodeType && 0 === node.data.length) {
			return true;
		}
		if ((node === node.parentNode.lastChild)
				&& isBlock(node.parentNode)
					&& 'BR' === node.nodeName) {
			return true;
		}
		return isWSPorZWSPNode(node);
	}

	/**
	 * Checks if `node` is rendered.
	 * @param {Node} node Node to be checked
	 * @return {boolean} true if `node` is rendered, false otherwise.
	 */
	function isRenderedNode(node) {
		return !isUnrenderedNode(node);
	}
	return {
		BLOCKLEVEL_ELEMENTS: BLOCKLEVEL_ELEMENTS,
		VOID_ELEMENTS: VOID_ELEMENTS,
		TEXT_LEVEL_SEMANTIC_ELEMENTS: TEXT_LEVEL_SEMANTIC_ELEMENTS,
		isBlock: isBlock,
		isIgnorableWhitespace: isIgnorableWhitespace,
		isInlineFormattable: isInlineFormattable,
		isProppedBlock: isProppedBlock,
		isEditingHost: isEditingHost,
		findNodeLeft: findNodeLeft,
		findNodeRight: findNodeRight,
		allowNestedParagraph: allowNestedParagraph,
		trimWhitespaceCharacters: trimWhitespaceCharacters,
		isWSPorZWSPNode: isWSPorZWSPNode,
		isWSPorZWSPText: isWSPorZWSPText,
		isUnrenderedNode: isUnrenderedNode,
		isRenderedNode: isRenderedNode,
		hasOnlyWhiteSpaceChildren: hasOnlyWhiteSpaceChildren
	};
});

define('aloha/engine',[
	'aloha/core',
	'aloha/ecma5shims',
	'util/maps',
	'util/html',
	'util/dom',
	'util/dom2',
	'jquery',
	'aloha/content-rules',
	'PubSub'
], function (
	Aloha,
	$_,
	Maps,
	Html,
	Dom,
	Dom2,
	jQuery,
	ContentRules,
	PubSub
) {
	

	/**
	 *
	 * @param obj
	 * @param attr
	 * @returns {Boolean} true
	 */
	function hasAttribute(obj, attr) {
		var native_method = obj.hasAttribute;
		if (native_method) {
			return obj.hasAttribute(attr);
		}
		return (typeof obj.attributes[attr] !== 'undefined');
	}

	/**
	 * Insert the node `node` after `preceding`.
	 *
	 * @param {Element} node
	 * @param {Element} preceding
	 * @return {Element}
	 */
	function insertAfter(node, preceding) {
		var next = preceding.nextSibling,
		    parent = preceding.parentNode;
		if (next) {
			parent.insertBefore(node, next);
		} else {
			parent.appendChild(node);
		}
		return node;
	}

	/**
	 * Splits text node `node` at the given text index.
	 *
	 * Note that we cannot use splitText() because it is bugridden in IE 9.
	 *
	 * Borrowed from rangy.
	 *
	 * @param {Element} node
	 * @param {number} index
	 * @return {Element}
	 */
	function splitText(node, index) {
		var newNode = node.cloneNode(false);
		newNode.deleteData(0, index);
		node.deleteData(index, node.length - index);
		insertAfter(newNode, node);
		return newNode;
	}

	var htmlNamespace = "http://www.w3.org/1999/xhtml";

	var cssStylingFlag = false;

	// This is bad :(
	var globalRange = null;

	// Commands are stored in a dictionary where we call their actions and such
	var commands = {};

	///////////////////////////////////////////////////////////////////////////////
	////////////////////////////// Utility functions //////////////////////////////
	///////////////////////////////////////////////////////////////////////////////
	//@{

	// Opera 11 puts HTML elements in the null namespace, it seems.
	function isHtmlNamespace(ns) {
		return ns === null || !ns || ns === htmlNamespace;
	}

	// "An HTML element is an Element whose namespace is the HTML namespace."
	//
	// I allow an extra argument to more easily check whether something is a
	// particular HTML element, like isNamedHtmlElement(node, 'OL').  It accepts arrays
	// too, like isHtmlElementInArray(node, ["OL", "UL"]) to check if it's an ol or ul.
	// TODO This function was prominent during profiling. Remove it
	//      and replace with calls to isAnyHtmlElement, isNamedHtmlElement
	//      and is isMappedHtmlElement.
	function isHtmlElement_obsolete(node, tags) {
		if (typeof tags == "string") {
			tags = [tags];
		}
		if (typeof tags == "object") {
			tags = $_(tags).map(function (tag) {
				return tag.toUpperCase();
			});
		}
		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI) && (typeof tags == "undefined" || $_(tags).indexOf(node.tagName) != -1);
	}

	function isAnyHtmlElement(node) {
		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI);
	}

	// name should be uppercase
	function isNamedHtmlElement(node, name) {
		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI)
		// This function is passed in a mix of upper and lower case names
			&& name.toUpperCase() === node.nodeName;
	}

	// TODO remove when isHtmlElementInArray is removed
	function arrayContainsInsensitive(array, str) {
		var i, len;
		str = str.toUpperCase();
		for (i = 0, len = array.length; i < len; i++) {
			if (array[i].toUpperCase() === str) {
				return true;
			}
		}
		return false;
	}
	// TODO replace calls to this function with calls to isMappedHtmlElement()
	function isHtmlElementInArray(node, array) {
		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI)
		// This function is passed in a mix of upper and lower case names
			&& arrayContainsInsensitive(array, node.nodeName);
	}

	// map must have all-uppercase keys
	function isMappedHtmlElement(node, map) {
		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI) && map[node.nodeName];
	}

	/**
	 * Method to count the number of styles in the given style
	 */
	function getStyleLength(node) {
		var s;
		var styleLength = 0;

		if (!node) {
			return 0;
		}

		if (!node.style) {
			return 0;
		}

		// some browsers support .length on styles
		if (typeof node.style.length !== 'undefined') {
			return node.style.length;
		}

		/*jslint forin: true*/ //not sure whether node.style.hasOwnProperty is valid
		for (s in node.style) {
			if (node.style[s] && node.style[s] !== 0 && node.style[s] !== 'false') {
				styleLength++;
			}
		}
		/*jslint forin: false*/

		return styleLength;
	}

	function toArray(obj) {
		if (!obj) {
			return null;
		}
		var array = [],
			i,
		    l = obj.length;
		// iterate backwards ensuring that length is an UInt32
		i = l >>> 0;
		while (i--) {
			array[i] = obj[i];
		}
		return array;
	}

	function nextNodeDescendants(node) {
		while (node && !node.nextSibling) {
			node = node.parentNode;
		}
		if (!node) {
			return null;
		}
		return node.nextSibling;
	}

	function nextNode(node) {
		if (node.hasChildNodes()) {
			return node.firstChild;
		}
		return nextNodeDescendants(node);
	}

	function previousNode(node) {
		if (node.previousSibling) {
			node = node.previousSibling;
			while (node.hasChildNodes()) {
				node = node.lastChild;
			}
			return node;
		}
		if (node.parentNode && node.parentNode.nodeType == $_.Node.ELEMENT_NODE) {
			return node.parentNode;
		}
		return null;
	}

	/**
	 * Returns true if ancestor is an ancestor of descendant, false otherwise.
	 */
	function isAncestor(ancestor, descendant) {
		return ancestor && descendant && Boolean($_.compareDocumentPosition(ancestor, descendant) & $_.Node.DOCUMENT_POSITION_CONTAINED_BY);
	}

	/**
	 * Returns true if ancestor is an ancestor of or equal to descendant, false
	 * otherwise.
	 */
	function isAncestorContainer(ancestor, descendant) {
		return (ancestor || descendant) && (ancestor == descendant || isAncestor(ancestor, descendant));
	}

	/**
	 * Returns true if descendant is a descendant of ancestor, false otherwise.
	 */
	function isDescendant(descendant, ancestor) {
		return ancestor && descendant && Boolean($_.compareDocumentPosition(ancestor, descendant) & $_.Node.DOCUMENT_POSITION_CONTAINED_BY);
	}

	/**
	 * Returns true if node1 is before node2 in tree order, false otherwise.
	 */
	function isBefore(node1, node2) {
		return Boolean($_.compareDocumentPosition(node1, node2) & $_.Node.DOCUMENT_POSITION_FOLLOWING);
	}

	/**
	 * Returns true if node1 is after node2 in tree order, false otherwise.
	 */
	function isAfter(node1, node2) {
		return Boolean($_.compareDocumentPosition(node1, node2) & $_.Node.DOCUMENT_POSITION_PRECEDING);
	}

	function getAncestors(node) {
		var ancestors = [];
		while (node.parentNode) {
			ancestors.unshift(node.parentNode);
			node = node.parentNode;
		}
		return ancestors;
	}

	function getDescendants(node) {
		var descendants = [];
		var stop = nextNodeDescendants(node);
		while (null != (node = nextNode(node)) && node != stop) {
			descendants.push(node);
		}
		return descendants;
	}

	function convertProperty(property) {
		// Special-case for now
		var map = {
			"fontFamily": "font-family",
			"fontSize": "font-size",
			"fontStyle": "font-style",
			"fontWeight": "font-weight",
			"textDecoration": "text-decoration"
		};
		if (typeof map[property] != "undefined") {
			return map[property];
		}

		return property;
	}

	// Return the <font size=X> value for the given CSS size, or undefined if there
	// is none.
	function cssSizeToLegacy(cssVal) {
		return {
			"xx-small": 1,
			"small": 2,
			"medium": 3,
			"large": 4,
			"x-large": 5,
			"xx-large": 6,
			"xxx-large": 7
		}[cssVal];
	}

	// Return the CSS size given a legacy size.
	function legacySizeToCss(legacyVal) {
		return {
			1: "xx-small",
			2: "small",
			3: "medium",
			4: "large",
			5: "x-large",
			6: "xx-large",
			7: "xxx-large"
		}[legacyVal];
	}

	// "the directionality" from HTML.  I don't bother caring about non-HTML
	// elements.
	//
	// "The directionality of an element is either 'ltr' or 'rtl', and is
	// determined as per the first appropriate set of steps from the following
	// list:"
	function getDirectionality(element) {
		// "If the element's dir attribute is in the ltr state
		//     The directionality of the element is 'ltr'."
		if (element.dir == "ltr") {
			return "ltr";
		}

		// "If the element's dir attribute is in the rtl state
		//     The directionality of the element is 'rtl'."
		if (element.dir == "rtl") {
			return "rtl";
		}

		// "If the element's dir attribute is in the auto state
		// "If the element is a bdi element and the dir attribute is not in a
		// defined state (i.e. it is not present or has an invalid value)
		//     [lots of complicated stuff]
		//
		// Skip this, since no browser implements it anyway.

		// "If the element is a root element and the dir attribute is not in a
		// defined state (i.e. it is not present or has an invalid value)
		//     The directionality of the element is 'ltr'."
		if (!isAnyHtmlElement(element.parentNode)) {
			return "ltr";
		}

		// "If the element has a parent element and the dir attribute is not in a
		// defined state (i.e. it is not present or has an invalid value)
		//     The directionality of the element is the same as the element's
		//     parent element's directionality."
		return getDirectionality(element.parentNode);
	}

	//@}

	///////////////////////////////////////////////////////////////////////////////
	///////////////////////////// DOM Range functions /////////////////////////////
	///////////////////////////////////////////////////////////////////////////////
	//@{

	// "The length of a Node node is the following, depending on node:
	//
	// ProcessingInstruction
	// DocumentType
	//   Always 0.
	// Text
	// Comment
	//   node's length.
	// Any other node
	//   node's childNodes's length."
	function getNodeLength(node) {
		switch (node.nodeType) {
		case $_.Node.PROCESSING_INSTRUCTION_NODE:
		case $_.Node.DOCUMENT_TYPE_NODE:
			return 0;

		case $_.Node.TEXT_NODE:
		case $_.Node.COMMENT_NODE:
			return node.length;

		default:
			return node.childNodes.length;
		}
	}

	/**
	 * The position of two boundary points relative to one another, as defined by
	 * DOM Range.
	 */
	function getPosition(nodeA, offsetA, nodeB, offsetB) {
		// "If node A is the same as node B, return equal if offset A equals offset
		// B, before if offset A is less than offset B, and after if offset A is
		// greater than offset B."
		if (nodeA == nodeB) {
			if (offsetA == offsetB) {
				return "equal";
			}
			if (offsetA < offsetB) {
				return "before";
			}
			if (offsetA > offsetB) {
				return "after";
			}
		}

		var documentPosition = $_.compareDocumentPosition(nodeB, nodeA);
		// "If node A is after node B in tree order, compute the position of (node
		// B, offset B) relative to (node A, offset A). If it is before, return
		// after. If it is after, return before."
		if (documentPosition & $_.Node.DOCUMENT_POSITION_FOLLOWING) {
			var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
			if (pos == "before") {
				return "after";
			}
			if (pos == "after") {
				return "before";
			}
		}

		// "If node A is an ancestor of node B:"
		if (documentPosition & $_.Node.DOCUMENT_POSITION_CONTAINS) {
			// "Let child equal node B."
			var child = nodeB;

			// "While child is not a child of node A, set child to its parent."
			while (child.parentNode != nodeA) {
				child = child.parentNode;
			}

			// "If the index of child is less than offset A, return after."
			if (Dom.getIndexInParent(child) < offsetA) {
				return "after";
			}
		}

		// "Return before."
		return "before";
	}

	/**
	 * Returns the furthest ancestor of a Node as defined by DOM Range.
	 */
	function getFurthestAncestor(node) {
		var root = node;
		while (root.parentNode != null) {
			root = root.parentNode;
		}
		return root;
	}

	/**
	 * "contained" as defined by DOM Range: "A Node node is contained in a range
	 * range if node's furthest ancestor is the same as range's root, and (node, 0)
	 * is after range's start, and (node, length of node) is before range's end."
	 */
	function isContained(node, range) {
		var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
		if (pos1 !== "after") {
			return false;
		}
		var pos2 = getPosition(node, getNodeLength(node), range.endContainer, range.endOffset);
		if (pos2 !== "before") {
			return false;
		}
		return getFurthestAncestor(node) == getFurthestAncestor(range.startContainer);
	}

	/**
	 * Return all nodes contained in range that the provided function returns true
	 * for, omitting any with an ancestor already being returned.
	 */
	function getContainedNodes(range, condition) {
		if (typeof condition == "undefined") {
			condition = function () {
				return true;
			};
		}
		var node = range.startContainer;
		if (node.hasChildNodes() && range.startOffset < node.childNodes.length) {
			// A child is contained
			node = node.childNodes[range.startOffset];
		} else if (range.startOffset == getNodeLength(node)) {
			// No descendant can be contained
			node = nextNodeDescendants(node);
		} else {
			// No children; this node at least can't be contained
			node = nextNode(node);
		}

		var stop = range.endContainer;
		if (stop.hasChildNodes() && range.endOffset < stop.childNodes.length) {
			// The node after the last contained node is a child
			stop = stop.childNodes[range.endOffset];
		} else {
			// This node and/or some of its children might be contained
			stop = nextNodeDescendants(stop);
		}

		var nodeList = [];
		while (isBefore(node, stop)) {
			if (isContained(node, range) && condition(node)) {
				nodeList.push(node);
				node = nextNodeDescendants(node);
				continue;
			}
			node = nextNode(node);
		}
		return nodeList;
	}

	/**
	 * As above, but includes nodes with an ancestor that's already been returned.
	 */
	function getAllContainedNodes(range, condition) {
		if (typeof condition == "undefined") {
			condition = function () {
				return true;
			};
		}
		var node = range.startContainer;
		if (node.hasChildNodes() && range.startOffset < node.childNodes.length) {
			// A child is contained
			node = node.childNodes[range.startOffset];
		} else if (range.startOffset == getNodeLength(node)) {
			// No descendant can be contained
			node = nextNodeDescendants(node);
		} else {
			// No children; this node at least can't be contained
			node = nextNode(node);
		}

		var stop = range.endContainer;
		if (stop.hasChildNodes() && range.endOffset < stop.childNodes.length) {
			// The node after the last contained node is a child
			stop = stop.childNodes[range.endOffset];
		} else {
			// This node and/or some of its children might be contained
			stop = nextNodeDescendants(stop);
		}

		var nodeList = [];
		while (isBefore(node, stop)) {
			if (isContained(node, range) && condition(node)) {
				nodeList.push(node);
			}
			node = nextNode(node);
		}
		return nodeList;
	}

	// Returns either null, or something of the form rgb(x, y, z), or something of
	// the form rgb(x, y, z, w) with w != 0.
	function normalizeColor(color) {
		if (color.toLowerCase() == "currentcolor") {
			return null;
		}

		var outerSpan = document.createElement("span");
		document.body.appendChild(outerSpan);
		outerSpan.style.color = "black";

		var innerSpan = document.createElement("span");
		outerSpan.appendChild(innerSpan);
		innerSpan.style.color = color;
		color = $_.getComputedStyle(innerSpan).color;

		if (color == "rgb(0, 0, 0)") {
			// Maybe it's really black, maybe it's invalid.
			outerSpan.color = "white";
			color = $_.getComputedStyle(innerSpan).color;
			if (color != "rgb(0, 0, 0)") {
				return null;
			}
		}

		document.body.removeChild(outerSpan);

		// I rely on the fact that browsers generally provide consistent syntax for
		// getComputedStyle(), although it's not standardized.  There are only two
		// exceptions I found:
		if (/^rgba\([0-9]+, [0-9]+, [0-9]+, 1\)$/.test(color)) {
			// IE10PP2 seems to do this sometimes.
			return color.replace("rgba", "rgb").replace(", 1)", ")");
		}
		if (color == "transparent") {
			// IE10PP2, Firefox 7.0a2, and Opera 11.50 all return "transparent" if
			// the specified value is "transparent".
			return "rgba(0, 0, 0, 0)";
		}
		return color;
	}

	// Returns either null, or something of the form #xxxxxx, or the color itself
	// if it's a valid keyword.
	function parseSimpleColor(color) {
		color = color.toLowerCase();
		if ($_(["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", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "green", "greenyellow", "grey", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey", "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", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen"]).indexOf(color) != -1) {
			return color;
		}

		color = normalizeColor(color);
		var matches = /^rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)$/.exec(color);
		if (matches) {
			return "#" + parseInt(matches[1], 10).toString(16).replace(/^.$/, "0$&") + parseInt(matches[2], 10).toString(16).replace(/^.$/, "0$&") + parseInt(matches[3], 10).toString(16).replace(/^.$/, "0$&");
		} else if (/^#[abcdef0123456789]+$/i.exec(color)) {
			// return hexadecimal color values (as returned by IE 7/8)
			return color;
		}
		return null;
	}

	//@}

	//////////////////////////////////////////////////////////////////////////////
	/////////////////////////// Edit command functions ///////////////////////////
	//////////////////////////////////////////////////////////////////////////////

	/////////////////////////////////////////////////
	///// Methods of the HTMLDocument interface /////
	/////////////////////////////////////////////////
	//@{

	var getStateOverride,
	    setStateOverride,
	    resetOverrides,
	    unsetStateOverride,
	    getValueOverride,
	    setValueOverride,
	    unsetValueOverride;

	var executionStackDepth = 0;

	// Helper function for fontSize's action plus queryOutputHelper.  It's just the
	// middle of fontSize's action, ripped out into its own function.
	function normalizeFontSize(value) {
		// "Strip leading and trailing whitespace from value."
		//
		// Cheap hack, not following the actual algorithm.
		value = $_(value).trim();

		// "If value is a valid floating point number, or would be a valid
		// floating point number if a single leading "+" character were
		// stripped:"
		if (/^[\-+]?[0-9]+(\.[0-9]+)?([eE][\-+]?[0-9]+)?$/.test(value)) {
			var mode;

			// "If the first character of value is "+", delete the character
			// and let mode be "relative-plus"."
			if (value[0] == "+") {
				value = value.slice(1);
				mode = "relative-plus";
				// "Otherwise, if the first character of value is "-", delete the
				// character and let mode be "relative-minus"."
			} else if (value[0] == "-") {
				value = value.slice(1);
				mode = "relative-minus";
				// "Otherwise, let mode be "absolute"."
			} else {
				mode = "absolute";
			}

			// "Apply the rules for parsing non-negative integers to value, and
			// let number be the result."
			//
			// Another cheap hack.
			var num = parseInt(value, 10);

			// "If mode is "relative-plus", add three to number."
			if (mode == "relative-plus") {
				num += 3;
			}

			// "If mode is "relative-minus", negate number, then add three to
			// it."
			if (mode == "relative-minus") {
				num = 3 - num;
			}

			// "If number is less than one, let number equal 1."
			if (num < 1) {
				num = 1;
			}

			// "If number is greater than seven, let number equal 7."
			if (num > 7) {
				num = 7;
			}

			// "Set value to the string here corresponding to number:" [table
			// omitted]
			value = {
				1: "xx-small",
				2: "small",
				3: "medium",
				4: "large",
				5: "x-large",
				6: "xx-large",
				7: "xxx-large"
			}[num];
		}

		return value;
	}

	function getLegacyFontSize(size) {
		// For convenience in other places in my code, I handle all sizes, not just
		// pixel sizes as the spec says.  This means pixel sizes have to be passed
		// in suffixed with "px", not as plain numbers.
		size = normalizeFontSize(size);

		if (jQuery.inArray(size, ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]) == -1 && !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc|px)$/.test(size)) {
			// There is no sensible legacy size for things like "2em".
			return null;
		}

		var font = document.createElement("font");
		document.body.appendChild(font);
		if (size == "xxx-large") {
			font.size = 7;
		} else {
			font.style.fontSize = size;
		}
		var pixelSize = parseInt($_.getComputedStyle(font).fontSize, 10);
		document.body.removeChild(font);

		// "Let returned size be 1."
		var returnedSize = 1;

		// "While returned size is less than 7:"
		while (returnedSize < 7) {
			// "Let lower bound be the resolved value of "font-size" in pixels
			// of a font element whose size attribute is set to returned size."
			font = document.createElement("font");
			font.size = returnedSize;
			document.body.appendChild(font);
			var lowerBound = parseInt($_.getComputedStyle(font).fontSize, 10);

			// "Let upper bound be the resolved value of "font-size" in pixels
			// of a font element whose size attribute is set to one plus
			// returned size."
			font.size = 1 + returnedSize;
			var upperBound = parseInt($_.getComputedStyle(font).fontSize, 10);
			document.body.removeChild(font);

			// "Let average be the average of upper bound and lower bound."
			var average = (upperBound + lowerBound) / 2;

			// "If pixel size is less than average, return the one-element
			// string consisting of the digit returned size."
			if (pixelSize < average) {
				return String(returnedSize);
			}

			// "Add one to returned size."
			returnedSize++;
		}

		// "Return "7"."
		return "7";
	}

	// Helper function for common behavior.
	function editCommandMethod(command, prop, range, callback) {
		var ret;

		// Set up our global range magic, but only if we're the outermost function
		if (executionStackDepth === 0) {
			globalRange = range;
		}

		executionStackDepth++;
		try {
			ret = callback();
		} catch (e) {
			executionStackDepth--;
			throw e;
		}
		executionStackDepth--;
		return ret;
	}

	function myQueryCommandEnabled(command, range) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		command = command.toLowerCase();

		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
		return editCommandMethod(command, "action", range, (function (command) {
			return function () {
				// "Among commands defined in this specification, those listed in
				// Miscellaneous commands are always enabled. The other commands defined
				// here are enabled if the active range is not null, and disabled
				// otherwise."
				return jQuery.inArray(command, ["copy", "cut", "paste", "selectall", "stylewithcss", "usecss"]) != -1 || range !== null;
			};
		}(command)));
	}

	function setActiveRange(range) {
		var rangeObject = new window.GENTICS.Utils.RangeObject();

		rangeObject.startContainer = range.startContainer;
		rangeObject.startOffset = range.startOffset;
		rangeObject.endContainer = range.endContainer;
		rangeObject.endOffset = range.endOffset;

		rangeObject.correctRange();
		rangeObject.select();
	}

	function myExecCommand(commandArg, showUiArg, valueArg, range) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		var command = commandArg.toLowerCase();
		var showUi = showUiArg;
		var value = valueArg;

		// "If only one argument was provided, let show UI be false."
		//
		// If range was passed, I can't actually detect how many args were passed
		// . . .
		if (arguments.length == 1 || (arguments.length >= 4 && typeof showUi == "undefined")) {
			showUi = false;
		}

		// "If only one or two arguments were provided, let value be the empty
		// string."
		if (arguments.length <= 2 || (arguments.length >= 4 && typeof value == "undefined")) {
			value = "";
		}

		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
		//
		// "If command has no action, raise an INVALID_ACCESS_ERR exception."
		return editCommandMethod(command, "action", range, (function (command, showUi, value) {
			return function () {
				// "If command is not enabled, return false."
				if (!myQueryCommandEnabled(command)) {
					return false;
				}

				// "Take the action for command, passing value to the instructions as an
				// argument."
				commands[command].action(value, range);

				// always fix the range after the command is complete
				setActiveRange(range);

				// "Return true."
				return true;
			};
		}(command, showUi, value)));
	}

	function myQueryCommandIndeterm(command, range) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		command = command.toLowerCase();

		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
		//
		// "If command has no indeterminacy, raise an INVALID_ACCESS_ERR
		// exception."
		return editCommandMethod(command, "indeterm", range, (function (command) {
			return function () {
				// "If command is not enabled, return false."
				if (!myQueryCommandEnabled(command, range)) {
					return false;
				}

				// "Return true if command is indeterminate, otherwise false."
				return commands[command].indeterm(range);
			};
		}(command)));
	}

	function myQueryCommandState(command, range) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		command = command.toLowerCase();

		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
		//
		// "If command has no state, raise an INVALID_ACCESS_ERR exception."
		return editCommandMethod(command, "state", range, (function (command) {
			return function () {
				// "If command is not enabled, return false."
				if (!myQueryCommandEnabled(command, range)) {
					return false;
				}

				// "If the state override for command is set, return it."
				if (typeof getStateOverride(command, range) != "undefined") {
					return getStateOverride(command, range);
				}

				// "Return true if command's state is true, otherwise false."
				return commands[command].state(range);
			};
		}(command)));
	}

	// "When the queryCommandSupported(command) method on the HTMLDocument
	// interface is invoked, the user agent must return true if command is
	// supported, and false otherwise."
	function myQueryCommandSupported(command) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		command = command.toLowerCase();

		return commands.hasOwnProperty(command);
	}

	function myQueryCommandValue(command, range) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		command = command.toLowerCase();

		return editCommandMethod(command, "value", range, function () {
			// "If command is not supported or has no value, return the empty string."
			if (!commands.hasOwnProperty(command) || !commands[command].hasOwnProperty("value")) {
				return "";
			}

			// "If command is "fontSize" and its value override is set, convert the
			// value override to an integer number of pixels and return the legacy
			// font size for the result."
			if (command == "fontsize" && getValueOverride("fontsize", range) !== undefined) {
				return getLegacyFontSize(getValueOverride("fontsize", range));
			}

			// "If the value override for command is set, return it."
			if (typeof getValueOverride(command, range) != "undefined") {
				return getValueOverride(command, range);
			}

			// "Return command's value."
			return commands[command].value(range);
		});
	}
	//@}

	//////////////////////////////
	///// Common definitions /////
	//////////////////////////////
	//@{

	// "A prohibited paragraph child name is "address", "article", "aside",
	// "blockquote", "caption", "center", "col", "colgroup", "dd", "details",
	// "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
	// "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
	// "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
	// "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or
	// "xmp"."
	var prohibitedParagraphChildNamesMap = {
		"ADDRESS": true,
		"ARTICLE": true,
		"ASIDE": true,
		"BLOCKQUOTE": true,
		"CAPTION": true,
		"CENTER": true,
		"COL": true,
		"COLGROUP": true,
		"DD": true,
		"DETAILS": true,
		"DIR": true,
		"DIV": true,
		"DL": true,
		"DT": true,
		"FIELDSET": true,
		"FIGCAPTION": true,
		"FIGURE": true,
		"FOOTER": true,
		"FORM": true,
		"H1": true,
		"H2": true,
		"H3": true,
		"H4": true,
		"H5": true,
		"H6": true,
		"HEADER": true,
		"HGROUP": true,
		"HR": true,
		"LI": true,
		"LISTING": true,
		"MENU": true,
		"NAV": true,
		"OL": true,
		"P": true,
		"PLAINTEXT": true,
		"PRE": true,
		"SECTION": true,
		"SUMMARY": true,
		"TABLE": true,
		"TBODY": true,
		"TD": true,
		"TFOOT": true,
		"TH": true,
		"THEAD": true,
		"TR": true,
		"UL": true,
		"XMP": true
	};

	// "A prohibited paragraph child is an HTML element whose local name is a
	// prohibited paragraph child name."
	function isProhibitedParagraphChild(node) {
		return isMappedHtmlElement(node, prohibitedParagraphChildNamesMap);
	}

	var nonBlockDisplayValuesMap = {
		"inline": true,
		"inline-block": true,
		"inline-table": true,
		"none": true
	};

	// "A block node is either an Element whose "display" property does not have
	// resolved value "inline" or "inline-block" or "inline-table" or "none", or a
	// Document, or a DocumentFragment."
	function isBlockNode(node) {
		return node && ((node.nodeType == $_.Node.ELEMENT_NODE && !nonBlockDisplayValuesMap[$_.getComputedStyle(node).display]) || node.nodeType == $_.Node.DOCUMENT_NODE || node.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE);
	}

	// "An inline node is a node that is not a block node."
	function isInlineNode(node) {
		return node && !isBlockNode(node);
	}

	// "An editing host is a node that is either an Element with a contenteditable
	// attribute set to the true state, or the Element child of a Document whose
	// designMode is enabled."
	function isEditingHost(node) {
		return node && node.nodeType == $_.Node.ELEMENT_NODE && (node.contentEditable == "true" || (node.parentNode && node.parentNode.nodeType == $_.Node.DOCUMENT_NODE && node.parentNode.designMode == "on"));
	}

	// "Something is editable if it is a node which is not an editing host, does
	// not have a contenteditable attribute set to the false state, and whose
	// parent is an editing host or editable."
	function isEditable(node) {
		// This is slightly a lie, because we're excluding non-HTML elements with
		// contentEditable attributes.
		return node && !isEditingHost(node) && (node.nodeType != $_.Node.ELEMENT_NODE || node.contentEditable != "false" || jQuery(node).hasClass('aloha-table-wrapper')) && (isEditingHost(node.parentNode) || isEditable(node.parentNode));
	}

	// Helper function, not defined in the spec
	function hasEditableDescendants(node) {
		var i;
		for (i = 0; i < node.childNodes.length; i++) {
			if (isEditable(node.childNodes[i]) || hasEditableDescendants(node.childNodes[i])) {
				return true;
			}
		}
		return false;
	}

	// "The editing host of node is null if node is neither editable nor an editing
	// host; node itself, if node is an editing host; or the nearest ancestor of
	// node that is an editing host, if node is editable."
	function getEditingHostOf(node) {
		if (isEditingHost(node)) {
			return node;
		}
		if (isEditable(node)) {
			var ancestor = node.parentNode;
			while (!isEditingHost(ancestor)) {
				ancestor = ancestor.parentNode;
			}
			return ancestor;
		}
		return null;
	}

	// "Two nodes are in the same editing host if the editing host of the first is
	// non-null and the same as the editing host of the second."
	function inSameEditingHost(node1, node2) {
		return getEditingHostOf(node1) && getEditingHostOf(node1) == getEditingHostOf(node2);
	}

	// "A collapsed line break is a br that begins a line box which has nothing
	// else in it, and therefore has zero height."
	function isCollapsedLineBreak(br) {
		if (!isNamedHtmlElement(br, 'br')) {
			return false;
		}

		// Add a zwsp after it and see if that changes the height of the nearest
		// non-inline parent.  Note: this is not actually reliable, because the
		// parent might have a fixed height or something.
		var ref = br.parentNode;
		while ($_.getComputedStyle(ref).display == "inline") {
			ref = ref.parentNode;
		}

		var origStyle = {
			height: ref.style.height,
			maxHeight: ref.style.maxHeight,
			minHeight: ref.style.minHeight
		};

		ref.style.height = 'auto';
		ref.style.maxHeight = 'none';
		if (!(Aloha.browser.msie && Aloha.browser.version < 8)) {
			ref.style.minHeight = '0';
		}
		var space = document.createTextNode('\u200b');
		var origHeight = ref.offsetHeight;
		if (origHeight == 0) {
			throw 'isCollapsedLineBreak: original height is zero, bug?';
		}
		br.parentNode.insertBefore(space, br.nextSibling);
		var finalHeight = ref.offsetHeight;
		space.parentNode.removeChild(space);

		ref.style.height = origStyle.height;
		ref.style.maxHeight = origStyle.maxHeight;
		if (!(Aloha.browser.msie && Aloha.browser.version < 8)) {
			ref.style.minHeight = origStyle.minHeight;
		}

		// Allow some leeway in case the zwsp didn't create a whole new line, but
		// only made an existing line slightly higher.  Firefox 6.0a2 shows this
		// behavior when the first line is bold.
		return origHeight < finalHeight - 5;
	}

	// "An extraneous line break is a br that has no visual effect, in that
	// removing it from the DOM would not change layout, except that a br that is
	// the sole child of an li is not extraneous."
	function isExtraneousLineBreak(br) {

		if (!isNamedHtmlElement(br, 'br')) {
			return false;
		}

		if (isNamedHtmlElement(br.parentNode, "li") && br.parentNode.childNodes.length == 1) {
			return false;
		}

		// Make the line break disappear and see if that changes the block's
		// height.  Yes, this is an absurd hack.  We have to reset height etc. on
		// the reference node because otherwise its height won't change if it's not
		// auto.
		var ref = br.parentNode;
		while ($_.getComputedStyle(ref).display == "inline") {
			ref = ref.parentNode;
		}

		var origStyle = {
			height: ref.style.height,
			maxHeight: ref.style.maxHeight,
			minHeight: ref.style.minHeight,
			contentEditable: ref.contentEditable
		};

		ref.style.height = 'auto';
		ref.style.maxHeight = 'none';
		ref.style.minHeight = '0';
		// IE7 would ignore display:none in contentEditable, so we temporarily set it to false
		if (Aloha.browser.msie && Aloha.browser.version <= 7) {
			ref.contentEditable = 'false';
		}

		var origHeight = Dom.getOffsetHeight(ref);
		if (origHeight == 0) {
			throw "isExtraneousLineBreak: original height is zero, bug?";
		}

		var origBrDisplay = br.style.display;
		br.style.display = 'none';
		var finalHeight = ref.offsetHeight;

		// Restore original styles to the touched elements.
		ref.style.height = origStyle.height;
		ref.style.maxHeight = origStyle.maxHeight;
		ref.style.minHeight = origStyle.minHeight;
		// reset contentEditable for IE7
		if (Aloha.browser.msie && Aloha.browser.version <= 7) {
			ref.contentEditable = origStyle.contentEditable;
		}
		br.style.display = origBrDisplay;

		// https://github.com/alohaeditor/Aloha-Editor/issues/516
		// look like it works in msie > 7
		/* if (Aloha.browser.msie && Aloha.browser.version < 8) {
		   br.removeAttribute("style");
		   ref.removeAttribute("style");
		   } */

		return origHeight == finalHeight;
	}

	// "A whitespace node is either a Text node whose data is the empty string; or
	// a Text node whose data consists only of one or more tabs (0x0009), line
	// feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
	// parent is an Element whose resolved value for "white-space" is "normal" or
	// "nowrap"; or a Text node whose data consists only of one or more tabs
	// (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
	// parent is an Element whose resolved value for "white-space" is "pre-line"."
	function isWhitespaceNode(node) {
		var nodeTypes = $_.Node;
		if (node && node.nodeType === nodeTypes.TEXT_NODE) {
			var parentNode = node.parentNode;

			var nodeData = node.data;
			if (jQuery.trim(nodeData).length === 0) {
				return true;
			} else if (parentNode && /^[\t\n\r ]+$/.test(nodeData)) {
				if (parentNode.nodeType === nodeTypes.ELEMENT_NODE) {
					if (jQuery.inArray($_.getComputedStyle(parentNode).whiteSpace, ["normal", "nowrap"]) !== -1) {
						return true;
					} else if ($_.getComputedStyle(parentNode).whiteSpace === "pre-line") {
						return true;
					}
				} else if (parentNode.nodeType === nodeTypes.DOCUMENT_FRAGMENT_NODE) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Collapse sequences of ignorable whitespace (tab (0x0009), line feed (0x000A), carriage return (0x000D), space (0x0020)) to only one space.
	 * Preserve the given range if necessary.
	 * @param node text node
	 * @param range range
	 */
	function collapseWhitespace(node, range) {
		// "If node is neither editable nor an editing host, abort these steps."
		if (!isEditable(node) && !isEditingHost(node)) {
			return;
		}

		// if the given node is not a text node, return
		if (!node || node.nodeType !== $_.Node.TEXT_NODE) {
			return;
		}

		// if the node is in a pre or pre-wrap node, return
		if (jQuery.inArray($_.getComputedStyle(node.parentNode).whiteSpace, ["pre", "pre-wrap"]) != -1) {
			return;
		}

		// if the given node does not contain sequences of at least two consecutive ignorable whitespace characters, return
		if (!/[\t\n\r ]{2,}/.test(node.data)) {
			return;
		}

		var newData = '';
		var correctStart = range.startContainer == node;
		var correctEnd = range.endContainer == node;
		var wsFound = false;
		var i;

		// iterate through the node data
		for (i = 0; i < node.data.length; ++i) {
			if (/[\t\n\r ]/.test(node.data.substr(i, 1))) {
				// found a whitespace
				if (!wsFound) {
					// this is the first whitespace in the current sequence
					// add a whitespace to the new data sequence
					newData += ' ';
					// remember that we found a whitespace
					wsFound = true;
				} else {
					// this is not the first whitespace in the sequence, so omit this character
					if (correctStart && newData.length < range.startOffset) {
						range.startOffset--;
					}
					if (correctEnd && newData.length < range.endOffset) {
						range.endOffset--;
					}
				}
			} else {
				newData += node.data.substr(i, 1);
				wsFound = false;
			}
		}

		// set the new data
		node.data = newData;
	}

	// "node is a collapsed whitespace node if the following algorithm returns
	// true:"
	function isCollapsedWhitespaceNode(node) {
		// "If node is not a whitespace node, return false."
		if (!isWhitespaceNode(node)) {
			return false;
		}

		// "If node's data is the empty string, return true."
		if (node.data == "") {
			return true;
		}

		// "Let ancestor be node's parent."
		var ancestor = node.parentNode;

		// "If ancestor is null, return true."
		if (!ancestor) {
			return true;
		}

		// "If the "display" property of some ancestor of node has resolved value
		// "none", return true."
		if ($_(getAncestors(node)).some(function (ancestor) { return ancestor.nodeType == $_.Node.ELEMENT_NODE && $_.getComputedStyle(ancestor).display == "none"; })) {
			return true;
		}

		// "While ancestor is not a block node and its parent is not null, set
		// ancestor to its parent."
		while (!isBlockNode(ancestor) && ancestor.parentNode) {
			ancestor = ancestor.parentNode;
		}

		// "Let reference be node."
		var reference = node;

		// "While reference is a descendant of ancestor:"
		while (reference != ancestor) {
			// "Let reference be the node before it in tree order."
			reference = previousNode(reference);

			// "If reference is a block node or a br, return true."
			if (isBlockNode(reference) || isNamedHtmlElement(reference, 'br')) {
				return true;
			}

			// "If reference is a Text node that is not a whitespace node, or is an
			// img, break from this loop."
			if ((reference.nodeType == $_.Node.TEXT_NODE && !isWhitespaceNode(reference)) || isNamedHtmlElement(reference, 'img')) {
				break;
			}
		}

		// "Let reference be node."
		reference = node;

		// "While reference is a descendant of ancestor:"
		var stop = nextNodeDescendants(ancestor);
		while (reference != stop) {
			// "Let reference be the node after it in tree order, or null if there
			// is no such node."
			reference = nextNode(reference);

			// "If reference is a block node or a br, return true."
			if (isBlockNode(reference) || isNamedHtmlElement(reference, 'br')) {
				return true;
			}

			// "If reference is a Text node that is not a whitespace node, or is an
			// img, break from this loop."
			if ((reference && reference.nodeType == $_.Node.TEXT_NODE && !isWhitespaceNode(reference)) || isNamedHtmlElement(reference, 'img')) {
				break;
			}
		}

		// "Return false."
		return false;
	}

	// "Something is visible if it is a node that either is a block node, or a Text
	// node that is not a collapsed whitespace node, or an img, or a br that is not
	// an extraneous line break, or any node with a visible descendant; excluding
	// any node with an ancestor container Element whose "display" property has
	// resolved value "none"."
	function isVisible(node) {
		var i;

		if (!node) {
			return false;
		}

		if ($_(getAncestors(node).concat(node))
			    .filter(function (node) { return node.nodeType == $_.Node.ELEMENT_NODE; }, true)
			    .some(function (node) { return $_.getComputedStyle(node).display == "none"; })) {
			return false;
		}

		if (isBlockNode(node) || (node.nodeType == $_.Node.TEXT_NODE && !isCollapsedWhitespaceNode(node)) || isNamedHtmlElement(node, 'img') || (isNamedHtmlElement(node, 'br') && !isExtraneousLineBreak(node))) {
			return true;
		}

		for (i = 0; i < node.childNodes.length; i++) {
			if (isVisible(node.childNodes[i])) {
				return true;
			}
		}

		return false;
	}

	// "Something is invisible if it is a node that is not visible."
	function isInvisible(node) {
		return node && !isVisible(node);
	}

	// "A collapsed block prop is either a collapsed line break that is not an
	// extraneous line break, or an Element that is an inline node and whose
	// children are all either invisible or collapsed block props and that has at
	// least one child that is a collapsed block prop."
	function isCollapsedBlockProp(node) {
		var i;

		if (isCollapsedLineBreak(node) && !isExtraneousLineBreak(node)) {
			return true;
		}

		if (!isInlineNode(node) || node.nodeType != $_.Node.ELEMENT_NODE) {
			return false;
		}

		var hasCollapsedBlockPropChild = false;
		for (i = 0; i < node.childNodes.length; i++) {
			if (!isInvisible(node.childNodes[i]) && !isCollapsedBlockProp(node.childNodes[i])) {
				return false;
			}
			if (isCollapsedBlockProp(node.childNodes[i])) {
				hasCollapsedBlockPropChild = true;
			}
		}

		return hasCollapsedBlockPropChild;
	}

	/**
	 * Checks whether the given node is a visible text node.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if `node` is a visible text node.
	 */
	function isInvisibleTextNode(node) {
		if (node && node.nodeType !== $_.Node.TEXT_NODE) {
			return false;
		}
		var offset = 0;
		var data = node.data;
		var len = data.length;
		while (offset < len && data.charAt(offset) === '\u200b') {
			offset++;
		}
		return offset === len;
	}

	/**
	 * Complement of isInvisibleTextNode().
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if `node` is anything but an invisible text node.
	 */
	function isNotInvisibleTextNode(node) {
		return !isInvisibleTextNode(node);
	}

	/**
	 * Checks whether the given node is a otherwise empty block-level element
	 * containing a propping <br> element.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if `node` is a propped up block-level element.
	 */
	function isProppedBlock(node) {
		if (!Html.isBlock(node)) {
			return false;
		}
		var child = Html.findNodeRight(node.lastChild, isVisible);
		return (
			child
			&& 'br' === child.nodeName.toLowerCase()
			&& !Html.findNodeRight(child.previousSibling, isVisible)
		);
	}

	/**
	 * Checks whether the given node is a empty element, or an element that
	 * would otherwise be empty except for a propping <br>, or an element
	 * containing only invisible text nodes.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if `node` can be considered empty.
	 */
	function isEmptyNode(node) {
		return (
			!node.hasChildNodes()
			|| isProppedBlock(node)
			|| !Html.findNodeRight(node.lastChild, isNotInvisibleTextNode)
		);
	}

	/**
	 * Check if the given node is a empty element which is the only
	 * immediate child of a editing host.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if `node` can be regarded as empty and the
	 *                   only immediate child of its parent editing host.
	 */
	function isEmptyOnlyChildOfEditingHost(node) {
		return (
			node
				&& isEmptyNode(node)
					&& isEditingHost(node.parentNode)
						&& !node.previousSibling
							&& !node.nextSibling
		);
	}

	/**
	 * Remove the given node and return the position from where it was
	 * removed.
	 *
	 * @param {HTMLElement} node Element to remove from DOM
	 * @return {object} Object containing node and offset index.
	 */
	function removeNode(node) {
		var ancestor = node.parentNode;
		var offset = Dom.getIndexInParent(node);
		ancestor.removeChild(node);
		return {
			node: ancestor,
			offset: offset
		};
	}

	// Please note: This method is deprecated and will be removed.
	// Every command should use the value and range parameter.
	//
	// "The active range is the first range in the Selection given by calling
	// getSelection() on the context object, or null if there is no such range."
	//
	// We cheat and return globalRange if that's defined.  We also ensure that the
	// active range meets the requirements that selection boundary points are
	// supposed to meet, i.e., that the nodes are both Text or Element nodes that
	// descend from a Document.
	function getActiveRange() {
		var ret;
		if (globalRange) {
			ret = globalRange;
		} else if (Aloha.getSelection().rangeCount) {
			ret = Aloha.getSelection().getRangeAt(0);
		} else {
			return null;
		}
		if (jQuery.inArray(ret.startContainer.nodeType, [$_.Node.TEXT_NODE, $_.Node.ELEMENT_NODE]) == -1 || jQuery.inArray(ret.endContainer.nodeType, [$_.Node.TEXT_NODE, $_.Node.ELEMENT_NODE]) == -1 || !ret.startContainer.ownerDocument || !ret.endContainer.ownerDocument || !isDescendant(ret.startContainer, ret.startContainer.ownerDocument) || !isDescendant(ret.endContainer, ret.endContainer.ownerDocument)) {
			throw "Invalid active range; test bug?";
		}
		return ret;
	}

	// "For some commands, each HTMLDocument must have a boolean state override
	// and/or a string value override. These do not change the command's state or
	// value, but change the way some algorithms behave, as specified in those
	// algorithms' definitions. Initially, both must be unset for every command.
	// Whenever the number of ranges in the Selection changes to something
	// different, and whenever a boundary point of the range at a given index in
	// the Selection changes to something different, the state override and value
	// override must be unset for every command."
	//
	// We implement this crudely by using setters and getters.  To verify that the
	// selection hasn't changed, we copy the active range and just check the
	// endpoints match.  This isn't really correct, but it's good enough for us.
	// Unset state/value overrides are undefined.  We put everything in a function
	// so no one can access anything except via the provided functions, since
	// otherwise callers might mistakenly use outdated overrides (if the selection
	// has changed).
	(function () {
		var stateOverrides = {};
		var valueOverrides = {};
		var storedRange = null;

		resetOverrides = function (range) {
			if (!storedRange
				    || storedRange.startContainer != range.startContainer
				    || storedRange.endContainer != range.endContainer
				    || storedRange.startOffset != range.startOffset
				    || storedRange.endOffset != range.endOffset) {
				storedRange = {
					startContainer: range.startContainer,
					endContainer: range.endContainer,
					startOffset: range.startOffset,
					endOffset: range.endOffset
				};
				if (!Maps.isEmpty(stateOverrides) || !Maps.isEmpty(valueOverrides)) {
					stateOverrides = {};
					valueOverrides = {};
					return true;
				}
			}
			return false;
		};

		getStateOverride = function (command, range) {
			resetOverrides(range);
			return stateOverrides[command];
		};

		setStateOverride = function (command, newState, range) {
			resetOverrides(range);
			stateOverrides[command] = newState;
		};

		unsetStateOverride = function (command, range) {
			resetOverrides(range);
			delete stateOverrides[command];
		};

		getValueOverride = function (command, range) {
			resetOverrides(range);
			return valueOverrides[command];
		};

		// "The value override for the backColor command must be the same as the
		// value override for the hiliteColor command, such that setting one sets
		// the other to the same thing and unsetting one unsets the other."
		setValueOverride = function (command, newValue, range) {
			resetOverrides(range);
			valueOverrides[command] = newValue;
			if (command == "backcolor") {
				valueOverrides.hilitecolor = newValue;
			} else if (command == "hilitecolor") {
				valueOverrides.backcolor = newValue;
			}
		};

		unsetValueOverride = function (command, range) {
			resetOverrides(range);
			delete valueOverrides[command];
			if (command == "backcolor") {
				delete valueOverrides.hilitecolor;
			} else if (command == "hilitecolor") {
				delete valueOverrides.backcolor;
			}
		};
	}());

	//@}

	/////////////////////////////
	///// Common algorithms /////
	/////////////////////////////

	///// Assorted common algorithms /////
	//@{

	function movePreservingRanges(node, newParent, newIndex, range) {
		// For convenience, I allow newIndex to be -1 to mean "insert at the end".
		if (newIndex == -1) {
			newIndex = newParent.childNodes.length;
		}

		// "When the user agent is to move a Node to a new location, preserving
		// ranges, it must remove the Node from its original parent (if any), then
		// insert it in the new location. In doing so, however, it must ignore the
		// regular range mutation rules, and instead follow these rules:"

		// "Let node be the moved Node, old parent and old index be the old parent
		// (which may be null) and index, and new parent and new index be the new
		// parent and index."
		var oldParent = node.parentNode;
		var oldIndex = Dom.getIndexInParent(node);
		var i;

		// We only even attempt to preserve the global range object and the ranges
		// in the selection, not every range out there (the latter is probably
		// impossible).
		var ranges = [range];
		for (i = 0; i < Aloha.getSelection().rangeCount; i++) {
			ranges.push(Aloha.getSelection().getRangeAt(i));
		}
		var boundaryPoints = [];
		$_(ranges).forEach(function (range) {
			boundaryPoints.push([range.startContainer, range.startOffset]);
			boundaryPoints.push([range.endContainer, range.endOffset]);
		});

		$_(boundaryPoints).forEach(function (boundaryPoint) {
			// "If a boundary point's node is the same as or a descendant of node,
			// leave it unchanged, so it moves to the new location."
			//
			// No modifications necessary.

			// "If a boundary point's node is new parent and its offset is greater
			// than new index, add one to its offset."
			if (boundaryPoint[0] == newParent && boundaryPoint[1] > newIndex) {
				boundaryPoint[1]++;
			}

			// "If a boundary point's node is old parent and its offset is old index or
			// old index + 1, set its node to new parent and add new index − old index
			// to its offset."
			if (boundaryPoint[0] == oldParent && (boundaryPoint[1] == oldIndex || boundaryPoint[1] == oldIndex + 1)) {
				boundaryPoint[0] = newParent;
				boundaryPoint[1] += newIndex - oldIndex;
			}

			// "If a boundary point's node is old parent and its offset is greater than
			// old index + 1, subtract one from its offset."
			if (boundaryPoint[0] == oldParent && boundaryPoint[1] > oldIndex + 1) {
				boundaryPoint[1]--;
			}
		});

		// Now actually move it and preserve the ranges.
		if (newParent.childNodes.length == newIndex) {
			newParent.appendChild(node);
		} else {
			newParent.insertBefore(node, newParent.childNodes[newIndex]);
		}

		// if we're off actual node boundaries this implies that the move was
		// part of a deletion process (backspace). If that's the case we
		// attempt to fix this by restoring the range to the first index of
		// the node that has been moved
		var newRange = null;
		if (boundaryPoints[0][1] > boundaryPoints[0][0].childNodes.length && boundaryPoints[1][1] > boundaryPoints[1][0].childNodes.length) {
			range.setStart(node, 0);
			range.setEnd(node, 0);
		} else {
			range.setStart(boundaryPoints[0][0], boundaryPoints[0][1]);
			range.setEnd(boundaryPoints[1][0], boundaryPoints[1][1]);

			Aloha.getSelection().removeAllRanges();
			for (i = 1; i < ranges.length; i++) {
				newRange = Aloha.createRange();
				newRange.setStart(boundaryPoints[2 * i][0], boundaryPoints[2 * i][1]);
				newRange.setEnd(boundaryPoints[2 * i + 1][0], boundaryPoints[2 * i + 1][1]);
				Aloha.getSelection().addRange(newRange);
			}
			if (newRange) {
				range = newRange;
			}
		}
	}

	/**
	 * Copy all non empty attributes from an existing to a new element
	 *
	 * @param {dom} element The source DOM element
	 * @param {dom} newElement The new DOM element which will get the attributes of the source DOM element
	 * @return void
	 */
	function copyAttributes(element, newElement) {

		// This is an IE7 workaround. We identified three places that were connected
		// to the mysterious ie7 crash:
		// 1. Add attribute to dom element (Initialization of jquery-ui sortable)
		// 2. Access the jquery expando attribute. Just reading the name is
		//    sufficient to make the browser vulnerable for the crash (Press enter)
		// 3. On editable blur the Aloha.editables[0].getContents(); gets invoked.
		//    This invokation somehow crashes the ie7. We assume that the access of
		//    shared expando attribute updates internal references which are not
		//    correclty handled during clone();
		if (Aloha.browser.msie && Aloha.browser.version >= 7 && typeof element.attributes[jQuery.expando] !== 'undefined') {
			jQuery(element).removeAttr(jQuery.expando);
		}

		var attrs = element.attributes;
		var i;
		for (i = 0; i < attrs.length; i++) {
			var attr = attrs[i];
			// attr.specified is an IE specific check to exclude attributes that were never really set.
			if (typeof attr.specified === "undefined" || attr.specified) {
				if (typeof newElement.setAttributeNS === 'function') {
					newElement.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
				} else {
					// fixes https://github.com/alohaeditor/Aloha-Editor/issues/515
					newElement.setAttribute(attr.name, attr.value);
				}
			}
		}
	}

	function setTagName(element, newName, range) {
		// "If element is an HTML element with local name equal to new name, return
		// element."
		if (isNamedHtmlElement(element, newName)) {
			return element;
		}

		// "If element's parent is null, return element."
		if (!element.parentNode) {
			return element;
		}

		// "Let replacement element be the result of calling createElement(new
		// name) on the ownerDocument of element."
		var replacementElement = element.ownerDocument.createElement(newName);

		// "Insert replacement element into element's parent immediately before
		// element."
		element.parentNode.insertBefore(replacementElement, element);

		// "Copy all attributes of element to replacement element, in order."
		copyAttributes(element, replacementElement);

		// "While element has children, append the first child of element as the
		// last child of replacement element, preserving ranges."
		while (element.childNodes.length) {
			movePreservingRanges(element.firstChild, replacementElement, replacementElement.childNodes.length, range);
		}

		// "Remove element from its parent."
		element.parentNode.removeChild(element);

		// if the range still uses the old element, we modify it to the new one
		if (range.startContainer === element) {
			range.startContainer = replacementElement;
		}
		if (range.endContainer === element) {
			range.endContainer = replacementElement;
		}

		// "Return replacement element."
		return replacementElement;
	}

	function removeExtraneousLineBreaksBefore(node) {
		// "Let ref be the previousSibling of node."
		var ref = node.previousSibling;

		// "If ref is null, abort these steps."
		if (!ref) {
			return;
		}

		// "While ref has children, set ref to its lastChild."
		while (ref.hasChildNodes()) {
			ref = ref.lastChild;
		}

		// "While ref is invisible but not an extraneous line break, and ref does
		// not equal node's parent, set ref to the node before it in tree order."
		while (isInvisible(ref) && !isExtraneousLineBreak(ref) && ref != node.parentNode) {
			ref = previousNode(ref);
		}

		// "If ref is an editable extraneous line break, remove it from its
		// parent."
		if (isEditable(ref) && isExtraneousLineBreak(ref)) {
			ref.parentNode.removeChild(ref);
		}
	}

	function removeExtraneousLineBreaksAtTheEndOf(node) {
		// "Let ref be node."
		var ref = node;

		// "While ref has children, set ref to its lastChild."
		while (ref.hasChildNodes()) {
			ref = ref.lastChild;
		}

		// "While ref is invisible but not an extraneous line break, and ref does
		// not equal node, set ref to the node before it in tree order."
		while (isInvisible(ref) && !isExtraneousLineBreak(ref) && ref != node) {
			ref = previousNode(ref);
		}

		// "If ref is an editable extraneous line break, remove it from its
		// parent."
		if (isEditable(ref) && isExtraneousLineBreak(ref)) {
			ref.parentNode.removeChild(ref);
		}
	}

	// "To remove extraneous line breaks from a node, first remove extraneous line
	// breaks before it, then remove extraneous line breaks at the end of it."
	function removeExtraneousLineBreaksFrom(node) {
		removeExtraneousLineBreaksBefore(node);
		removeExtraneousLineBreaksAtTheEndOf(node);
	}

	//@}
	///// Wrapping a list of nodes /////
	//@{

	function wrap(nodeList, siblingCriteria, newParentInstructions, range) {
		var i;

		// "If not provided, sibling criteria returns false and new parent
		// instructions returns null."
		if (typeof siblingCriteria == "undefined") {
			siblingCriteria = function () {
				return false;
			};
		}
		if (typeof newParentInstructions == "undefined") {
			newParentInstructions = function () {
				return null;
			};
		}

		// "If node list is empty, or the first member of node list is not
		// editable, return null and abort these steps."
		if (!nodeList.length || !isEditable(nodeList[0])) {
			return null;
		}

		// "If node list's last member is an inline node that's not a br, and node
		// list's last member's nextSibling is a br, append that br to node list."
		if (isInlineNode(nodeList[nodeList.length - 1]) && !isNamedHtmlElement(nodeList[nodeList.length - 1], "br") && isNamedHtmlElement(nodeList[nodeList.length - 1].nextSibling, "br")) {
			nodeList.push(nodeList[nodeList.length - 1].nextSibling);
		}

		// "If the previousSibling of the first member of node list is editable and
		// running sibling criteria on it returns true, let new parent be the
		// previousSibling of the first member of node list."
		var newParent;
		if (isEditable(nodeList[0].previousSibling) && siblingCriteria(nodeList[0].previousSibling)) {
			newParent = nodeList[0].previousSibling;

			// "Otherwise, if the nextSibling of the last member of node list is
			// editable and running sibling criteria on it returns true, let new parent
			// be the nextSibling of the last member of node list."
		} else if (isEditable(nodeList[nodeList.length - 1].nextSibling) && siblingCriteria(nodeList[nodeList.length - 1].nextSibling)) {
			newParent = nodeList[nodeList.length - 1].nextSibling;

			// "Otherwise, run new parent instructions, and let new parent be the
			// result."
		} else {
			newParent = newParentInstructions();
		}

		// "If new parent is null, abort these steps and return null."
		if (!newParent) {
			return null;
		}

		// "If new parent's parent is null:"
		if (!newParent.parentNode) {
			// "Insert new parent into the parent of the first member of node list
			// immediately before the first member of node list."
			nodeList[0].parentNode.insertBefore(newParent, nodeList[0]);

			// "If any range has a boundary point with node equal to the parent of
			// new parent and offset equal to the index of new parent, add one to
			// that boundary point's offset."
			//
			// Try to fix range
			var startContainer = range.startContainer,
				startOffset = range.startOffset,
				endContainer = range.endContainer,
				endOffset = range.endOffset,
			    newParentIndex = Dom.getIndexInParent(newParent);

			if (startOffset >= newParentIndex && startContainer == newParent.parentNode) {
				range.setStart(startContainer, startOffset + 1);
			}
			if (endOffset >= newParentIndex && endContainer == newParent.parentNode) {
				range.setEnd(endContainer, endOffset + 1);
			}

			// Only try to fix the global range. TODO remove globalRange here
			if (globalRange && globalRange !== range) {
				startContainer = globalRange.startContainer;
				startOffset = globalRange.startOffset;
				endContainer = globalRange.endContainer;
				endOffset = globalRange.endOffset;
				if (startContainer == newParent.parentNode && startOffset >= newParentIndex) {
					globalRange.setStart(startContainer, startOffset + 1);
				}
				if (endContainer == newParent.parentNode && endOffset >= newParentIndex) {
					globalRange.setEnd(endContainer, endOffset + 1);
				}
			}
		}

		// "Let original parent be the parent of the first member of node list."
		var originalParent = nodeList[0].parentNode;

		// "If new parent is before the first member of node list in tree order:"
		if (isBefore(newParent, nodeList[0])) {
			// "If new parent is not an inline node, but the last child of new
			// parent and the first member of node list are both inline nodes, and
			// the last child of new parent is not a br, call createElement("br")
			// on the ownerDocument of new parent and append the result as the last
			// child of new parent."
			if (!isInlineNode(newParent) && isInlineNode(newParent.lastChild) && isInlineNode(nodeList[0]) && !isNamedHtmlElement(newParent.lastChild, "BR")) {
				newParent.appendChild(newParent.ownerDocument.createElement("br"));
			}

			// "For each node in node list, append node as the last child of new
			// parent, preserving ranges."
			for (i = 0; i < nodeList.length; i++) {
				movePreservingRanges(nodeList[i], newParent, -1, range);
			}

			// "Otherwise:"
		} else {
			// "If new parent is not an inline node, but the first child of new
			// parent and the last member of node list are both inline nodes, and
			// the last member of node list is not a br, call createElement("br")
			// on the ownerDocument of new parent and insert the result as the
			// first child of new parent."
			if (!isInlineNode(newParent) && isInlineNode(newParent.firstChild) && isInlineNode(nodeList[nodeList.length - 1]) && !isNamedHtmlElement(nodeList[nodeList.length - 1], "BR")) {
				newParent.insertBefore(newParent.ownerDocument.createElement("br"), newParent.firstChild);
			}

			// "For each node in node list, in reverse order, insert node as the
			// first child of new parent, preserving ranges."
			for (i = nodeList.length - 1; i >= 0; i--) {
				movePreservingRanges(nodeList[i], newParent, 0, range);
			}
		}

		// "If original parent is editable and has no children, remove it from its
		// parent."
		if (isEditable(originalParent) && !originalParent.hasChildNodes()) {
			originalParent.parentNode.removeChild(originalParent);
		}

		// "If new parent's nextSibling is editable and running sibling criteria on
		// it returns true:"
		if (isEditable(newParent.nextSibling) && siblingCriteria(newParent.nextSibling)) {
			// "If new parent is not an inline node, but new parent's last child
			// and new parent's nextSibling's first child are both inline nodes,
			// and new parent's last child is not a br, call createElement("br") on
			// the ownerDocument of new parent and append the result as the last
			// child of new parent."
			if (!isInlineNode(newParent) && isInlineNode(newParent.lastChild) && isInlineNode(newParent.nextSibling.firstChild) && !isNamedHtmlElement(newParent.lastChild, "BR")) {
				newParent.appendChild(newParent.ownerDocument.createElement("br"));
			}

			// "While new parent's nextSibling has children, append its first child
			// as the last child of new parent, preserving ranges."
			while (newParent.nextSibling.hasChildNodes()) {
				movePreservingRanges(newParent.nextSibling.firstChild, newParent, -1, range);
			}

			// "Remove new parent's nextSibling from its parent."
			newParent.parentNode.removeChild(newParent.nextSibling);
		}

		// "Remove extraneous line breaks from new parent."
		removeExtraneousLineBreaksFrom(newParent);

		// "Return new parent."
		return newParent;
	}


	//@}
	///// Allowed children /////
	//@{

	// "A name of an element with inline contents is "a", "abbr", "b", "bdi",
	// "bdo", "cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",
	// "kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small",
	// "span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",
	// "xmp", "big", "blink", "font", "marquee", "nobr", or "tt"."
	var namesOfElementsWithInlineContentsMap = {
		"A": true,
		"ABBR": true,
		"B": true,
		"BDI": true,
		"BDO": true,
		"CITE": true,
		"CODE": true,
		"DFN": true,
		"EM": true,
		"H1": true,
		"H2": true,
		"H3": true,
		"H4": true,
		"H5": true,
		"H6": true,
		"I": true,
		"KBD": true,
		"MARK": true,
		"P": true,
		"PRE": true,
		"Q": true,
		"RP": true,
		"RT": true,
		"RUBY": true,
		"S": true,
		"SAMP": true,
		"SMALL": true,
		"SPAN": true,
		"STRONG": true,
		"SUB": true,
		"SUP": true,
		"U": true,
		"VAR": true,
		"ACRONYM": true,
		"LISTING": true,
		"STRIKE": true,
		"XMP": true,
		"BIG": true,
		"BLINK": true,
		"FONT": true,
		"MARQUEE": true,
		"NOBR": true,
		"TT": true
	};


	var tableRelatedElements = {
		"colgroup": true,
		"table": true,
		"tbody": true,
		"tfoot": true,
		"thead": true,
		"tr": true
	};

	var scriptRelatedElements = {
		"script": true,
		"style": true,
		"plaintext": true,
		"xmp": true
	};

	var prohibitedHeadingNestingMap = jQuery.extend({
		"H1": true,
		"H2": true,
		"H3": true,
		"H4": true,
		"H5": true,
		"H6": true
	}, prohibitedParagraphChildNamesMap);
	var prohibitedTableNestingMap = {
		"CAPTION": true,
		"COL": true,
		"COLGROUP": true,
		"TBODY": true,
		"TD": true,
		"TFOOT": true,
		"TH": true,
		"THEAD": true,
		"TR": true
	};
	var prohibitedDefNestingMap = {
		"DD": true,
		"DT": true
	};
	var prohibitedNestingCombinationsMap = {
		"A": jQuery.extend({
			"A": true
		}, prohibitedParagraphChildNamesMap),
		"DD": prohibitedDefNestingMap,
		"DT": prohibitedDefNestingMap,
		"LI": {
			"LI": true
		},
		"NOBR": jQuery.extend({
			"NOBR": true
		}, prohibitedParagraphChildNamesMap),
		"H1": prohibitedHeadingNestingMap,
		"H2": prohibitedHeadingNestingMap,
		"H3": prohibitedHeadingNestingMap,
		"H4": prohibitedHeadingNestingMap,
		"H5": prohibitedHeadingNestingMap,
		"H6": prohibitedHeadingNestingMap,
		"TD": prohibitedTableNestingMap,
		"TH": prohibitedTableNestingMap,
		// this is the same as namesOfElementsWithInlineContentsMap excluding a and h1-h6 elements above
		"ABBR": prohibitedParagraphChildNamesMap,
		"B": prohibitedParagraphChildNamesMap,
		"BDI": prohibitedParagraphChildNamesMap,
		"BDO": prohibitedParagraphChildNamesMap,
		"CITE": prohibitedParagraphChildNamesMap,
		"CODE": prohibitedParagraphChildNamesMap,
		"DFN": prohibitedParagraphChildNamesMap,
		"EM": prohibitedParagraphChildNamesMap,
		"I": prohibitedParagraphChildNamesMap,
		"KBD": prohibitedParagraphChildNamesMap,
		"MARK": prohibitedParagraphChildNamesMap,
		"P": prohibitedParagraphChildNamesMap,
		"PRE": prohibitedParagraphChildNamesMap,
		"Q": prohibitedParagraphChildNamesMap,
		"RP": prohibitedParagraphChildNamesMap,
		"RT": prohibitedParagraphChildNamesMap,
		"RUBY": prohibitedParagraphChildNamesMap,
		"S": prohibitedParagraphChildNamesMap,
		"SAMP": prohibitedParagraphChildNamesMap,
		"SMALL": prohibitedParagraphChildNamesMap,
		"SPAN": prohibitedParagraphChildNamesMap,
		"STRONG": prohibitedParagraphChildNamesMap,
		"SUB": prohibitedParagraphChildNamesMap,
		"SUP": prohibitedParagraphChildNamesMap,
		"U": prohibitedParagraphChildNamesMap,
		"VAR": prohibitedParagraphChildNamesMap,
		"ACRONYM": prohibitedParagraphChildNamesMap,
		"LISTING": prohibitedParagraphChildNamesMap,
		"STRIKE": prohibitedParagraphChildNamesMap,
		"XMP": prohibitedParagraphChildNamesMap,
		"BIG": prohibitedParagraphChildNamesMap,
		"BLINK": prohibitedParagraphChildNamesMap,
		"FONT": prohibitedParagraphChildNamesMap,
		"MARQUEE": prohibitedParagraphChildNamesMap,
		"TT": prohibitedParagraphChildNamesMap
	};

	// "An element with inline contents is an HTML element whose local name is a
	// name of an element with inline contents."
	function isElementWithInlineContents(node) {
		return isMappedHtmlElement(node, namesOfElementsWithInlineContentsMap);
	}

	function isAllowedChild(child, parent_) {
		// "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
		// an HTML element with local name equal to one of those, and child is a
		// Text node whose data does not consist solely of space characters, return
		// false."
		if ((tableRelatedElements[parent_] || isHtmlElementInArray(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"])) && typeof child == "object" && child.nodeType == $_.Node.TEXT_NODE && !/^[ \t\n\f\r]*$/.test(child.data)) {
			return false;
		}

		// "If parent is "script", "style", "plaintext", or "xmp", or an HTML
		// element with local name equal to one of those, and child is not a Text
		// node, return false."
		if ((scriptRelatedElements[parent_] || isHtmlElementInArray(parent_, ["script", "style", "plaintext", "xmp"])) && (typeof child != "object" || child.nodeType != $_.Node.TEXT_NODE)) {
			return false;
		}

		// "If child is a Document, DocumentFragment, or DocumentType, return
		// false."
		if (typeof child == "object" && (child.nodeType == $_.Node.DOCUMENT_NODE || child.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE || child.nodeType == $_.Node.DOCUMENT_TYPE_NODE)) {
			return false;
		}

		// "If child is an HTML element, set child to the local name of child."
		if (isAnyHtmlElement(child)) {
			child = child.tagName.toLowerCase();
		}

		// "If child is not a string, return true."
		if (typeof child != "string") {
			return true;
		}

		// "If parent is an HTML element:"
		if (isAnyHtmlElement(parent_)) {
			// "If child is "a", and parent or some ancestor of parent is an a,
			// return false."
			//
			// "If child is a prohibited paragraph child name and parent or some
			// ancestor of parent is an element with inline contents, return
			// false."
			//
			// "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
			// some ancestor of parent is an HTML element with local name "h1",
			// "h2", "h3", "h4", "h5", or "h6", return false."
			var ancestor = parent_;
			while (ancestor) {
				if (child == "a" && isNamedHtmlElement(ancestor, 'a')) {
					return false;
				}
				if (prohibitedParagraphChildNamesMap[child.toUpperCase()] && isElementWithInlineContents(ancestor)) {
					return false;
				}
				if (/^h[1-6]$/.test(child) && isAnyHtmlElement(ancestor) && /^H[1-6]$/.test(ancestor.tagName)) {
					return false;
				}
				ancestor = ancestor.parentNode;
			}

			// "Let parent be the local name of parent."
			parent_ = parent_.tagName.toLowerCase();
		}

		// "If parent is an Element or DocumentFragment, return true."
		if (typeof parent_ == "object" && (parent_.nodeType == $_.Node.ELEMENT_NODE || parent_.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE)) {
			return true;
		}

		// "If parent is not a string, return false."
		if (typeof parent_ != "string") {
			return false;
		}

		// "If parent is on the left-hand side of an entry on the following list,
		// then return true if child is listed on the right-hand side of that
		// entry, and false otherwise."
		switch (parent_) {
		case "colgroup":
			return child == "col";
		case "table":
			return jQuery.inArray(child, ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]) != -1;
		case "tbody":
		case "thead":
		case "tfoot":
			return jQuery.inArray(child, ["td", "th", "tr"]) != -1;
		case "tr":
			return jQuery.inArray(child, ["td", "th"]) != -1;
		case "dl":
			return jQuery.inArray(child, ["dt", "dd"]) != -1;
		case "dir":
		case "ol":
		case "ul":
			return jQuery.inArray(child, ["dir", "li", "ol", "ul"]) != -1;
		case "hgroup":
			return (/^h[1-6]$/).test(child);
		}

		// "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
		// "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
		// false."
		if (jQuery.inArray(child, ["body", "caption", "col", "colgroup", "frame", "frameset", "head", "html", "tbody", "td", "tfoot", "th", "thead", "tr"]) != -1) {
			return false;
		}

		// "If child is "dd" or "dt" and parent is not "dl", return false."
		if (jQuery.inArray(child, ["dd", "dt"]) != -1 && parent_ != "dl") {
			return false;
		}

		// "If child is "li" and parent is not "ol" or "ul", return false."
		if (child == "li" && parent_ != "ol" && parent_ != "ul") {
			return false;
		}

		// "If parent is on the left-hand side of an entry on the following list
		// and child is listed on the right-hand side of that entry, return false."
		var leftSide = prohibitedNestingCombinationsMap[parent_.toUpperCase()];
		if (leftSide) {
			var rightSide = leftSide[child.toUpperCase()];
			if (rightSide) {
				return false;
			}
		}

		// "Return true."
		return true;
	}


	//@}

	//////////////////////////////////////
	///// Inline formatting commands /////
	//////////////////////////////////////

	///// Inline formatting command definitions /////
	//@{

	// "A node node is effectively contained in a range range if range is not
	// collapsed, and at least one of the following holds:"
	function isEffectivelyContained(node, range) {
		if (range.collapsed) {
			return false;
		}

		// "node is contained in range."
		if (isContained(node, range)) {
			return true;
		}

		// "node is range's start node, it is a Text node, and its length is
		// different from range's start offset."
		if (node == range.startContainer && node.nodeType == $_.Node.TEXT_NODE && getNodeLength(node) != range.startOffset) {
			return true;
		}

		// "node is range's end node, it is a Text node, and range's end offset is
		// not 0."
		if (node == range.endContainer && node.nodeType == $_.Node.TEXT_NODE && range.endOffset != 0) {
			return true;
		}

		// "node has at least one child; and all its children are effectively
		// contained in range; and either range's start node is not a descendant of
		// node or is not a Text node or range's start offset is zero; and either
		// range's end node is not a descendant of node or is not a Text node or
		// range's end offset is its end node's length."
		if (node.hasChildNodes() && $_(node.childNodes).every(function (child) { return isEffectivelyContained(child, range); })
			    && (!isDescendant(range.startContainer, node)
					|| range.startContainer.nodeType != $_.Node.TEXT_NODE
					|| range.startOffset == 0)
			    && (!isDescendant(range.endContainer, node)
					|| range.endContainer.nodeType != $_.Node.TEXT_NODE
					|| range.endOffset == getNodeLength(range.endContainer))) {
			return true;
		}

		return false;
	}

	// Like get(All)ContainedNodes(), but for effectively contained nodes.
	function getEffectivelyContainedNodes(range, condition) {
		if (typeof condition == "undefined") {
			condition = function () {
				return true;
			};
		}
		var node = range.startContainer;
		while (isEffectivelyContained(node.parentNode, range)) {
			node = node.parentNode;
		}

		var stop = nextNodeDescendants(range.endContainer);

		var nodeList = [];
		while (isBefore(node, stop)) {
			if (isEffectivelyContained(node, range) && condition(node)) {
				nodeList.push(node);
				node = nextNodeDescendants(node);
				continue;
			}
			node = nextNode(node);
		}
		return nodeList;
	}

	function getAllEffectivelyContainedNodes(range, condition) {
		if (typeof condition == "undefined") {
			condition = function () {
				return true;
			};
		}
		var node = range.startContainer;
		while (isEffectivelyContained(node.parentNode, range)) {
			node = node.parentNode;
		}

		var stop = nextNodeDescendants(range.endContainer);

		var nodeList = [];
		while (isBefore(node, stop)) {
			if (isEffectivelyContained(node, range) && condition(node)) {
				nodeList.push(node);
			}
			node = nextNode(node);
		}
		return nodeList;
	}

	// "A modifiable element is a b, em, i, s, span, strong, sub, sup, or u element
	// with no attributes except possibly style; or a font element with no
	// attributes except possibly style, color, face, and/or size; or an a element
	// with no attributes except possibly style and/or href."
	function isModifiableElement(node) {
		if (!isAnyHtmlElement(node)) {
			return false;
		}

		if (jQuery.inArray(node.tagName, ["B", "EM", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"]) != -1) {
			if (node.attributes.length == 0) {
				return true;
			}

			if (node.attributes.length == 1 && hasAttribute(node, "style")) {
				return true;
			}
		}

		if (node.tagName == "FONT" || node.tagName == "A") {
			var numAttrs = node.attributes.length;

			if (hasAttribute(node, "style")) {
				numAttrs--;
			}

			if (node.tagName == "FONT") {
				if (hasAttribute(node, "color")) {
					numAttrs--;
				}

				if (hasAttribute(node, "face")) {
					numAttrs--;
				}

				if (hasAttribute(node, "size")) {
					numAttrs--;
				}
			}

			if (node.tagName == "A" && hasAttribute(node, "href")) {
				numAttrs--;
			}

			if (numAttrs == 0) {
				return true;
			}
		}

		return false;
	}

	function isSimpleModifiableElement(node) {
		// "A simple modifiable element is an HTML element for which at least one
		// of the following holds:"
		if (!isAnyHtmlElement(node)) {
			return false;
		}

		// Only these elements can possibly be a simple modifiable element.
		if (jQuery.inArray(node.tagName, ["A", "B", "EM", "FONT", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"]) == -1) {
			return false;
		}

		// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
		// element with no attributes."
		if (node.attributes.length == 0) {
			return true;
		}

		// If it's got more than one attribute, everything after this fails.
		if (node.attributes.length > 1) {
			return false;
		}

		// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
		// element with exactly one attribute, which is style, which sets no CSS
		// properties (including invalid or unrecognized properties)."
		//
		// Not gonna try for invalid or unrecognized.
		if (hasAttribute(node, "style") && getStyleLength(node) == 0) {
			return true;
		}

		// "It is an a element with exactly one attribute, which is href."
		if (node.tagName == "A" && hasAttribute(node, "href")) {
			return true;
		}

		// "It is a font element with exactly one attribute, which is either color,
		// face, or size."
		if (node.tagName == "FONT" && (hasAttribute(node, "color") || hasAttribute(node, "face") || hasAttribute(node, "size"))) {
			return true;
		}

		// "It is a b or strong element with exactly one attribute, which is style,
		// and the style attribute sets exactly one CSS property (including invalid
		// or unrecognized properties), which is "font-weight"."
		if ((node.tagName == "B" || node.tagName == "STRONG") && hasAttribute(node, "style") && getStyleLength(node) == 1 && node.style.fontWeight != "") {
			return true;
		}

		// "It is an i or em element with exactly one attribute, which is style,
		// and the style attribute sets exactly one CSS property (including invalid
		// or unrecognized properties), which is "font-style"."
		if ((node.tagName == "I" || node.tagName == "EM") && hasAttribute(node, "style") && getStyleLength(node) == 1 && node.style.fontStyle != "") {
			return true;
		}

		// "It is an a, font, or span element with exactly one attribute, which is
		// style, and the style attribute sets exactly one CSS property (including
		// invalid or unrecognized properties), and that property is not
		// "text-decoration"."
		if ((node.tagName == "A" || node.tagName == "FONT" || node.tagName == "SPAN") && hasAttribute(node, "style") && getStyleLength(node) == 1 && node.style.textDecoration == "") {
			return true;
		}

		// "It is an a, font, s, span, strike, or u element with exactly one
		// attribute, which is style, and the style attribute sets exactly one CSS
		// property (including invalid or unrecognized properties), which is
		// "text-decoration", which is set to "line-through" or "underline" or
		// "overline" or "none"."
		if (jQuery.inArray(node.tagName, ["A", "FONT", "S", "SPAN", "STRIKE", "U"]) != -1 && hasAttribute(node, "style") && getStyleLength(node) == 1 && (node.style.textDecoration == "line-through" || node.style.textDecoration == "underline" || node.style.textDecoration == "overline" || node.style.textDecoration == "none")) {
			return true;
		}

		return false;
	}

	// "Two quantities are equivalent values for a command if either both are null,
	// or both are strings and they're equal and the command does not define any
	// equivalent values, or both are strings and the command defines equivalent
	// values and they match the definition."
	function areEquivalentValues(command, val1, val2) {
		if (val1 === null && val2 === null) {
			return true;
		}

		if (typeof val1 == "string" && typeof val2 == "string" && val1 == val2 && !(commands[command].hasOwnProperty("equivalentValues"))) {
			return true;
		}

		if (typeof val1 == "string" && typeof val2 == "string" && commands[command].hasOwnProperty("equivalentValues") && commands[command].equivalentValues(val1, val2)) {
			return true;
		}

		return false;
	}

	// "Two quantities are loosely equivalent values for a command if either they
	// are equivalent values for the command, or if the command is the fontSize
	// command; one of the quantities is one of "xx-small", "small", "medium",
	// "large", "x-large", "xx-large", or "xxx-large"; and the other quantity is
	// the resolved value of "font-size" on a font element whose size attribute has
	// the corresponding value set ("1" through "7" respectively)."
	function areLooselyEquivalentValues(command, val1, val2) {
		if (areEquivalentValues(command, val1, val2)) {
			return true;
		}

		if (command != "fontsize" || typeof val1 != "string" || typeof val2 != "string") {
			return false;
		}

		// Static variables in JavaScript?
		var callee = areLooselyEquivalentValues;
		if (callee.sizeMap === undefined) {
			callee.sizeMap = {};
			var font = document.createElement("font");
			document.body.appendChild(font);
			$_(["xx-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]).forEach(function (keyword) {
				font.size = cssSizeToLegacy(keyword);
				callee.sizeMap[keyword] = $_.getComputedStyle(font).fontSize;
			});
			document.body.removeChild(font);
		}

		return val1 === callee.sizeMap[val2] || val2 === callee.sizeMap[val1];
	}

	//@}
	///// Assorted inline formatting command algorithms /////
	//@{

	function getEffectiveCommandValue(node, command) {
		// "If neither node nor its parent is an Element, return null."
		if (node.nodeType != $_.Node.ELEMENT_NODE && (!node.parentNode || node.parentNode.nodeType != $_.Node.ELEMENT_NODE)) {
			return null;
		}

		// "If node is not an Element, return the effective command value of its
		// parent for command."
		if (node.nodeType != $_.Node.ELEMENT_NODE) {
			return getEffectiveCommandValue(node.parentNode, command);
		}

		// "If command is "createLink" or "unlink":"
		if (command == "createlink" || command == "unlink") {
			// "While node is not null, and is not an a element that has an href
			// attribute, set node to its parent."
			while (node && (!isAnyHtmlElement(node) || node.tagName != "A" || !hasAttribute(node, "href"))) {
				node = node.parentNode;
			}

			// "If node is null, return null."
			if (!node) {
				return null;
			}

			// "Return the value of node's href attribute."
			return node.getAttribute("href");
		}

		// "If command is "backColor" or "hiliteColor":"
		if (command == "backcolor" || command == "hilitecolor") {
			// "While the resolved value of "background-color" on node is any
			// fully transparent value, and node's parent is an Element, set
			// node to its parent."
			//
			// Another lame hack to avoid flawed APIs.
			while (($_.getComputedStyle(node).backgroundColor == "rgba(0, 0, 0, 0)" || $_.getComputedStyle(node).backgroundColor === "" || $_.getComputedStyle(node).backgroundColor == "transparent") && node.parentNode && node.parentNode.nodeType == $_.Node.ELEMENT_NODE) {
				node = node.parentNode;
			}

			// "If the resolved value of "background-color" on node is a fully
			// transparent value, return "rgb(255, 255, 255)"."
			if ($_.getComputedStyle(node).backgroundColor == "rgba(0, 0, 0, 0)" || $_.getComputedStyle(node).backgroundColor === "" || $_.getComputedStyle(node).backgroundColor == "transparent") {
				return "rgb(255, 255, 255)";
			}

			// "Otherwise, return the resolved value of "background-color" for
			// node."
			return $_.getComputedStyle(node).backgroundColor;
		}

		// "If command is "subscript" or "superscript":"
		if (command == "subscript" || command == "superscript") {
			// "Let affected by subscript and affected by superscript be two
			// boolean variables, both initially false."
			var affectedBySubscript = false;
			var affectedBySuperscript = false;

			// "While node is an inline node:"
			while (isInlineNode(node)) {
				var verticalAlign = $_.getComputedStyle(node).verticalAlign;

				// "If node is a sub, set affected by subscript to true."
				if (isNamedHtmlElement(node, 'sub')) {
					affectedBySubscript = true;
					// "Otherwise, if node is a sup, set affected by superscript to
					// true."
				} else if (isNamedHtmlElement(node, 'sup')) {
					affectedBySuperscript = true;
				}

				// "Set node to its parent."
				node = node.parentNode;
			}

			// "If affected by subscript and affected by superscript are both true,
			// return the string "mixed"."
			if (affectedBySubscript && affectedBySuperscript) {
				return "mixed";
			}

			// "If affected by subscript is true, return "subscript"."
			if (affectedBySubscript) {
				return "subscript";
			}

			// "If affected by superscript is true, return "superscript"."
			if (affectedBySuperscript) {
				return "superscript";
			}

			// "Return null."
			return null;
		}

		// "If command is "strikethrough", and the "text-decoration" property of
		// node or any of its ancestors has resolved value containing
		// "line-through", return "line-through". Otherwise, return null."
		if (command == "strikethrough") {
			do {
				if ($_.getComputedStyle(node).textDecoration.indexOf("line-through") != -1) {
					return "line-through";
				}
				node = node.parentNode;
			} while (node && node.nodeType == $_.Node.ELEMENT_NODE);
			return null;
		}

		// "If command is "underline", and the "text-decoration" property of node
		// or any of its ancestors has resolved value containing "underline",
		// return "underline". Otherwise, return null."
		if (command == "underline") {
			do {
				if ($_.getComputedStyle(node).textDecoration.indexOf("underline") != -1) {
					return "underline";
				}
				node = node.parentNode;
			} while (node && node.nodeType == $_.Node.ELEMENT_NODE);
			return null;
		}

		if (!commands[command].hasOwnProperty("relevantCssProperty")) {
			throw "Bug: no relevantCssProperty for " + command + " in getEffectiveCommandValue";
		}

		// "Return the resolved value for node of the relevant CSS property for
		// command."
		return $_.getComputedStyle(node)[commands[command].relevantCssProperty].toString();
	}

	function getSpecifiedCommandValue(element, command) {
		// "If command is "backColor" or "hiliteColor" and element's display
		// property does not have resolved value "inline", return null."
		if ((command == "backcolor" || command == "hilitecolor") && $_.getComputedStyle(element).display != "inline") {
			return null;
		}

		// "If command is "createLink" or "unlink":"
		if (command == "createlink" || command == "unlink") {
			// "If element is an a element and has an href attribute, return the
			// value of that attribute."
			if (isAnyHtmlElement(element) && element.tagName == "A" && hasAttribute(element, "href")) {
				return element.getAttribute("href");
			}

			// "Return null."
			return null;
		}

		// "If command is "subscript" or "superscript":"
		if (command == "subscript" || command == "superscript") {
			// "If element is a sup, return "superscript"."
			if (isNamedHtmlElement(element, 'sup')) {
				return "superscript";
			}

			// "If element is a sub, return "subscript"."
			if (isNamedHtmlElement(element, 'sub')) {
				return "subscript";
			}

			// "Return null."
			return null;
		}

		// "If command is "strikethrough", and element has a style attribute set,
		// and that attribute sets "text-decoration":"
		if (command == "strikethrough" && element.style.textDecoration != "") {
			// "If element's style attribute sets "text-decoration" to a value
			// containing "line-through", return "line-through"."
			if (element.style.textDecoration.indexOf("line-through") != -1) {
				return "line-through";
			}

			// "Return null."
			return null;
		}

		// "If command is "strikethrough" and element is a s or strike element,
		// return "line-through"."
		if (command == "strikethrough" && isHtmlElementInArray(element, ["S", "STRIKE"])) {
			return "line-through";
		}

		// "If command is "underline", and element has a style attribute set, and
		// that attribute sets "text-decoration":"
		if (command == "underline" && element.style.textDecoration != "") {
			// "If element's style attribute sets "text-decoration" to a value
			// containing "underline", return "underline"."
			if (element.style.textDecoration.indexOf("underline") != -1) {
				return "underline";
			}

			// "Return null."
			return null;
		}

		// "If command is "underline" and element is a u element, return
		// "underline"."
		if (command == "underline" && isNamedHtmlElement(element, 'U')) {
			return "underline";
		}

		// "Let property be the relevant CSS property for command."
		var property = commands[command].relevantCssProperty;

		// "If property is null, return null."
		if (property === null) {
			return null;
		}

		// "If element has a style attribute set, and that attribute has the
		// effect of setting property, return the value that it sets property to."
		if (element.style[property] != "") {
			return element.style[property];
		}

		// "If element is a font element that has an attribute whose effect is
		// to create a presentational hint for property, return the value that the
		// hint sets property to.  (For a size of 7, this will be the non-CSS value
		// "xxx-large".)"
		if (isHtmlNamespace(element.namespaceURI) && element.tagName == "FONT") {
			if (property == "color" && hasAttribute(element, "color")) {
				return element.color;
			}
			if (property == "fontFamily" && hasAttribute(element, "face")) {
				return element.face;
			}
			if (property == "fontSize" && hasAttribute(element, "size")) {
				// This is not even close to correct in general.
				var size = parseInt(element.size, 10);
				if (size < 1) {
					size = 1;
				}
				if (size > 7) {
					size = 7;
				}
				return {
					1: "xx-small",
					2: "small",
					3: "medium",
					4: "large",
					5: "x-large",
					6: "xx-large",
					7: "xxx-large"
				}[size];
			}
		}

		// "If element is in the following list, and property is equal to the
		// CSS property name listed for it, return the string listed for it."
		//
		// A list follows, whose meaning is copied here.
		if (property == "fontWeight" && (element.tagName == "B" || element.tagName == "STRONG")) {
			return "bold";
		}
		if (property == "fontStyle" && (element.tagName == "I" || element.tagName == "EM")) {
			return "italic";
		}

		// "Return null."
		return null;
	}

	function reorderModifiableDescendants(node, command, newValue, range) {
		// "Let candidate equal node."
		var candidate = node;

		// "While candidate is a modifiable element, and candidate has exactly one
		// child, and that child is also a modifiable element, and candidate is not
		// a simple modifiable element or candidate's specified command value for
		// command is not equivalent to new value, set candidate to its child."
		while (isModifiableElement(candidate) && candidate.childNodes.length == 1 && isModifiableElement(candidate.firstChild) && (!isSimpleModifiableElement(candidate) || !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue))) {
			candidate = candidate.firstChild;
		}

		// "If candidate is node, or is not a simple modifiable element, or its
		// specified command value is not equivalent to new value, or its effective
		// command value is not loosely equivalent to new value, abort these
		// steps."
		if (candidate == node || !isSimpleModifiableElement(candidate) || !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue) || !areLooselyEquivalentValues(command, getEffectiveCommandValue(candidate, command), newValue)) {
			return;
		}

		// "While candidate has children, insert the first child of candidate into
		// candidate's parent immediately before candidate, preserving ranges."
		while (candidate.hasChildNodes()) {
			movePreservingRanges(candidate.firstChild, candidate.parentNode, Dom.getIndexInParent(candidate), range);
		}

		// "Insert candidate into node's parent immediately after node."
		node.parentNode.insertBefore(candidate, node.nextSibling);

		// "Append the node as the last child of candidate, preserving ranges."
		movePreservingRanges(node, candidate, -1, range);
	}

	var recordValuesCommands = ["subscript", "bold", "fontname", "fontsize", "forecolor", "hilitecolor", "italic", "strikethrough", "underline"];

	function recordValues(nodeList) {
		// "Let values be a list of (node, command, specified command value)
		// triples, initially empty."
		var values = [];

		// "For each node in node list, for each command in the list "subscript",
		// "bold", "fontName", "fontSize", "foreColor", "hiliteColor", "italic",
		// "strikethrough", and "underline" in that order:"

		// Ensure we have a plain array to avoid the potential performance
		// overhead of a NodeList
		var nodes = jQuery.makeArray(nodeList);
		var i, j;
		var node;
		var command;
		var ancestor;
		var specifiedCommandValue;
		for (i = 0; i < nodes.length; i++) {
			node = nodes[i];
			for (j = 0; j < recordValuesCommands.length; j++) {
				command = recordValuesCommands[j];

				// "Let ancestor equal node."
				ancestor = node;

				// "If ancestor is not an Element, set it to its parent."
				if (ancestor.nodeType != 1) {
					ancestor = ancestor.parentNode;
				}

				// "While ancestor is an Element and its specified command value
				// for command is null, set it to its parent."
				specifiedCommandValue = null;
				while (ancestor && ancestor.nodeType == 1 && (specifiedCommandValue = getSpecifiedCommandValue(ancestor, command)) === null) {
					ancestor = ancestor.parentNode;
				}

				// "If ancestor is an Element, add (node, command, ancestor's
				// specified command value for command) to values. Otherwise add
				// (node, command, null) to values."
				values.push([node, command, specifiedCommandValue]);
			}
		}

		// "Return values."
		return values;
	}

	//@}
	///// Clearing an element's value /////
	//@{

	function clearValue(element, command, range) {
		// "If element is not editable, return the empty list."
		if (!isEditable(element)) {
			return [];
		}

		// "If element's specified command value for command is null, return the
		// empty list."
		if (getSpecifiedCommandValue(element, command) === null) {
			return [];
		}

		// "If element is a simple modifiable element:"
		if (isSimpleModifiableElement(element)) {
			// "Let children be the children of element."
			var children = Array.prototype.slice.call(toArray(element.childNodes));

			// "For each child in children, insert child into element's parent
			// immediately before element, preserving ranges."
			var i;
			for (i = 0; i < children.length; i++) {
				movePreservingRanges(children[i], element.parentNode, Dom.getIndexInParent(element), range);
			}

			// "Remove element from its parent."
			element.parentNode.removeChild(element);

			// "Return children."
			return children;
		}

		// "If command is "strikethrough", and element has a style attribute that
		// sets "text-decoration" to some value containing "line-through", delete
		// "line-through" from the value."
		if (command == "strikethrough" && element.style.textDecoration.indexOf("line-through") != -1) {
			if (element.style.textDecoration == "line-through") {
				element.style.textDecoration = "";
			} else {
				element.style.textDecoration = element.style.textDecoration.replace("line-through", "");
			}
			if (element.getAttribute("style") == "") {
				element.removeAttribute("style");
			}
		}

		// "If command is "underline", and element has a style attribute that sets
		// "text-decoration" to some value containing "underline", delete
		// "underline" from the value."
		if (command == "underline" && element.style.textDecoration.indexOf("underline") != -1) {
			if (element.style.textDecoration == "underline") {
				element.style.textDecoration = "";
			} else {
				element.style.textDecoration = element.style.textDecoration.replace("underline", "");
			}
			if (element.getAttribute("style") == "") {
				element.removeAttribute("style");
			}
		}

		// "If the relevant CSS property for command is not null, unset the CSS
		// property property of element."
		if (commands[command].relevantCssProperty !== null) {
			element.style[commands[command].relevantCssProperty] = '';
			if (element.getAttribute("style") == "") {
				element.removeAttribute("style");
			}
		}

		// "If element is a font element:"
		if (isHtmlNamespace(element.namespaceURI) && element.tagName == "FONT") {
			// "If command is "foreColor", unset element's color attribute, if set."
			if (command == "forecolor") {
				element.removeAttribute("color");
			}

			// "If command is "fontName", unset element's face attribute, if set."
			if (command == "fontname") {
				element.removeAttribute("face");
			}

			// "If command is "fontSize", unset element's size attribute, if set."
			if (command == "fontsize") {
				element.removeAttribute("size");
			}
		}

		// "If element is an a element and command is "createLink" or "unlink",
		// unset the href property of element."
		if (isNamedHtmlElement(element, 'A') && (command == "createlink" || command == "unlink")) {
			element.removeAttribute("href");
		}

		// "If element's specified command value for command is null, return the
		// empty list."
		if (getSpecifiedCommandValue(element, command) === null) {
			return [];
		}

		// "Set the tag name of element to "span", and return the one-node list
		// consisting of the result."
		return [setTagName(element, "span", range)];
	}

	//@}
	///// Forcing the value of a node /////
	//@{

	function forceValue(node, command, newValue, range) {
		var children = [];
		var i;
		var specifiedValue;

		// "If node's parent is null, abort this algorithm."
		if (!node.parentNode) {
			return;
		}

		// "If new value is null, abort this algorithm."
		if (newValue === null) {
			return;
		}

		// "If node is an allowed child of "span":"
		if (isAllowedChild(node, "span")) {
			// "Reorder modifiable descendants of node's previousSibling."
			reorderModifiableDescendants(node.previousSibling, command, newValue, range);

			// "Reorder modifiable descendants of node's nextSibling."
			reorderModifiableDescendants(node.nextSibling, command, newValue, range);

			// "Wrap the one-node list consisting of node, with sibling criteria
			// returning true for a simple modifiable element whose specified
			// command value is equivalent to new value and whose effective command
			// value is loosely equivalent to new value and false otherwise, and
			// with new parent instructions returning null."
			wrap(
				[node],
				function (node) {
					return isSimpleModifiableElement(node) && areEquivalentValues(command, getSpecifiedCommandValue(node, command), newValue) && areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue);
				},
				function () {
					return null;
				},
				range
			);
		}

		// "If the effective command value of command is loosely equivalent to new
		// value on node, abort this algorithm."
		if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
			return;
		}

		// "If node is not an allowed child of "span":"
		if (!isAllowedChild(node, "span")) {
			// "Let children be all children of node, omitting any that are
			// Elements whose specified command value for command is neither null
			// nor equivalent to new value."
			for (i = 0; i < node.childNodes.length; i++) {
				if (node.childNodes[i].nodeType == $_.Node.ELEMENT_NODE) {
					specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);

					if (specifiedValue !== null && !areEquivalentValues(command, newValue, specifiedValue)) {
						continue;
					}
				}
				children.push(node.childNodes[i]);
			}

			// "Force the value of each Node in children, with command and new
			// value as in this invocation of the algorithm."
			for (i = 0; i < children.length; i++) {
				forceValue(children[i], command, newValue, range);
			}

			// "Abort this algorithm."
			return;
		}

		// "If the effective command value of command is loosely equivalent to new
		// value on node, abort this algorithm."
		if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
			return;
		}

		// "Let new parent be null."
		var newParent = null;

		// "If the CSS styling flag is false:"
		if (!cssStylingFlag) {
			// "If command is "bold" and new value is "bold", let new parent be the
			// result of calling createElement("b") on the ownerDocument of node."
			if (command == "bold" && (newValue == "bold" || newValue == "700")) {
				newParent = node.ownerDocument.createElement("b");
			}

			// "If command is "italic" and new value is "italic", let new parent be
			// the result of calling createElement("i") on the ownerDocument of
			// node."
			if (command == "italic" && newValue == "italic") {
				newParent = node.ownerDocument.createElement("i");
			}

			// "If command is "strikethrough" and new value is "line-through", let
			// new parent be the result of calling createElement("s") on the
			// ownerDocument of node."
			if (command == "strikethrough" && newValue == "line-through") {
				newParent = node.ownerDocument.createElement("s");
			}

			// "If command is "underline" and new value is "underline", let new
			// parent be the result of calling createElement("u") on the
			// ownerDocument of node."
			if (command == "underline" && newValue == "underline") {
				newParent = node.ownerDocument.createElement("u");
			}

			// "If command is "foreColor", and new value is fully opaque with red,
			// green, and blue components in the range 0 to 255:"
			if (command == "forecolor" && parseSimpleColor(newValue)) {
				// "Let new parent be the result of calling createElement("span")
				// on the ownerDocument of node."
				// NOTE: modified this process to create span elements with style attributes
				// instead of oldschool font tags with color attributes
				newParent = node.ownerDocument.createElement("span");

				// "If new value is an extended color keyword, set the color
				// attribute of new parent to new value."
				//
				// "Otherwise, set the color attribute of new parent to the result
				// of applying the rules for serializing simple color values to new
				// value (interpreted as a simple color)."
				jQuery(newParent).css('color', parseSimpleColor(newValue));
			}

			// "If command is "fontName", let new parent be the result of calling
			// createElement("font") on the ownerDocument of node, then set the
			// face attribute of new parent to new value."
			if (command == "fontname") {
				newParent = node.ownerDocument.createElement("font");
				newParent.face = newValue;
			}
		}

		// "If command is "createLink" or "unlink":"
		if (command == "createlink" || command == "unlink") {
			// "Let new parent be the result of calling createElement("a") on the
			// ownerDocument of node."
			newParent = node.ownerDocument.createElement("a");

			// "Set the href attribute of new parent to new value."
			newParent.setAttribute("href", newValue);

			// "Let ancestor be node's parent."
			var ancestor = node.parentNode;

			// "While ancestor is not null:"
			while (ancestor) {
				// "If ancestor is an a, set the tag name of ancestor to "span",
				// and let ancestor be the result."
				if (isNamedHtmlElement(ancestor, 'A')) {
					ancestor = setTagName(ancestor, "span", range);
				}

				// "Set ancestor to its parent."
				ancestor = ancestor.parentNode;
			}
		}

		// "If command is "fontSize"; and new value is one of "xx-small", "small",
		// "medium", "large", "x-large", "xx-large", or "xxx-large"; and either the
		// CSS styling flag is false, or new value is "xxx-large": let new parent
		// be the result of calling createElement("font") on the ownerDocument of
		// node, then set the size attribute of new parent to the number from the
		// following table based on new value: [table omitted]"
		if (command == "fontsize" && jQuery.inArray(newValue, ["xx-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]) != -1 && (!cssStylingFlag || newValue == "xxx-large")) {
			newParent = node.ownerDocument.createElement("font");
			newParent.size = cssSizeToLegacy(newValue);
		}

		// "If command is "subscript" or "superscript" and new value is
		// "subscript", let new parent be the result of calling
		// createElement("sub") on the ownerDocument of node."
		if ((command == "subscript" || command == "superscript") && newValue == "subscript") {
			newParent = node.ownerDocument.createElement("sub");
		}

		// "If command is "subscript" or "superscript" and new value is
		// "superscript", let new parent be the result of calling
		// createElement("sup") on the ownerDocument of node."
		if ((command == "subscript" || command == "superscript") && newValue == "superscript") {
			newParent = node.ownerDocument.createElement("sup");
		}

		// "If new parent is null, let new parent be the result of calling
		// createElement("span") on the ownerDocument of node."
		if (!newParent) {
			newParent = node.ownerDocument.createElement("span");
		}

		// "Insert new parent in node's parent before node."
		node.parentNode.insertBefore(newParent, node);

		// "If the effective command value of command for new parent is not loosely
		// equivalent to new value, and the relevant CSS property for command is
		// not null, set that CSS property of new parent to new value (if the new
		// value would be valid)."
		var property = commands[command].relevantCssProperty;
		if (property !== null && !areLooselyEquivalentValues(command, getEffectiveCommandValue(newParent, command), newValue)) {
			newParent.style[property] = newValue;
		}

		// "If command is "strikethrough", and new value is "line-through", and the
		// effective command value of "strikethrough" for new parent is not
		// "line-through", set the "text-decoration" property of new parent to
		// "line-through"."
		if (command == "strikethrough" && newValue == "line-through" && getEffectiveCommandValue(newParent, "strikethrough") != "line-through") {
			newParent.style.textDecoration = "line-through";
		}

		// "If command is "underline", and new value is "underline", and the
		// effective command value of "underline" for new parent is not
		// "underline", set the "text-decoration" property of new parent to
		// "underline"."
		if (command == "underline" && newValue == "underline" && getEffectiveCommandValue(newParent, "underline") != "underline") {
			newParent.style.textDecoration = "underline";
		}

		// "Append node to new parent as its last child, preserving ranges."
		movePreservingRanges(node, newParent, newParent.childNodes.length, range);

		// "If node is an Element and the effective command value of command for
		// node is not loosely equivalent to new value:"
		if (node.nodeType == $_.Node.ELEMENT_NODE && !areEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
			// "Insert node into the parent of new parent before new parent,
			// preserving ranges."
			movePreservingRanges(node, newParent.parentNode, Dom.getIndexInParent(newParent), range);

			// "Remove new parent from its parent."
			newParent.parentNode.removeChild(newParent);

			// "Let children be all children of node, omitting any that are
			// Elements whose specified command value for command is neither null
			// nor equivalent to new value."
			children = [];
			for (i = 0; i < node.childNodes.length; i++) {
				if (node.childNodes[i].nodeType == $_.Node.ELEMENT_NODE) {
					specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);

					if (specifiedValue !== null && !areEquivalentValues(command, newValue, specifiedValue)) {
						continue;
					}
				}
				children.push(node.childNodes[i]);
			}

			// "Force the value of each Node in children, with command and new
			// value as in this invocation of the algorithm."
			for (i = 0; i < children.length; i++) {
				forceValue(children[i], command, newValue, range);
			}
		}
	}

	//@}
	///// Pushing down values /////
	//@{

	function pushDownValues(node, command, newValue, range) {
		// "If node's parent is not an Element, abort this algorithm."
		if (!node.parentNode || node.parentNode.nodeType != $_.Node.ELEMENT_NODE) {
			return;
		}

		// "If the effective command value of command is loosely equivalent to new
		// value on node, abort this algorithm."
		if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
			return;
		}

		// "Let current ancestor be node's parent."
		var currentAncestor = node.parentNode;

		// "Let ancestor list be a list of Nodes, initially empty."
		var ancestorList = [];

		// "While current ancestor is an editable Element and the effective command
		// value of command is not loosely equivalent to new value on it, append
		// current ancestor to ancestor list, then set current ancestor to its
		// parent."
		while (isEditable(currentAncestor) && currentAncestor.nodeType == $_.Node.ELEMENT_NODE && !areLooselyEquivalentValues(command, getEffectiveCommandValue(currentAncestor, command), newValue)) {
			ancestorList.push(currentAncestor);
			currentAncestor = currentAncestor.parentNode;
		}

		// "If ancestor list is empty, abort this algorithm."
		if (!ancestorList.length) {
			return;
		}

		// "Let propagated value be the specified command value of command on the
		// last member of ancestor list."
		var propagatedValue = getSpecifiedCommandValue(ancestorList[ancestorList.length - 1], command);

		// "If propagated value is null and is not equal to new value, abort this
		// algorithm."
		if (propagatedValue === null && propagatedValue != newValue) {
			return;
		}

		// "If the effective command value for the parent of the last member of
		// ancestor list is not loosely equivalent to new value, and new value is
		// not null, abort this algorithm."
		if (newValue !== null && !areLooselyEquivalentValues(command, getEffectiveCommandValue(ancestorList[ancestorList.length - 1].parentNode, command), newValue)) {
			return;
		}

		// "While ancestor list is not empty:"
		while (ancestorList.length) {
			// "Let current ancestor be the last member of ancestor list."
			// "Remove the last member from ancestor list."
			currentAncestor = ancestorList.pop();

			// "If the specified command value of current ancestor for command is
			// not null, set propagated value to that value."
			if (getSpecifiedCommandValue(currentAncestor, command) !== null) {
				propagatedValue = getSpecifiedCommandValue(currentAncestor, command);
			}

			// "Let children be the children of current ancestor."
			var children = Array.prototype.slice.call(toArray(currentAncestor.childNodes));

			// "If the specified command value of current ancestor for command is
			// not null, clear the value of current ancestor."
			if (getSpecifiedCommandValue(currentAncestor, command) !== null) {
				clearValue(currentAncestor, command, range);
			}

			// "For every child in children:"
			var i;
			for (i = 0; i < children.length; i++) {
				var child = children[i];

				// "If child is node, continue with the next child."
				if (child == node) {
					continue;
				}

				// "If child is an Element whose specified command value for
				// command is neither null nor equivalent to propagated value,
				// continue with the next child."
				if (child.nodeType == $_.Node.ELEMENT_NODE && getSpecifiedCommandValue(child, command) !== null && !areEquivalentValues(command, propagatedValue, getSpecifiedCommandValue(child, command))) {
					continue;
				}

				// "If child is the last member of ancestor list, continue with the
				// next child."
				if (child == ancestorList[ancestorList.length - 1]) {
					continue;
				}

				// "Force the value of child, with command as in this algorithm
				// and new value equal to propagated value."
				forceValue(child, command, propagatedValue, range);
			}
		}
	}

	function restoreValues(values, range) {
		// "For each (node, command, value) triple in values:"
		$_(values).forEach(function (triple) {
			var node = triple[0];
			var command = triple[1];
			var value = triple[2];

			// "Let ancestor equal node."
			var ancestor = node;

			// "If ancestor is not an Element, set it to its parent."
			if (!ancestor || ancestor.nodeType != $_.Node.ELEMENT_NODE) {
				ancestor = ancestor.parentNode;
			}

			// "While ancestor is an Element and its specified command value for
			// command is null, set it to its parent."
			while (ancestor && ancestor.nodeType == $_.Node.ELEMENT_NODE && getSpecifiedCommandValue(ancestor, command) === null) {
				ancestor = ancestor.parentNode;
			}

			// "If value is null and ancestor is an Element, push down values on
			// node for command, with new value null."
			if (value === null && ancestor && ancestor.nodeType == $_.Node.ELEMENT_NODE) {
				pushDownValues(node, command, null, range);

				// "Otherwise, if ancestor is an Element and its specified command
				// value for command is not equivalent to value, or if ancestor is not
				// an Element and value is not null, force the value of command to
				// value on node."
			} else if ((ancestor && ancestor.nodeType == $_.Node.ELEMENT_NODE && !areEquivalentValues(command, getSpecifiedCommandValue(ancestor, command), value)) || ((!ancestor || ancestor.nodeType != $_.Node.ELEMENT_NODE) && value !== null)) {
				forceValue(node, command, value, range);
			}
		});
	}

	//@}
	///// Setting the selection's value /////
	//@{

	function setSelectionValue(command, newValue, range) {

		// Use current selected range if no range passed
		range = range || getActiveRange();

		// "If there is no editable text node effectively contained in the active
		// range:"
		if (!$_(getAllEffectivelyContainedNodes(range)).filter(function (node) { return node.nodeType == $_.Node.TEXT_NODE; }, true).some(isEditable)) {
			// "If command has inline command activated values, set the state
			// override to true if new value is among them and false if it's not."
			if (commands[command].hasOwnProperty("inlineCommandActivatedValues")) {
				setStateOverride(
					command,
					$_(commands[command].inlineCommandActivatedValues).indexOf(newValue) != -1,
					range
				);
			}

			// "If command is "subscript", unset the state override for
			// "superscript"."
			if (command == "subscript") {
				unsetStateOverride("superscript", range);
			}

			// "If command is "superscript", unset the state override for
			// "subscript"."
			if (command == "superscript") {
				unsetStateOverride("subscript", range);
			}

			// "If new value is null, unset the value override (if any)."
			if (newValue === null) {
				unsetValueOverride(command, range);

				// "Otherwise, if command has a value specified, set the value override
				// to new value."
			} else if (commands[command].hasOwnProperty("value")) {
				setValueOverride(command, newValue, range);
			}

			// "Abort these steps."
			return;
		}

		// "If the active range's start node is an editable Text node, and its
		// start offset is neither zero nor its start node's length, call
		// splitText() on the active range's start node, with argument equal to the
		// active range's start offset. Then set the active range's start node to
		// the result, and its start offset to zero."
		if (isEditable(range.startContainer) && range.startContainer.nodeType == $_.Node.TEXT_NODE && range.startOffset != 0 && range.startOffset != getNodeLength(range.startContainer)) {
			// Account for browsers not following range mutation rules
			var newNode = splitText(range.startContainer, range.startOffset);
			var newActiveRange = Aloha.createRange();
			if (range.startContainer == range.endContainer) {
				var newEndOffset = range.endOffset - range.startOffset;
				newActiveRange.setEnd(newNode, newEndOffset);
				range.setEnd(newNode, newEndOffset);
			}
			newActiveRange.setStart(newNode, 0);
			Aloha.getSelection().removeAllRanges();
			Aloha.getSelection().addRange(newActiveRange);

			range.setStart(newNode, 0);
		}

		// "If the active range's end node is an editable Text node, and its end
		// offset is neither zero nor its end node's length, call splitText() on
		// the active range's end node, with argument equal to the active range's
		// end offset."
		if (isEditable(range.endContainer) && range.endContainer.nodeType == $_.Node.TEXT_NODE && range.endOffset != 0 && range.endOffset != getNodeLength(range.endContainer)) {
			// IE seems to mutate the range incorrectly here, so we need correction
			// here as well.  The active range will be temporarily in orphaned
			// nodes, so calling getActiveRange() after splitText() but before
			// fixing the range will throw an exception.
			// TODO: check if this is still neccessary
			var activeRange = range;
			var newStart = [activeRange.startContainer, activeRange.startOffset];
			var newEnd = [activeRange.endContainer, activeRange.endOffset];
			splitText(activeRange.endContainer, activeRange.endOffset);
			activeRange.setStart(newStart[0], newStart[1]);
			activeRange.setEnd(newEnd[0], newEnd[1]);

			Aloha.getSelection().removeAllRanges();
			Aloha.getSelection().addRange(activeRange);
		}

		// "Let element list be all editable Elements effectively contained in the
		// active range.
		//
		// "For each element in element list, clear the value of element."
		$_(getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
			return isEditable(node) && node.nodeType == $_.Node.ELEMENT_NODE;
		})).forEach(function (element) {
			clearValue(element, command, range);
		});

		// "Let node list be all editable nodes effectively contained in the active
		// range.
		//
		// "For each node in node list:"
		$_(getAllEffectivelyContainedNodes(range, isEditable)).forEach(function (node) {
			// "Push down values on node."
			pushDownValues(node, command, newValue, range);

			// "Force the value of node."
			forceValue(node, command, newValue, range);
		});
	}

	/**
	 * attempt to retrieve a block like a table or an Aloha Block
	 * which is located one step right of the current caret position.
	 * If an appropriate element is found it will be returned or
	 * false otherwise
	 *
	 * @param {element} node current node we're in
	 * @param {number} offset current offset within that node
	 *
	 * @return the dom node if found or false if no appropriate
	 * element was found
	 */
	function getBlockAtNextPosition(node, offset) {
		var i;

		// if we're inside a text node we first have to check
		// if there is nothing but tabs, newlines or the like
		// after our current cursor position
		if (node.nodeType === $_.Node.TEXT_NODE && offset < node.length) {
			for (i = offset; i < node.length; i++) {
				if ((node.data.charAt(i) !== '\t' && node.data.charAt(i) !== '\r' && node.data.charAt(i) !== '\n') || node.data.charCodeAt(i) === 160) { // &nbsp;
					// this is a character that has to be deleted first
					return false;
				}
			}
		}

		// try the most simple approach first: the next sibling
		// is a table
		if (node.nextSibling && node.nextSibling.className && node.nextSibling.className.indexOf("aloha-table-wrapper") >= 0) {
			return node.nextSibling;
		}

		// since we got only ignorable whitespace here determine if
		// our nodes parents next sibling is a table
		if (node.parentNode && node.parentNode.nextSibling && node.parentNode.nextSibling.className && node.parentNode.nextSibling.className.indexOf("aloha-table-wrapper") >= 0) {
			return node.parentNode.nextSibling;
		}

		// our parents nextsibling is a pure whitespace node such as
		// generated by sourcecode indentation so we'll check for
		// the next next sibling
		if (node.parentNode && node.parentNode.nextSibling && isWhitespaceNode(node.parentNode.nextSibling) && node.parentNode.nextSibling.nextSibling && node.parentNode.nextSibling.nextSibling.className && node.parentNode.nextSibling.nextSibling.className.indexOf("aloha-table-wrapper") >= 0) {
			return node.parentNode.nextSibling.nextSibling;
		}

		// Note: the search above works for tables, since they cannot be
		// nested deeply in paragraphs and other formatting tags. If this code
		// is extended to work also for other blocks, the search probably needs to be adapted
	}

	/**
	 * Attempt to retrieve a block like a table or an Aloha Block
	 * which is located right before the current position.
	 * If an appropriate element is found, it will be returned or
	 * false otherwise
	 *
	 * @param {element} node current node
	 * @param {offset} offset current offset
	 *
	 * @return dom node of found or false if no appropriate
	 * element was found
	 */
	function getBlockAtPreviousPosition(node, offset) {
		var i;

		if (node.nodeType === $_.Node.TEXT_NODE && offset > 0) {
			for (i = offset - 1; i >= 0; i--) {
				if ((node.data.charAt(i) !== '\t' && node.data.charAt(i) !== '\r' && node.data.charAt(i) !== '\n') || node.data.charCodeAt(i) === 160) { // &nbsp;
					// this is a character that has to be deleted first
					return false;
				}
			}
		}

		// try the previous sibling
		if (node.previousSibling && node.previousSibling.className && node.previousSibling.className.indexOf("aloha-table-wrapper") >= 0) {
			return node.previousSibling;
		}

		// try the parent's previous sibling
		if (node.parentNode && node.parentNode.previousSibling && node.parentNode.previousSibling.className && node.parentNode.previousSibling.className.indexOf("aloha-table-wrapper") >= 0) {
			return node.parentNode.previousSibling;
		}

		// the parent's previous sibling might be a whitespace node
		if (node.parentNode && node.parentNode.previousSibling && isWhitespaceNode(node.parentNode.previousSibling) && node.parentNode.previousSibling.previousSibling && node.parentNode.previousSibling.previousSibling.className && node.parentNode.previousSibling.previousSibling.className.indexOf('aloha-table-wrapper') >= 0) {
			return node.parentNode.previousSibling.previousSibling;
		}

		// Note: the search above works for tables, since they cannot be
		// nested deeply in paragraphs and other formatting tags. If this code
		// is extended to work also for other blocks, the search probably needs to be adapted

		return false;
	}

	// "A boundary point (node, offset) is a block start point if either node's
	// parent is null and offset is zero; or node has a child with index offset −
	// 1, and that child is either a visible block node or a visible br."
	function isBlockStartPoint(node, offset) {
		return (!node.parentNode && offset == 0) || (0 <= offset - 1 && offset - 1 < node.childNodes.length && isVisible(node.childNodes[offset - 1]) && (isBlockNode(node.childNodes[offset - 1]) || isNamedHtmlElement(node.childNodes[offset - 1], "br")));
	}

	// "A boundary point (node, offset) is a block end point if either node's
	// parent is null and offset is node's length; or node has a child with index
	// offset, and that child is a visible block node."
	function isBlockEndPoint(node, offset) {
		return (!node.parentNode && offset == getNodeLength(node)) || (offset < node.childNodes.length && isVisible(node.childNodes[offset]) && isBlockNode(node.childNodes[offset]));
	}

	// "A boundary point is a block boundary point if it is either a block start
	// point or a block end point."
	function isBlockBoundaryPoint(node, offset) {
		return isBlockStartPoint(node, offset) || isBlockEndPoint(node, offset);
	}

	function followsLineBreak(node) {
		// "Let offset be zero."
		var offset = 0;

		// "While (node, offset) is not a block boundary point:"
		while (!isBlockBoundaryPoint(node, offset)) {
			// "If node has a visible child with index offset minus one, return
			// false."
			if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isVisible(node.childNodes[offset - 1])) {
				return false;
			}

			// "If offset is zero or node has no children, set offset to node's
			// index, then set node to its parent."
			if (offset == 0 || !node.hasChildNodes()) {
				offset = Dom.getIndexInParent(node);
				node = node.parentNode;

				// "Otherwise, set node to its child with index offset minus one, then
				// set offset to node's length."
			} else {
				node = node.childNodes[offset - 1];
				offset = getNodeLength(node);
			}
		}

		// "Return true."
		return true;
	}

	function precedesLineBreak(node) {
		// "Let offset be node's length."
		var offset = getNodeLength(node);

		// "While (node, offset) is not a block boundary point:"
		while (!isBlockBoundaryPoint(node, offset)) {
			// "If node has a visible child with index offset, return false."
			if (offset < node.childNodes.length && isVisible(node.childNodes[offset])) {
				return false;
			}

			// "If offset is node's length or node has no children, set offset to
			// one plus node's index, then set node to its parent."
			if (offset == getNodeLength(node) || !node.hasChildNodes()) {
				offset = 1 + Dom.getIndexInParent(node);
				node = node.parentNode;

				// "Otherwise, set node to its child with index offset and set offset
				// to zero."
			} else {
				node = node.childNodes[offset];
				offset = 0;
			}
		}

		// "Return true."
		return true;
	}

	//@}
	///// Splitting a node list's parent /////
	//@{

	function splitParent(nodeList, range) {
		var i;

		// "Let original parent be the parent of the first member of node list."
		var originalParent = nodeList[0].parentNode;

		// "If original parent is not editable or its parent is null, do nothing
		// and abort these steps."
		if (!isEditable(originalParent) || !originalParent.parentNode) {
			return;
		}

		// "If the first child of original parent is in node list, remove
		// extraneous line breaks before original parent."
		if (jQuery.inArray(originalParent.firstChild, nodeList) != -1) {
			removeExtraneousLineBreaksBefore(originalParent);
		}

		var firstChildInNodeList = jQuery.inArray(originalParent.firstChild, nodeList) != -1;
		var lastChildInNodeList = jQuery.inArray(originalParent.lastChild, nodeList) != -1;

		// "If the first child of original parent is in node list, and original
		// parent follows a line break, set follows line break to true. Otherwise,
		// set follows line break to false."
		var followsLineBreak_ = firstChildInNodeList && followsLineBreak(originalParent);

		// "If the last child of original parent is in node list, and original
		// parent precedes a line break, set precedes line break to true.
		// Otherwise, set precedes line break to false."
		var precedesLineBreak_ = lastChildInNodeList && precedesLineBreak(originalParent);

		// "If the first child of original parent is not in node list, but its last
		// child is:"
		if (!firstChildInNodeList && lastChildInNodeList) {
			// "For each node in node list, in reverse order, insert node into the
			// parent of original parent immediately after original parent,
			// preserving ranges."
			for (i = nodeList.length - 1; i >= 0; i--) {
				movePreservingRanges(nodeList[i], originalParent.parentNode, 1 + Dom.getIndexInParent(originalParent), range);
			}

			// "If precedes line break is true, and the last member of node list
			// does not precede a line break, call createElement("br") on the
			// context object and insert the result immediately after the last
			// member of node list."
			if (precedesLineBreak_ && !precedesLineBreak(nodeList[nodeList.length - 1])) {
				nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
			}

			// "Remove extraneous line breaks at the end of original parent."
			removeExtraneousLineBreaksAtTheEndOf(originalParent);

			// "Abort these steps."
			return;
		}

		// "If the first child of original parent is not in node list:"
		if (!firstChildInNodeList) {
			// "Let cloned parent be the result of calling cloneNode(false) on
			// original parent."
			var clonedParent = originalParent.cloneNode(false);

			// "If original parent has an id attribute, unset it."
			originalParent.removeAttribute("id");

			// "Insert cloned parent into the parent of original parent immediately
			// before original parent."
			originalParent.parentNode.insertBefore(clonedParent, originalParent);

			// "While the previousSibling of the first member of node list is not
			// null, append the first child of original parent as the last child of
			// cloned parent, preserving ranges."
			while (nodeList[0].previousSibling) {
				movePreservingRanges(originalParent.firstChild, clonedParent, clonedParent.childNodes.length, range);
			}
		}

		// "For each node in node list, insert node into the parent of original
		// parent immediately before original parent, preserving ranges."
		for (i = 0; i < nodeList.length; i++) {
			movePreservingRanges(nodeList[i], originalParent.parentNode, Dom.getIndexInParent(originalParent), range);
		}

		// "If follows line break is true, and the first member of node list does
		// not follow a line break, call createElement("br") on the context object
		// and insert the result immediately before the first member of node list."
		if (followsLineBreak_ && !followsLineBreak(nodeList[0])) {
			nodeList[0].parentNode.insertBefore(document.createElement("br"), nodeList[0]);
		}

		// "If the last member of node list is an inline node other than a br, and
		// the first child of original parent is a br, and original parent is not
		// an inline node, remove the first child of original parent from original
		// parent."
		if (isInlineNode(nodeList[nodeList.length - 1]) && !isNamedHtmlElement(nodeList[nodeList.length - 1], "br") && isNamedHtmlElement(originalParent.firstChild, "br") && !isInlineNode(originalParent)) {
			originalParent.removeChild(originalParent.firstChild);
		}

		// "If original parent has no children:"
		if (!originalParent.hasChildNodes()) {
			// if the current range is collapsed and at the end of the originalParent.parentNode
			// the offset will not be available anymore after the next step (remove child)
			// that's why we need to fix the range to prevent a bogus offset
			if (originalParent.parentNode === range.startContainer && originalParent.parentNode === range.endContainer && range.startContainer === range.endContainer && range.startOffset === range.endOffset && originalParent.parentNode.childNodes.length === range.startOffset) {
				range.startOffset = originalParent.parentNode.childNodes.length - 1;
				range.endOffset = range.startOffset;
			}

			// "Remove original parent from its parent."
			originalParent.parentNode.removeChild(originalParent);

			// "If precedes line break is true, and the last member of node list
			// does not precede a line break, call createElement("br") on the
			// context object and insert the result immediately after the last
			// member of node list."
			if (precedesLineBreak_ && !precedesLineBreak(nodeList[nodeList.length - 1])) {
				nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
			}

			// "Otherwise, remove extraneous line breaks before original parent."
		} else {
			removeExtraneousLineBreaksBefore(originalParent);
		}

		// "If node list's last member's nextSibling is null, but its parent is not
		// null, remove extraneous line breaks at the end of node list's last
		// member's parent."
		if (!nodeList[nodeList.length - 1].nextSibling && nodeList[nodeList.length - 1].parentNode) {
			removeExtraneousLineBreaksAtTheEndOf(nodeList[nodeList.length - 1].parentNode);
		}
	}

	//@}
	///// The backColor command /////
	//@{
	commands.backcolor = {
		// Copy-pasted, same as hiliteColor
		action: function (value, range) {
			// Action is further copy-pasted, same as foreColor

			// "If value is not a valid CSS color, prepend "#" to it."
			//
			// "If value is still not a valid CSS color, or if it is currentColor,
			// abort these steps and do nothing."
			//
			// Cheap hack for testing, no attempt to be comprehensive.
			if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
				value = "#" + value;
			}
			if (!/^(rgba?|hsla?)\(.*\)$/.test(value) && !parseSimpleColor(value) && value.toLowerCase() != "transparent") {
				return;
			}

			// "Set the selection's value to value."
			setSelectionValue("backcolor", value, range);
		},
		standardInlineValueCommand: true,
		relevantCssProperty: "backgroundColor",
		equivalentValues: function (val1, val2) {
			// "Either both strings are valid CSS colors and have the same red,
			// green, blue, and alpha components, or neither string is a valid CSS
			// color."
			return normalizeColor(val1) === normalizeColor(val2);
		}
	};

	//@}
	///// The bold command /////
	//@{
	commands.bold = {
		action: function (value, range) {
			// "If queryCommandState("bold") returns true, set the selection's
			// value to "normal". Otherwise set the selection's value to "bold"."
			if (myQueryCommandState("bold", range)) {
				setSelectionValue("bold", "normal", range);
			} else {
				setSelectionValue("bold", "bold", range);
			}
		},
		inlineCommandActivatedValues: ["bold", "600", "700", "800", "900"],
		relevantCssProperty: "fontWeight",
		equivalentValues: function (val1, val2) {
			// "Either the two strings are equal, or one is "bold" and the other is
			// "700", or one is "normal" and the other is "400"."
			return val1 == val2 || (val1 == "bold" && val2 == "700") || (val1 == "700" && val2 == "bold") || (val1 == "normal" && val2 == "400") || (val1 == "400" && val2 == "normal");
		}
	};

	//@}
	///// The createLink command /////
	//@{
	commands.createlink = {
		action: function (value, range) {
			// "If value is the empty string, abort these steps and do nothing."
			if (value === "") {
				return;
			}

			// "For each editable a element that has an href attribute and is an
			// ancestor of some node effectively contained in the active range, set
			// that a element's href attribute to value."
			//
			// TODO: We don't actually do this in tree order, not that it matters
			// unless you're spying with mutation events.
			$_(getAllEffectivelyContainedNodes(getActiveRange())).forEach(function (node) {
				$_(getAncestors(node)).forEach(function (ancestor) {
					if (isEditable(ancestor) && isNamedHtmlElement(ancestor, 'a') && hasAttribute(ancestor, "href")) {
						ancestor.setAttribute("href", value);
					}
				});
			});

			// "Set the selection's value to value."
			setSelectionValue("createlink", value, range);
		},
		standardInlineValueCommand: true
	};

	//@}
	///// The fontName command /////
	//@{
	commands.fontname = {
		action: function (value, range) {
			// "Set the selection's value to value."
			setSelectionValue("fontname", value, range);
		},
		standardInlineValueCommand: true,
		relevantCssProperty: "fontFamily"
	};

	//@}
	///// The fontSize command /////
	//@{

	commands.fontsize = {
		action: function (value, range) {
			// "If value is the empty string, abort these steps and do nothing."
			if (value === "") {
				return;
			}

			value = normalizeFontSize(value);

			// "If value is not one of the strings "xx-small", "x-small", "small",
			// "medium", "large", "x-large", "xx-large", "xxx-large", and is not a
			// valid CSS absolute length, then abort these steps and do nothing."
			//
			// More cheap hacks to skip valid CSS absolute length checks.
			if (jQuery.inArray(value, ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]) == -1 && !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc)$/.test(value)) {
				return;
			}

			// "Set the selection's value to value."
			setSelectionValue("fontsize", value, range);
		},
		indeterm: function () {
			// "True if among editable Text nodes that are effectively contained in
			// the active range, there are two that have distinct effective command
			// values.  Otherwise false."
			return $_(getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
				return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
			})).map(function (node) {
				return getEffectiveCommandValue(node, "fontsize");
			}, true).filter(function (value, i, arr) {
				return $_(arr.slice(0, i)).indexOf(value) == -1;
			}).length >= 2;
		},
		value: function (range) {
			// "Let pixel size be the effective command value of the first editable
			// Text node that is effectively contained in the active range, or if
			// there is no such node, the effective command value of the active
			// range's start node, in either case interpreted as a number of
			// pixels."
			var node = getAllEffectivelyContainedNodes(range, function (node) {
				return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
			})[0];
			if (node === undefined) {
				node = range.startContainer;
			}
			var pixelSize = getEffectiveCommandValue(node, "fontsize");

			// "Return the legacy font size for pixel size."
			return getLegacyFontSize(pixelSize);
		},
		relevantCssProperty: "fontSize"
	};

	//@}
	///// The foreColor command /////
	//@{
	commands.forecolor = {
		action: function (value, range) {
			// Copy-pasted, same as backColor and hiliteColor

			// "If value is not a valid CSS color, prepend "#" to it."
			//
			// "If value is still not a valid CSS color, or if it is currentColor,
			// abort these steps and do nothing."
			//
			// Cheap hack for testing, no attempt to be comprehensive.
			if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
				value = "#" + value;
			}
			if (!/^(rgba?|hsla?)\(.*\)$/.test(value) && !parseSimpleColor(value) && value.toLowerCase() != "transparent") {
				return;
			}

			// "Set the selection's value to value."
			setSelectionValue("forecolor", value, range);
		},
		standardInlineValueCommand: true,
		relevantCssProperty: "color",
		equivalentValues: function (val1, val2) {
			// "Either both strings are valid CSS colors and have the same red,
			// green, blue, and alpha components, or neither string is a valid CSS
			// color."
			return normalizeColor(val1) === normalizeColor(val2);
		}
	};

	//@}
	///// The hiliteColor command /////
	//@{
	commands.hilitecolor = {
		// Copy-pasted, same as backColor
		action: function (value, range) {
			// Action is further copy-pasted, same as foreColor

			// "If value is not a valid CSS color, prepend "#" to it."
			//
			// "If value is still not a valid CSS color, or if it is currentColor,
			// abort these steps and do nothing."
			//
			// Cheap hack for testing, no attempt to be comprehensive.
			if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
				value = "#" + value;
			}
			if (!/^(rgba?|hsla?)\(.*\)$/.test(value) && !parseSimpleColor(value) && value.toLowerCase() != "transparent") {
				return;
			}

			// "Set the selection's value to value."
			setSelectionValue("hilitecolor", value, range);
		},
		indeterm: function () {
			// "True if among editable Text nodes that are effectively contained in
			// the active range, there are two that have distinct effective command
			// values.  Otherwise false."
			return $_(getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
				return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
			})).map(function (node) {
				return getEffectiveCommandValue(node, "hilitecolor");
			}, true).filter(function (value, i, arr) {
				return $_(arr.slice(0, i)).indexOf(value) == -1;
			}).length >= 2;
		},
		standardInlineValueCommand: true,
		relevantCssProperty: "backgroundColor",
		equivalentValues: function (val1, val2) {
			// "Either both strings are valid CSS colors and have the same red,
			// green, blue, and alpha components, or neither string is a valid CSS
			// color."
			return normalizeColor(val1) === normalizeColor(val2);
		}
	};

	//@}
	///// The italic command /////
	//@{
	commands.italic = {
		action: function (value, range) {
			// "If queryCommandState("italic") returns true, set the selection's
			// value to "normal". Otherwise set the selection's value to "italic"."
			if (myQueryCommandState("italic", range)) {
				setSelectionValue("italic", "normal", range);
			} else {
				setSelectionValue("italic", "italic", range);
			}
		},
		inlineCommandActivatedValues: ["italic", "oblique"],
		relevantCssProperty: "fontStyle"
	};

	//@}
	///// The removeFormat command /////
	//@{
	commands.removeformat = {
		action: function (value, range) {
			var newEnd, newStart, newNode;

			// "A removeFormat candidate is an editable HTML element with local
			// name "abbr", "acronym", "b", "bdi", "bdo", "big", "blink", "cite",
			// "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q",
			// "s", "samp", "small", "span", "strike", "strong", "sub", "sup",
			// "tt", "u", or "var"."
			function isRemoveFormatCandidate(node) {
				return isEditable(node) && isHtmlElementInArray(node, ["abbr", "acronym", "b", "bdi", "bdo", "big", "blink", "cite", "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q", "s", "samp", "small", "span", "strike", "strong", "sub", "sup", "tt", "u", "var"]);
			}

			// "Let elements to remove be a list of every removeFormat candidate
			// effectively contained in the active range."
			var elementsToRemove = getAllEffectivelyContainedNodes(getActiveRange(), isRemoveFormatCandidate);

			// "For each element in elements to remove:"
			$_(elementsToRemove).forEach(function (element) {
				// "While element has children, insert the first child of element
				// into the parent of element immediately before element,
				// preserving ranges."
				while (element.hasChildNodes()) {
					movePreservingRanges(element.firstChild, element.parentNode, Dom.getIndexInParent(element), getActiveRange());
				}

				// "Remove element from its parent."
				element.parentNode.removeChild(element);
			});

			// "If the active range's start node is an editable Text node, and its
			// start offset is neither zero nor its start node's length, call
			// splitText() on the active range's start node, with argument equal to
			// the active range's start offset. Then set the active range's start
			// node to the result, and its start offset to zero."
			if (isEditable(getActiveRange().startContainer) && getActiveRange().startContainer.nodeType == $_.Node.TEXT_NODE && getActiveRange().startOffset != 0 && getActiveRange().startOffset != getNodeLength(getActiveRange().startContainer)) {
				// Account for browsers not following range mutation rules
				if (getActiveRange().startContainer == getActiveRange().endContainer) {
					newEnd = getActiveRange().endOffset - getActiveRange().startOffset;
					newNode = splitText(getActiveRange().startContainer, getActiveRange().startOffset);
					getActiveRange().setStart(newNode, 0);
					getActiveRange().setEnd(newNode, newEnd);
				} else {
					getActiveRange().setStart(splitText(getActiveRange().startContainer, getActiveRange().startOffset), 0);
				}
			}

			// "If the active range's end node is an editable Text node, and its
			// end offset is neither zero nor its end node's length, call
			// splitText() on the active range's end node, with argument equal to
			// the active range's end offset."
			if (isEditable(getActiveRange().endContainer) && getActiveRange().endContainer.nodeType == $_.Node.TEXT_NODE && getActiveRange().endOffset != 0 && getActiveRange().endOffset != getNodeLength(getActiveRange().endContainer)) {
				// IE seems to mutate the range incorrectly here, so we need
				// correction here as well.  Have to be careful to set the range to
				// something not including the text node so that getActiveRange()
				// doesn't throw an exception due to a temporarily detached
				// endpoint.
				newStart = [getActiveRange().startContainer, getActiveRange().startOffset];
				newEnd = [getActiveRange().endContainer, getActiveRange().endOffset];
				getActiveRange().setEnd(document.documentElement, 0);
				splitText(newEnd[0], newEnd[1]);
				getActiveRange().setStart(newStart[0], newStart[1]);
				getActiveRange().setEnd(newEnd[0], newEnd[1]);
			}

			// "Let node list consist of all editable nodes effectively contained
			// in the active range."
			//
			// "For each node in node list, while node's parent is a removeFormat
			// candidate in the same editing host as node, split the parent of the
			// one-node list consisting of node."
			$_(getAllEffectivelyContainedNodes(getActiveRange(), isEditable)).forEach(function (node) {
				while (isRemoveFormatCandidate(node.parentNode) && inSameEditingHost(node.parentNode, node)) {
					splitParent([node], getActiveRange());
				}
			});

			// "For each of the entries in the following list, in the given order,
			// set the selection's value to null, with command as given."
			$_(["subscript", "bold", "fontname", "fontsize", "forecolor", "hilitecolor", "italic", "strikethrough", "underline"]).forEach(function (command) {
				setSelectionValue(command, null, range);
			});
		}
	};

	//@}
	///// The strikethrough command /////
	//@{
	commands.strikethrough = {
		action: function (value, range) {
			// "If queryCommandState("strikethrough") returns true, set the
			// selection's value to null. Otherwise set the selection's value to
			// "line-through"."
			if (myQueryCommandState("strikethrough", range)) {
				setSelectionValue("strikethrough", null, range);
			} else {
				setSelectionValue("strikethrough", "line-through", range);
			}
		},
		inlineCommandActivatedValues: ["line-through"]
	};

	//@}
	///// The subscript command /////
	//@{
	commands.subscript = {
		action: function (value, range) {
			// "Call queryCommandState("subscript"), and let state be the result."
			var state = myQueryCommandState("subscript", range);

			// "Set the selection's value to null."
			setSelectionValue("subscript", null, range);

			// "If state is false, set the selection's value to "subscript"."
			if (!state) {
				setSelectionValue("subscript", "subscript", range);
			}
		},
		indeterm: function () {
			// "True if either among editable Text nodes that are effectively
			// contained in the active range, there is at least one with effective
			// command value "subscript" and at least one with some other effective
			// command value; or if there is some editable Text node effectively
			// contained in the active range with effective command value "mixed".
			// Otherwise false."
			var nodes = getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
				return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
			});
			return (($_(nodes).some(function (node) { return getEffectiveCommandValue(node, "subscript") == "subscript"; })
					 && $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "subscript") != "subscript"; }))
					|| $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "subscript") == "mixed"; }));
		},
		inlineCommandActivatedValues: ["subscript"]
	};

	//@}
	///// The superscript command /////
	//@{
	commands.superscript = {
		action: function (value, range) {
			// "Call queryCommandState("superscript"), and let state be the
			// result."
			var state = myQueryCommandState("superscript", range);

			// "Set the selection's value to null."
			setSelectionValue("superscript", null, range);

			// "If state is false, set the selection's value to "superscript"."
			if (!state) {
				setSelectionValue("superscript", "superscript", range);
			}
		},
		indeterm: function () {
			// "True if either among editable Text nodes that are effectively
			// contained in the active range, there is at least one with effective
			// command value "superscript" and at least one with some other
			// effective command value; or if there is some editable Text node
			// effectively contained in the active range with effective command
			// value "mixed".  Otherwise false."
			var nodes = getAllEffectivelyContainedNodes(
				getActiveRange(),
				function (node) {
					return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
				}
			);
			return (($_(nodes).some(function (node) { return getEffectiveCommandValue(node, "superscript") == "superscript"; })
					 && $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "superscript") != "superscript"; }))
					|| $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "superscript") == "mixed"; }));
		},
		inlineCommandActivatedValues: ["superscript"]
	};

	//@}
	///// The underline command /////
	//@{
	commands.underline = {
		action: function (value, range) {
			// "If queryCommandState("underline") returns true, set the selection's
			// value to null. Otherwise set the selection's value to "underline"."
			if (myQueryCommandState("underline", range)) {
				setSelectionValue("underline", null, range);
			} else {
				setSelectionValue("underline", "underline", range);
			}
		},
		inlineCommandActivatedValues: ["underline"]
	};

	//@}
	///// The unlink command /////
	//@{
	commands.unlink = {
		action: function () {
			// "Let hyperlinks be a list of every a element that has an href
			// attribute and is contained in the active range or is an ancestor of
			// one of its boundary points."
			//
			// As usual, take care to ensure it's tree order.  The correctness of
			// the following is left as an exercise for the reader.
			var range = getActiveRange();
			var hyperlinks = [];
			var node;
			for (node = range.startContainer; node; node = node.parentNode) {
				if (isNamedHtmlElement(node, 'A') && hasAttribute(node, "href")) {
					hyperlinks.unshift(node);
				}
			}
			for (node = range.startContainer; node != nextNodeDescendants(range.endContainer); node = nextNode(node)) {
				if (isNamedHtmlElement(node, 'A') && hasAttribute(node, "href") && (isContained(node, range) || isAncestor(node, range.endContainer) || node == range.endContainer)) {
					hyperlinks.push(node);
				}
			}

			// "Clear the value of each member of hyperlinks."
			var i;
			for (i = 0; i < hyperlinks.length; i++) {
				clearValue(hyperlinks[i], "unlink", range);
			}
		},
		standardInlineValueCommand: true
	};

	//@}

	/////////////////////////////////////
	///// Block formatting commands /////
	/////////////////////////////////////

	///// Block formatting command definitions /////
	//@{

	// "An indentation element is either a blockquote, or a div that has a style
	// attribute that sets "margin" or some subproperty of it."
	function isIndentationElement(node) {
		// Handling of indentation elements while deleting is somehow broken (pressing backspace
		// in blockquotes wraps the blockquote into a div, ...)
		// therefore for now, we pretend that indentation elements do not exist at all.
		return false;
	}

	// "A simple indentation element is an indentation element that has no
	// attributes other than one or more of
	//
	//   * "a style attribute that sets no properties other than "margin", "border",
	//     "padding", or subproperties of those;
	//   * "a class attribute;
	//   * "a dir attribute."
	function isSimpleIndentationElement(node) {
		if (!isIndentationElement(node)) {
			return false;
		}

		if (node.tagName != "BLOCKQUOTE" && node.tagName != "DIV") {
			return false;
		}

		var i;
		for (i = 0; i < node.attributes.length; i++) {
			if (!isHtmlNamespace(node.attributes[i].namespaceURI) || jQuery.inArray(node.attributes[i].name, ["style", "class", "dir"]) == -1) {
				return false;
			}
		}

		if (typeof node.style.length !== 'undefined') {
			for (i = 0; i < node.style.length; i++) {
				// This is approximate, but it works well enough for my purposes.
				if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {
					return false;
				}
			}
		} else {
			var s;
			/*jslint forin: true*/ //not sure whether node.style.hasOwnProperty is valid
			for (s in node.style) {
				// This is approximate, but it works well enough for my purposes.
				if (!/^(-[a-z]+-)?(margin|border|padding)/.test(s) && node.style[s] && node.style[s] !== 0 && node.style[s] !== 'false') {
					return false;
				}
			}
			/*jslint forin: false*/
		}

		return true;
	}

	// "A non-list single-line container is an HTML element with local name
	// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "listing", "p", "pre",
	// or "xmp"."
	function isNonListSingleLineContainer(node) {
		return isHtmlElementInArray(node, ["address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "listing", "p", "pre", "xmp"]);
	}

	// "A single-line container is either a non-list single-line container, or an
	// HTML element with local name "li", "dt", or "dd"."
	function isSingleLineContainer(node) {
		return isNonListSingleLineContainer(node) || isHtmlElementInArray(node, ["li", "dt", "dd"]);
	}

	// "The default single-line container name is "p"."
	var defaultSingleLineContainerName = "p";

	//@}
	///// Check whether the given element is an end break /////
	//@{
	function isEndBreak(element) {
		return (isNamedHtmlElement(element, 'br') && element.parentNode.lastChild === element);
	}

	//@}
	///// Create an end break /////
	//@{
	function createEndBreak() {
		return document.createElement("br");
	}

	/**
	 * Ensure the container is editable
	 * E.g. when called for an empty paragraph or header, and the browser is not IE,
	 * we need to append a br (marked with class aloha-end-br)
	 * For IE7, there is a special behaviour that will append zero-width whitespace
	 * @param {DOMNode} container
	 */
	function ensureContainerEditable(container) {
		if (!container) {
			return;
		}

		// Because it is useful to be able to completely empty the contents of
		// an editing host during editing.  So long as the container's
		// contenteditable attribute is "true" (as is the case during editing),
		// the element will be rendered visibly in all browsers.  This fact
		// allows us to not have to prop up the container with a <br> in order
		// to keep it accessible to the editor.
		if (isEditingHost(container)) {
			return;
		}

		if (isNamedHtmlElement(container.lastChild, "br")) {
			return;
		}

		if ($_(container.childNodes).some(isVisible)) {
			return;
		}

		if (!Aloha.browser.msie || parseInt(Aloha.browser.version, 10) >= 11) {
			// for normal browsers, the end-br will do
			container.appendChild(createEndBreak());
		} else if (Aloha.browser.msie && Aloha.browser.version <= 7 && isHtmlElementInArray(container, ["p", "h1", "h2", "h3", "h4", "h5", "h6", "pre", "blockquote"])) {
			// for IE7, we need to insert a text node containing a single zero-width whitespace character
			if (!container.firstChild) {
				container.appendChild(document.createTextNode('\u200b'));
			}
		}
	}

	/**
	 * Node names that can not be unwrapped.
	 *
	 * @const
	 * @type {string[]}
	 */
	var NOT_UNWRAPPABLE_NODES = ['TABLE', 'OL', 'UL', 'DL'];

	/**
	 * Checks if `node` can be unwrapped.
	 *
	 * @param  {Element} node
	 * @return {boolean}
	 */
	function isUnwrappable(node) {
		return jQuery.inArray(node.nodeName, NOT_UNWRAPPABLE_NODES) === -1;
	}
	//@}
	///// Assorted block formatting command algorithms /////
	//@{

	function fixDisallowedAncestors(node, range) {
		var i;

		// "If node is not editable, abort these steps."
		if (!isEditable(node)) {
			return;
		}

		// "If node is not an allowed child of any of its ancestors in the same
		// editing host, and is not an HTML element with local name equal to the
		// default single-line container name:"
		if ($_(getAncestors(node)).every(function (ancestor) { return !inSameEditingHost(node, ancestor) || !isAllowedChild(node, ancestor); })
			    && !isHtmlElement_obsolete(node, defaultSingleLineContainerName)) {
			// "If node is a dd or dt, wrap the one-node list consisting of node,
			// with sibling criteria returning true for any dl with no attributes
			// and false otherwise, and new parent instructions returning the
			// result of calling createElement("dl") on the context object. Then
			// abort these steps."
			if (isHtmlElementInArray(node, ["dd", "dt"])) {
				wrap(
					[node],
					function (sibling) {
						return isNamedHtmlElement(sibling, 'dl') && !sibling.attributes.length;
					},
					function () {
						return document.createElement("dl");
					},
					range
				);
				return;
			}

			// "If node is not a prohibited paragraph child, abort these steps."
			if (!isProhibitedParagraphChild(node)) {
				return;
			}

			// "Set the tag name of node to the default single-line container name,
			// and let node be the result."
			node = setTagName(node, defaultSingleLineContainerName, range);

			ensureContainerEditable(node);

			// "Fix disallowed ancestors of node."
			fixDisallowedAncestors(node, range);

			// "Let descendants be all descendants of node."
			var descendants = getDescendants(node);

			// "Fix disallowed ancestors of each member of descendants."
			for (i = 0; i < descendants.length; i++) {
				fixDisallowedAncestors(descendants[i], range);
			}

			// "Abort these steps."
			return;
		}

		// "Record the values of the one-node list consisting of node, and let
		// values be the result."
		var values = recordValues([node]);
		var newStartOffset, newEndOffset;

		// "While node is not an allowed child of its parent, split the parent of
		// the one-node list consisting of node."
		while (!isAllowedChild(node, node.parentNode)) {
			// If the parent contains only this node and possibly empty text nodes, we rather want to unwrap the node, instead of splitting.
			// With splitting, we would get empty nodes, like:
			// split: <p><p>foo</p></p> -> <p></p><p>foo</p> (bad)
			// unwrap: <p><p>foo</p></p> -> <p>foo</p> (good)

			// First remove empty text nodes that are children of the parent and correct the range if necessary
			// we do this to have the node being the only child of its parent, so that we can replace the parent with the node
			for (i = node.parentNode.childNodes.length - 1; i >= 0; --i) {
				if (node.parentNode.childNodes[i].nodeType == 3 && node.parentNode.childNodes[i].data.length == 0) {
					// we remove the empty text node
					node.parentNode.removeChild(node.parentNode.childNodes[i]);

					// if the range points to somewhere behind the removed text node, we reduce the offset
					if (range.startContainer == node.parentNode && range.startOffset > i) {
						range.startOffset--;
					}
					if (range.endContainer == node.parentNode && range.endOffset > i) {
						range.endOffset--;
					}
				}
			}

			// now that the parent has only the node as child (because we
			// removed any existing empty text nodes), we can safely unwrap the
			// node's contents, and correct the range if necessary
			// if the node is unwrappable (table, lists) the node is not unwrapped
			// but splitted.
			if (node.parentNode.childNodes.length == 1 && isUnwrappable(node)) {
				newStartOffset = range.startOffset;
				newEndOffset = range.endOffset;

				if (range.startContainer === node.parentNode && range.startOffset > Dom.getIndexInParent(node)) {
					// the node (1 element) will be replaced by its contents (contents().length elements)
					newStartOffset = range.startOffset + (jQuery(node).contents().length - 1);
				}
				if (range.endContainer === node.parentNode && range.endOffset > Dom.getIndexInParent(node)) {
					// the node (1 element) will be replaced by its contents (contents().length elements)
					newEndOffset = range.endOffset + (jQuery(node).contents().length - 1);
				}
				jQuery(node).contents().unwrap();
				range.startOffset = newStartOffset;
				range.endOffset = newEndOffset;
				// after unwrapping, we are done
				break;
			} else {
				// store the original parent
				var originalParent = node.parentNode;
				splitParent([node], range);
				// check whether the parent did not change, so the split did not work, e.g.
				// because we already reached the editing host itself.
				// this situation can occur, e.g. when we insert a paragraph into an contenteditable span
				// in such cases, we just unwrap the contents of the paragraph
				if (originalParent === node.parentNode) {
					// so we unwrap now
					newStartOffset = range.startOffset;
					newEndOffset = range.endOffset;

					if (range.startContainer === node.parentNode && range.startOffset > Dom.getIndexInParent(node)) {
						// the node (1 element) will be replaced by its contents (contents().length elements)
						newStartOffset = range.startOffset + (jQuery(node).contents().length - 1);
					}
					if (range.endContainer === node.parentNode && range.endOffset > Dom.getIndexInParent(node)) {
						// the node (1 element) will be replaced by its contents (contents().length elements)
						newEndOffset = range.endOffset + (jQuery(node).contents().length - 1);
					}
					jQuery(node).contents().unwrap();
					range.startOffset = newStartOffset;
					range.endOffset = newEndOffset;
					// after unwrapping, we are done
					break;
				}
			}
		}

		// "Restore the values from values."
		restoreValues(values, range);
	}

	/**
	 * This method "normalizes" sublists of the given item (which is supposed to be a LI):
	 * If sublists are found in the LI element, they are moved directly into the outer list.
	 * @param item item
	 * @param range range, which will be modified if necessary
	 */
	function normalizeSublists(item, range) {
		// "If item is not an li or it is not editable or its parent is not
		// editable, abort these steps."
		if (!isNamedHtmlElement(item, 'LI') || !isEditable(item) || !isEditable(item.parentNode)) {
			return;
		}

		// "Let new item be null."
		var newItem = null;

		function isOlUl(node) {
			return isHtmlElementInArray(node, ["OL", "UL"]);
		}

		// "While item has an ol or ul child:"
		while ($_(item.childNodes).some(isOlUl)) {
			// "Let child be the last child of item."
			var child = item.lastChild;

			// "If child is an ol or ul, or new item is null and child is a Text
			// node whose data consists of zero of more space characters:"
			if (isHtmlElementInArray(child, ["OL", "UL"]) || (!newItem && child.nodeType == $_.Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {
				// "Set new item to null."
				newItem = null;

				// "Insert child into the parent of item immediately following
				// item, preserving ranges."
				movePreservingRanges(child, item.parentNode, 1 + Dom.getIndexInParent(item), range);

				// "Otherwise:"
			} else {
				// "If new item is null, let new item be the result of calling
				// createElement("li") on the ownerDocument of item, then insert
				// new item into the parent of item immediately after item."
				if (!newItem) {
					newItem = item.ownerDocument.createElement("li");
					item.parentNode.insertBefore(newItem, item.nextSibling);
				}

				// "Insert child into new item as its first child, preserving
				// ranges."
				movePreservingRanges(child, newItem, 0, range);
			}
		}
	}

	/**
	 * This method is the exact opposite of normalizeSublists.
	 * List nodes directly nested into each other are corrected to be nested in li elements (so that the resulting lists conform the html5 specification)
	 * @param item list node
	 * @param range range, which is preserved when modifying the list
	 */
	function unNormalizeSublists(item, range) {
		// "If item is not an ol or ol or it is not editable or its parent is not
		// editable, abort these steps."
		if (!isHtmlElementInArray(item, ["OL", "UL"]) || !isEditable(item)) {
			return;
		}

		var $list = jQuery(item);
		$list.children("ol,ul").each(function (index, sublist) {
			if (isNamedHtmlElement(sublist.previousSibling, "LI")) {
				// move the sublist into the LI
				movePreservingRanges(sublist, sublist.previousSibling, sublist.previousSibling.childNodes.length, range);
			}
		});
	}

	//@}
	///// Block-extending a range /////
	//@{

	function blockExtend(range) {
		// "Let start node, start offset, end node, and end offset be the start
		// and end nodes and offsets of the range."
		var startNode = range.startContainer;
		var startOffset = range.startOffset;
		var endNode = range.endContainer;
		var endOffset = range.endOffset;

		// "If some ancestor container of start node is an li, set start offset to
		// the index of the last such li in tree order, and set start node to that
		// li's parent."
		var liAncestors = $_(getAncestors(startNode).concat(startNode)).filter(function (ancestor) { return isNamedHtmlElement(ancestor, 'li'); }).slice(-1);
		if (liAncestors.length) {
			startOffset = Dom.getIndexInParent(liAncestors[0]);
			startNode = liAncestors[0].parentNode;
		}

		// "If (start node, start offset) is not a block start point, repeat the
		// following steps:"
		if (!isBlockStartPoint(startNode, startOffset)) {
			do {
				// "If start offset is zero, set it to start node's index, then set
				// start node to its parent."
				if (startOffset == 0) {
					startOffset = Dom.getIndexInParent(startNode);
					startNode = startNode.parentNode;

					// "Otherwise, subtract one from start offset."
				} else {
					startOffset--;
				}

				// "If (start node, start offset) is a block boundary point, break from
				// this loop."
			} while (!isBlockBoundaryPoint(startNode, startOffset));
		}

		// "While start offset is zero and start node's parent is not null, set
		// start offset to start node's index, then set start node to its parent."
		while (startOffset == 0 && startNode.parentNode) {
			startOffset = Dom.getIndexInParent(startNode);
			startNode = startNode.parentNode;
		}

		// "If some ancestor container of end node is an li, set end offset to one
		// plus the index of the last such li in tree order, and set end node to
		// that li's parent."
		liAncestors = $_(getAncestors(endNode).concat(endNode)).filter(function (ancestor) { return isNamedHtmlElement(ancestor, 'li'); }).slice(-1);
		if (liAncestors.length) {
			endOffset = 1 + Dom.getIndexInParent(liAncestors[0]);
			endNode = liAncestors[0].parentNode;
		}

		// "If (end node, end offset) is not a block end point, repeat the
		// following steps:"
		if (!isBlockEndPoint(endNode, endOffset)) {
			do {
				// "If end offset is end node's length, set it to one plus end node's
				// index, then set end node to its parent."
				if (endOffset == getNodeLength(endNode)) {
					endOffset = 1 + Dom.getIndexInParent(endNode);
					endNode = endNode.parentNode;

					// "Otherwise, add one to end offset.
				} else {
					endOffset++;
				}

				// "If (end node, end offset) is a block boundary point, break from
				// this loop."
			} while (!isBlockBoundaryPoint(endNode, endOffset));
		}

		// "While end offset is end node's length and end node's parent is not
		// null, set end offset to one plus end node's index, then set end node to
		// its parent."
		while (endOffset == getNodeLength(endNode) && endNode.parentNode) {
			endOffset = 1 + Dom.getIndexInParent(endNode);
			endNode = endNode.parentNode;
		}

		// "Let new range be a new range whose start and end nodes and offsets
		// are start node, start offset, end node, and end offset."
		var newRange = Aloha.createRange();
		newRange.setStart(startNode, startOffset);
		newRange.setEnd(endNode, endOffset);

		// "Return new range."
		return newRange;
	}

	function getSelectionListState() {
		// "Block-extend the active range, and let new range be the result."
		var newRange = blockExtend(getActiveRange());

		// "Let node list be a list of nodes, initially empty."
		//
		// "For each node contained in new range, append node to node list if the
		// last member of node list (if any) is not an ancestor of node; node is
		// editable; node is not an indentation element; and node is either an ol
		// or ul, or the child of an ol or ul, or an allowed child of "li"."
		var nodeList = getContainedNodes(newRange, function (node) {
			return isEditable(node) && !isIndentationElement(node) && (isHtmlElementInArray(node, ["ol", "ul"]) || isHtmlElementInArray(node.parentNode, ["ol", "ul"]) || isAllowedChild(node, "li"));
		});

		// "If node list is empty, return "none"."
		if (!nodeList.length) {
			return "none";
		}

		// "If every member of node list is either an ol or the child of an ol or
		// the child of an li child of an ol, and none is a ul or an ancestor of a
		// ul, return "ol"."
		if ($_(nodeList).every(function (node) { return (isNamedHtmlElement(node, 'ol')
														 || isNamedHtmlElement(node.parentNode, "ol")
														 || (isNamedHtmlElement(node.parentNode, "li")
															 && isNamedHtmlElement(node.parentNode.parentNode, "ol"))); })
			    && !$_(nodeList).some(function (node) { return isNamedHtmlElement(node, 'ul') || (node.querySelector && node.querySelector("ul")); })) {
			return "ol";
		}

		// "If every member of node list is either a ul or the child of a ul or the
		// child of an li child of a ul, and none is an ol or an ancestor of an ol,
		// return "ul"."
		if ($_(nodeList).every(function (node) { return (isNamedHtmlElement(node, 'ul')
														 || isNamedHtmlElement(node.parentNode, "ul")
														 || (isNamedHtmlElement(node.parentNode, "li")
															 && isNamedHtmlElement(node.parentNode.parentNode, "ul"))); })
			    && !$_(nodeList).some(function (node) { return isNamedHtmlElement(node, 'ol') || (node.querySelector && node.querySelector("ol")); })) {
			return "ul";
		}

		var hasOl = $_(nodeList).some(function (node) {
			return (isNamedHtmlElement(node, 'ol')
					|| isNamedHtmlElement(node.parentNode, "ol")
					|| (node.querySelector && node.querySelector("ol"))
					|| (isNamedHtmlElement(node.parentNode, "li")
						&& isNamedHtmlElement(node.parentNode.parentNode, "ol")));
		});
		var hasUl = $_(nodeList).some(function (node) {
			return (isNamedHtmlElement(node, 'ul')
					|| isNamedHtmlElement(node.parentNode, "ul")
					|| (node.querySelector && node.querySelector("ul"))
					|| (isNamedHtmlElement(node.parentNode, "li")
						&& isNamedHtmlElement(node.parentNode.parentNode, "ul")));
		});
		// "If some member of node list is either an ol or the child or ancestor of
		// an ol or the child of an li child of an ol, and some member of node list
		// is either a ul or the child or ancestor of a ul or the child of an li
		// child of a ul, return "mixed"."
		if (hasOl && hasUl) {
			return "mixed";
		}

		// "If some member of node list is either an ol or the child or ancestor of
		// an ol or the child of an li child of an ol, return "mixed ol"."
		if (hasOl) {
			return "mixed ol";
		}

		// "If some member of node list is either a ul or the child or ancestor of
		// a ul or the child of an li child of a ul, return "mixed ul"."
		if (hasUl) {
			return "mixed ul";
		}

		// "Return "none"."
		return "none";
	}

	function getAlignmentValue(node) {
		// "While node is neither null nor an Element, or it is an Element but its
		// "display" property has resolved value "inline" or "none", set node to
		// its parent."
		while ((node && node.nodeType != $_.Node.ELEMENT_NODE) || (node.nodeType == $_.Node.ELEMENT_NODE && jQuery.inArray($_.getComputedStyle(node).display, ["inline", "none"]) != -1)) {
			node = node.parentNode;
		}

		// "If node is not an Element, return "left"."
		if (!node || node.nodeType != $_.Node.ELEMENT_NODE) {
			return "left";
		}

		var resolvedValue = $_.getComputedStyle(node).textAlign
		// Hack around browser non-standardness
			.replace(/^-(moz|webkit)-/, "").replace(/^auto$/, "start");

		// "If node's "text-align" property has resolved value "start", return
		// "left" if the directionality of node is "ltr", "right" if it is "rtl"."
		if (resolvedValue == "start") {
			return getDirectionality(node) == "ltr" ? "left" : "right";
		}

		// "If node's "text-align" property has resolved value "end", return
		// "right" if the directionality of node is "ltr", "left" if it is "rtl"."
		if (resolvedValue == "end") {
			return getDirectionality(node) == "ltr" ? "right" : "left";
		}

		// "If node's "text-align" property has resolved value "center", "justify",
		// "left", or "right", return that value."
		if (jQuery.inArray(resolvedValue, ["center", "justify", "left", "right"]) != -1) {
			return resolvedValue;
		}

		// "Return "left"."
		return "left";
	}

	//@}
	///// Recording and restoring overrides /////
	//@{

	function recordCurrentOverrides(range) {
		// "Let overrides be a list of (string, string or boolean) ordered pairs,
		// initially empty."
		var overrides = [];

		// "If there is a value override for "createLink", add ("createLink", value
		// override for "createLink") to overrides."
		if (getValueOverride("createlink", range) !== undefined) {
			overrides.push(["createlink", getValueOverride("createlink", range)]);
		}

		// "For each command in the list "bold", "italic", "strikethrough",
		// "subscript", "superscript", "underline", in order: if there is a state
		// override for command, add (command, command's state override) to
		// overrides."
		$_(["bold", "italic", "strikethrough", "subscript", "superscript", "underline"]).forEach(function (command) {
			if (getStateOverride(command, range) !== undefined) {
				overrides.push([command, getStateOverride(command, range)]);
			}
		});

		// "For each command in the list "fontName", "fontSize", "foreColor",
		// "hiliteColor", in order: if there is a value override for command, add
		// (command, command's value override) to overrides."
		$_(["fontname", "fontsize", "forecolor", "hilitecolor"]).forEach(function (command) {
			if (getValueOverride(command, range) !== undefined) {
				overrides.push([command, getValueOverride(command, range)]);
			}
		});

		// "Return overrides."
		return overrides;
	}

	function recordCurrentStatesAndValues(range) {
		// "Let overrides be a list of (string, string or boolean) ordered pairs,
		// initially empty."
		var overrides = [];

		// "Let node be the first editable Text node effectively contained in the
		// active range, or null if there is none."
		var node = $_(getAllEffectivelyContainedNodes(range)).filter(function (node) {
			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
		})[0];

		// "If node is null, return overrides."
		if (!node) {
			return overrides;
		}

		// "Add ("createLink", value for "createLink") to overrides."
		overrides.push(["createlink", commands.createlink.value(range)]);

		// "For each command in the list "bold", "italic", "strikethrough",
		// "subscript", "superscript", "underline", in order: if node's effective
		// command value for command is one of its inline command activated values,
		// add (command, true) to overrides, and otherwise add (command, false) to
		// overrides."
		$_(["bold", "italic", "strikethrough", "subscript", "superscript", "underline"]).forEach(function (command) {
			if ($_(commands[command].inlineCommandActivatedValues).indexOf(getEffectiveCommandValue(node, command)) != -1) {
				overrides.push([command, true]);
			} else {
				overrides.push([command, false]);
			}
		});

		// "For each command in the list "fontName", "foreColor", "hiliteColor", in
		// order: add (command, command's value) to overrides."

		$_(["fontname", "fontsize", "forecolor", "hilitecolor"]).forEach(function (command) {
			overrides.push([command, commands[command].value(range)]);
		});

		// "Add ("fontSize", node's effective command value for "fontSize") to
		// overrides."
		overrides.push(["fontsize", getEffectiveCommandValue(node, "fontsize")]);

		// "Return overrides."
		return overrides;
	}

	function restoreStatesAndValues(overrides, range) {
		var i;
		var command;
		var override;
		// "Let node be the first editable Text node effectively contained in the
		// active range, or null if there is none."
		var node = $_(getAllEffectivelyContainedNodes(range)).filter(function (node) {
			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
		})[0];

		function isEditableTextNode(node) {
			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
		}

		// "If node is not null, then for each (command, override) pair in
		// overrides, in order:"
		if (node) {

			for (i = 0; i < overrides.length; i++) {
				command = overrides[i][0];
				override = overrides[i][1];

				// "If override is a boolean, and queryCommandState(command)
				// returns something different from override, call
				// execCommand(command)."
				if (typeof override == "boolean" && myQueryCommandState(command, range) != override) {
					myExecCommand(command, false, override, range);

					// "Otherwise, if override is a string, and command is not
					// "fontSize", and queryCommandValue(command) returns something not
					// equivalent to override, call execCommand(command, false,
					// override)."
				} else if (typeof override == "string" && command != "fontsize" && !areEquivalentValues(command, myQueryCommandValue(command, range), override)) {
					myExecCommand(command, false, override, range);

					// "Otherwise, if override is a string; and command is "fontSize";
					// and either there is a value override for "fontSize" that is not
					// equal to override, or there is no value override for "fontSize"
					// and node's effective command value for "fontSize" is not loosely
					// equivalent to override: call execCommand("fontSize", false,
					// override)."
				} else if (typeof override == "string"
						   && command == "fontsize"
						   && ((getValueOverride("fontsize", range) !== undefined
								&& getValueOverride("fontsize", range) !== override)
							   || (getValueOverride("fontsize", range) === undefined
								   && !areLooselyEquivalentValues(command, getEffectiveCommandValue(node, "fontsize"), override)))) {
					myExecCommand("fontsize", false, override, range);

					// "Otherwise, continue this loop from the beginning."
				} else {
					continue;
				}

				// "Set node to the first editable Text node effectively contained
				// in the active range, if there is one."
				node = $_(getAllEffectivelyContainedNodes(range)).filter(isEditableTextNode)[0] || node;
			}

			// "Otherwise, for each (command, override) pair in overrides, in order:"
		} else {
			for (i = 0; i < overrides.length; i++) {
				command = overrides[i][0];
				override = overrides[i][1];

				// "If override is a boolean, set the state override for command to
				// override."
				if (typeof override == "boolean") {
					setStateOverride(command, override, range);
				}

				// "If override is a string, set the value override for command to
				// override."
				if (typeof override == "string") {
					setValueOverride(command, override, range);
				}
			}
		}
	}

	//@}
	///// Canonical space sequences /////
	//@{

	function canonicalSpaceSequence(n, nonBreakingStart, nonBreakingEnd) {
		// "If n is zero, return the empty string."
		if (n == 0) {
			return "";
		}

		// "If n is one and both non-breaking start and non-breaking end are false,
		// return a single space (U+0020)."
		if (n == 1 && !nonBreakingStart && !nonBreakingEnd) {
			return " ";
		}

		// "If n is one, return a single non-breaking space (U+00A0)."
		if (n == 1) {
			return "\xa0";
		}

		// "Let buffer be the empty string."
		var buffer = "";

		// "If non-breaking start is true, let repeated pair be U+00A0 U+0020.
		// Otherwise, let it be U+0020 U+00A0."
		var repeatedPair;
		if (nonBreakingStart) {
			repeatedPair = "\xa0 ";
		} else {
			repeatedPair = " \xa0";
		}

		// "While n is greater than three, append repeated pair to buffer and
		// subtract two from n."
		while (n > 3) {
			buffer += repeatedPair;
			n -= 2;
		}

		// "If n is three, append a three-element string to buffer depending on
		// non-breaking start and non-breaking end:"
		if (n == 3) {
			buffer += !nonBreakingStart && !nonBreakingEnd ? " \xa0 " : nonBreakingStart && !nonBreakingEnd ? "\xa0\xa0 " : !nonBreakingStart && nonBreakingEnd ? " \xa0\xa0" : nonBreakingStart && nonBreakingEnd ? "\xa0 \xa0" : "impossible";

			// "Otherwise, append a two-element string to buffer depending on
			// non-breaking start and non-breaking end:"
		} else {
			buffer += !nonBreakingStart && !nonBreakingEnd ? "\xa0 " : nonBreakingStart && !nonBreakingEnd ? "\xa0 " : !nonBreakingStart && nonBreakingEnd ? " \xa0" : nonBreakingStart && nonBreakingEnd ? "\xa0\xa0" : "impossible";
		}

		// "Return buffer."
		return buffer;
	}

	function canonicalizeWhitespace(node, offset) {
		// "If node is neither editable nor an editing host, abort these steps."
		if (!isEditable(node) && !isEditingHost(node)) {
			return;
		}

		// "Let start node equal node and let start offset equal offset."
		var startNode = node;
		var startOffset = offset;

		// "Repeat the following steps:"
		while (true) {
			// "If start node has a child in the same editing host with index start
			// offset minus one, set start node to that child, then set start
			// offset to start node's length."
			if (0 <= startOffset - 1 && inSameEditingHost(startNode, startNode.childNodes[startOffset - 1])) {
				startNode = startNode.childNodes[startOffset - 1];
				startOffset = getNodeLength(startNode);

				// "Otherwise, if start offset is zero and start node does not follow a
				// line break and start node's parent is in the same editing host, set
				// start offset to start node's index, then set start node to its
				// parent."
			} else if (startOffset == 0 && !followsLineBreak(startNode) && inSameEditingHost(startNode, startNode.parentNode)) {
				startOffset = Dom.getIndexInParent(startNode);
				startNode = startNode.parentNode;

				// "Otherwise, if start node is a Text node and its parent's resolved
				// value for "white-space" is neither "pre" nor "pre-wrap" and start
				// offset is not zero and the (start offset − 1)st element of start
				// node's data is a space (0x0020) or non-breaking space (0x00A0),
				// subtract one from start offset."
			} else if (startNode.nodeType == $_.Node.TEXT_NODE && jQuery.inArray($_.getComputedStyle(startNode.parentNode).whiteSpace, ["pre", "pre-wrap"]) == -1 && startOffset != 0 && /[ \xa0]/.test(startNode.data[startOffset - 1])) {
				startOffset--;

				// "Otherwise, break from this loop."
			} else {
				break;
			}
		}

		// "Let end node equal start node and end offset equal start offset."
		var endNode = startNode;
		var endOffset = startOffset;

		// "Let length equal zero."
		var length = 0;

		// "Let follows space be false."
		var followsSpace = false;

		// "Repeat the following steps:"
		while (true) {
			// "If end node has a child in the same editing host with index end
			// offset, set end node to that child, then set end offset to zero."
			if (endOffset < endNode.childNodes.length && inSameEditingHost(endNode, endNode.childNodes[endOffset])) {
				endNode = endNode.childNodes[endOffset];
				endOffset = 0;

				// "Otherwise, if end offset is end node's length and end node does not
				// precede a line break and end node's parent is in the same editing
				// host, set end offset to one plus end node's index, then set end node
				// to its parent."
			} else if (endOffset == getNodeLength(endNode) && !precedesLineBreak(endNode) && inSameEditingHost(endNode, endNode.parentNode)) {
				endOffset = 1 + Dom.getIndexInParent(endNode);
				endNode = endNode.parentNode;

				// "Otherwise, if end node is a Text node and its parent's resolved
				// value for "white-space" is neither "pre" nor "pre-wrap" and end
				// offset is not end node's length and the end offsetth element of
				// end node's data is a space (0x0020) or non-breaking space (0x00A0):"
			} else if (endNode.nodeType == $_.Node.TEXT_NODE && jQuery.inArray($_.getComputedStyle(endNode.parentNode).whiteSpace, ["pre", "pre-wrap"]) == -1 && endOffset != getNodeLength(endNode) && /[ \xa0]/.test(endNode.data[endOffset])) {
				// "If follows space is true and the end offsetth element of end
				// node's data is a space (0x0020), call deleteData(end offset, 1)
				// on end node, then continue this loop from the beginning."
				if (followsSpace && " " == endNode.data[endOffset]) {
					endNode.deleteData(endOffset, 1);
					continue;
				}

				// "Set follows space to true if the end offsetth element of end
				// node's data is a space (0x0020), false otherwise."
				followsSpace = " " == endNode.data[endOffset];

				// "Add one to end offset."
				endOffset++;

				// "Add one to length."
				length++;

				// "Otherwise, break from this loop."
			} else {
				break;
			}
		}

		// "Let replacement whitespace be the canonical space sequence of length
		// length. non-breaking start is true if start offset is zero and start
		// node follows a line break, and false otherwise. non-breaking end is true
		// if end offset is end node's length and end node precedes a line break,
		// and false otherwise."
		// Correction: The above specification rule has been implemented slightly different,
		// because the line breaks at start/end should not be taken in account if startNode
		// or endNode are editing hosts.
		var isNonBreakingStart    = startOffset == 0 &&
				(followsLineBreak(startNode) || isEditingHost(startNode));
		var isNonBreakingEnd      = endOffset == getNodeLength(endNode)
				&& (precedesLineBreak(endNode) || isEditingHost(endNode));
		var replacementWhitespace = canonicalSpaceSequence(length, isNonBreakingStart, isNonBreakingEnd);

		// "While (start node, start offset) is before (end node, end offset):"
		while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {
			// "If start node has a child with index start offset, set start node
			// to that child, then set start offset to zero."
			if (startOffset < startNode.childNodes.length) {
				startNode = startNode.childNodes[startOffset];
				startOffset = 0;

				// "Otherwise, if start node is not a Text node or if start offset is
				// start node's length, set start offset to one plus start node's
				// index, then set start node to its parent."
			} else if (startNode.nodeType != $_.Node.TEXT_NODE || startOffset == getNodeLength(startNode)) {
				startOffset = 1 + Dom.getIndexInParent(startNode);
				startNode = startNode.parentNode;

				// "Otherwise:"
			} else {
				// "Remove the first element from replacement whitespace, and let
				// element be that element."
				var element = replacementWhitespace[0];
				replacementWhitespace = replacementWhitespace.slice(1);

				// "If element is not the same as the start offsetth element of
				// start node's data:"
				if (element != startNode.data[startOffset]) {
					// "Call insertData(start offset, element) on start node."
					startNode.insertData(startOffset, element);

					// "Call deleteData(start offset + 1, 1) on start node."
					startNode.deleteData(startOffset + 1, 1);
				}

				// "Add one to start offset."
				startOffset++;
			}
		}
	}

	/**
	 * Remove the node if it doesn't have any children or 1 empty text node.
	 * This function doesn't handle all cases of unrendered nodes.
	 * In the following cases the node will not be removed:
	 * * The node contains multiple empty nodes
	 * * The child text node contains zero width characters
	 * * The child text node contains unrendered characters
	 *
	 * @param  {Text}  node   The node to check
	 * @param  {Element}  range  Range object to set the position
	 * @return {boolean} True if node got removed, false otherwise
	 */
	function removeNodeIfEmptyAndCorrectRange(node, range) {
		var
			removeNode = false,
			parentNode,
			nodeChildNodesLength,
			offsetInParent,
			firstChildNode;

		if (!node || !range) {
			return false;
		}

		if (isEditingHost(node)) {
			return false;
		}

		parentNode = node.parentNode;

		if (!parentNode) {
			return false;
		}

		// Check if the node is empty
		nodeChildNodesLength = node.childNodes.length;
		if (nodeChildNodesLength === 0) {
			removeNode = true;
		}

		// If the inline node only contains an empty text node,
		// it is also considered being empty.
		if (nodeChildNodesLength === 1) {
			firstChildNode = node.childNodes[0];

			if (firstChildNode.nodeType === $_.Node.TEXT_NODE
					&& firstChildNode.nodeValue.length === 0) {
				removeNode = true;
			}
		}

		// Don't continue if node should not be removed or has no parent
		if (!removeNode) {
			return false;
		}

		// Before removing, remember the offset of node in its parent
		offsetInParent = Dom.getIndexInParent(node);

		// I'm not gonna comment this, it's clear what it does.
		parentNode.removeChild(node);

		// Now the cursor has to be set at the position where the
		// removed parent was before.
		range.setStart(parentNode, offsetInParent);
		range.setEnd(parentNode, offsetInParent);

		return true;
	}

	//@}
	///// Deleting the contents of a range /////
	//@{

	function deleteContents(arg1, arg2, arg3, arg4, arg5) {
		// We accept several different calling conventions:
		//
		// 1) A single argument, which is a range.
		//
		// 2) Two arguments, the first being a range and the second flags.
		//
		// 3) Four arguments, the start and end of a range.
		//
		// 4) Five arguments, the start and end of a range plus flags.
		//
		// The flags argument is a dictionary that can have up to two keys,
		// blockMerging and stripWrappers, whose corresponding values are
		// interpreted as boolean.  E.g., {stripWrappers: false}.
		var range;
		var flags = {};
		var i;

		if (arguments.length < 3) {
			range = arg1;
		} else {
			range = Aloha.createRange();
			range.setStart(arg1, arg2);
			range.setEnd(arg3, arg4);
		}
		if (arguments.length == 2) {
			flags = arg2;
		}
		if (arguments.length == 5) {
			flags = arg5;
		}

		var blockMerging = null != flags.blockMerging ? !!flags.blockMerging : true;
		var stripWrappers = null != flags.stripWrappers ? !!flags.stripWrappers : true;

		// "If range is null, abort these steps and do nothing."
		if (!range) {
			return;
		}

		// "Let start node, start offset, end node, and end offset be range's start
		// and end nodes and offsets."
		var startNode = range.startContainer;
		var startOffset = range.startOffset;
		var endNode = range.endContainer;
		var endOffset = range.endOffset;
		var referenceNode;

		// "While start node has at least one child:"
		while (startNode.hasChildNodes()) {
			// "If start offset is start node's length, and start node's parent is
			// in the same editing host, and start node is an inline node, set
			// start offset to one plus the index of start node, then set start
			// node to its parent and continue this loop from the beginning."
			if (startOffset == getNodeLength(startNode) && inSameEditingHost(startNode, startNode.parentNode) && isInlineNode(startNode)) {
				startOffset = 1 + Dom.getIndexInParent(startNode);
				startNode = startNode.parentNode;
				continue;
			}

			// "If start offset is start node's length, break from this loop."
			if (startOffset == getNodeLength(startNode)) {
				break;
			}

			// "Let reference node be the child of start node with index equal to
			// start offset."
			referenceNode = startNode.childNodes[startOffset];

			// "If reference node is a block node or an Element with no children,
			// or is neither an Element nor a Text node, break from this loop."
			if (isBlockNode(referenceNode) || (referenceNode.nodeType == $_.Node.ELEMENT_NODE && !referenceNode.hasChildNodes()) || (referenceNode.nodeType != $_.Node.ELEMENT_NODE && referenceNode.nodeType != $_.Node.TEXT_NODE)) {
				break;
			}

			// "Set start node to reference node and start offset to 0."
			startNode = referenceNode;
			startOffset = 0;
		}

		// "While end node has at least one child:"
		while (endNode.hasChildNodes()) {
			// "If end offset is 0, and end node's parent is in the same editing
			// host, and end node is an inline node, set end offset to the index of
			// end node, then set end node to its parent and continue this loop
			// from the beginning."
			if (endOffset == 0 && inSameEditingHost(endNode, endNode.parentNode) && isInlineNode(endNode)) {
				endOffset = Dom.getIndexInParent(endNode);
				endNode = endNode.parentNode;
				continue;
			}

			// "If end offset is 0, break from this loop."
			if (endOffset == 0) {
				break;
			}

			// "Let reference node be the child of end node with index equal to end
			// offset minus one."
			referenceNode = endNode.childNodes[endOffset - 1];

			// "If reference node is a block node or an Element with no children,
			// or is neither an Element nor a Text node, break from this loop."
			if (isBlockNode(referenceNode) || (referenceNode.nodeType == $_.Node.ELEMENT_NODE && !referenceNode.hasChildNodes()) || (referenceNode.nodeType != $_.Node.ELEMENT_NODE && referenceNode.nodeType != $_.Node.TEXT_NODE)) {
				break;
			}

			// "Set end node to reference node and end offset to the length of
			// reference node."
			endNode = referenceNode;
			endOffset = getNodeLength(referenceNode);
		}

		// "If (end node, end offset) is not after (start node, start offset), set
		// range's end to its start and abort these steps."
		if (getPosition(endNode, endOffset, startNode, startOffset) !== "after") {
			range.setEnd(range.startContainer, range.startOffset);
			return range;
		}

		// "If start node is a Text node and start offset is 0, set start offset to
		// the index of start node, then set start node to its parent."
		// Commented out for unknown reason
		//if (startNode.nodeType == $_.Node.TEXT_NODE && startOffset == 0 && startNode != endNode) {
		//		startOffset = Dom.getIndexInParent(startNode);
		//		startNode = startNode.parentNode;
		//}

		// "If end node is a Text node and end offset is its length, set end offset
		// to one plus the index of end node, then set end node to its parent."
		if (endNode.nodeType == $_.Node.TEXT_NODE && endOffset == getNodeLength(endNode) && startNode != endNode) {
			endOffset = 1 + Dom.getIndexInParent(endNode);
			endNode = endNode.parentNode;
		}

		// "Set range's start to (start node, start offset) and its end to (end
		// node, end offset)."
		range.setStart(startNode, startOffset);
		range.setEnd(endNode, endOffset);

		// "Let start block be the start node of range."
		var startBlock = range.startContainer;

		// "While start block's parent is in the same editing host and start block
		// is an inline node, set start block to its parent."
		while (inSameEditingHost(startBlock, startBlock.parentNode) && isInlineNode(startBlock)) {
			startBlock = startBlock.parentNode;
		}

		// "If start block is neither a block node nor an editing host, or "span"
		// is not an allowed child of start block, or start block is a td or th,
		// set start block to null."
		if ((!isBlockNode(startBlock) && !isEditingHost(startBlock)) || !isAllowedChild("span", startBlock) || isHtmlElementInArray(startBlock, ["td", "th"])) {
			startBlock = null;
		}

		// "Let end block be the end node of range."
		var endBlock = range.endContainer;

		// "While end block's parent is in the same editing host and end block is
		// an inline node, set end block to its parent."
		while (inSameEditingHost(endBlock, endBlock.parentNode) && isInlineNode(endBlock)) {
			endBlock = endBlock.parentNode;
		}

		// "If end block is neither a block node nor an editing host, or "span" is
		// not an allowed child of end block, or end block is a td or th, set end
		// block to null."
		if ((!isBlockNode(endBlock) && !isEditingHost(endBlock)) || !isAllowedChild("span", endBlock) || isHtmlElementInArray(endBlock, ["td", "th"])) {
			endBlock = null;
		}

		// "Record current states and values, and let overrides be the result."
		var overrides = recordCurrentStatesAndValues(range);
		var parent_;
		// "If start node and end node are the same, and start node is an editable
		// Text node:"
		if (startNode == endNode && isEditable(startNode) && startNode.nodeType == $_.Node.TEXT_NODE) {
			// "Let parent be the parent of node."
			parent_ = startNode.parentNode;

			// "Call deleteData(start offset, end offset − start offset) on start
			// node."
			startNode.deleteData(startOffset, endOffset - startOffset);

			// if deleting the text moved two spaces together, we replace the left one by a &nbsp;, which makes the two spaces a visible
			// two space sequence
			if (startOffset > 0 && startNode.data.substr(startOffset - 1, 1) === ' ' && startOffset < startNode.data.length && startNode.data.substr(startOffset, 1) === ' ') {
				startNode.replaceData(startOffset - 1, 1, '\xa0');
			}

			// "Canonicalize whitespace at (start node, start offset)."
			canonicalizeWhitespace(startNode, startOffset);

			// Remove the parent node, if it is an inline node and empty
			// If the node is inline and empty, range correction will happen inside
			// removeNodeIfEmptyAndCorrectRange, otherwise it'will be done below.
			if (!isInlineNode(parent_) || !removeNodeIfEmptyAndCorrectRange(parent_, range)) {
				// "Set range's end to its start."
				// Ok, also set the range's start to its start, because modifying the text
				// might have somehow corrupted the range
				range.setStart(range.startContainer, range.startOffset);
				range.setEnd(range.startContainer, range.startOffset);
			}

			// "Restore states and values from overrides."
			restoreStatesAndValues(overrides, range);

			// "If parent is editable or an editing host, is not an inline node,
			// and has no children, call createElement("br") on the context object
			// and append the result as the last child of parent."
			// only do this, if the offsetHeight is 0
			if ((isEditable(parent_) || isEditingHost(parent_)) && !isInlineNode(parent_)) {
				ensureContainerEditable(parent_);
			}

			// "Abort these steps."
			return range;
		}

		// "If start node is an editable Text node, call deleteData() on it, with
		// start offset as the first argument and (length of start node − start
		// offset) as the second argument."
		if (isEditable(startNode) && startNode.nodeType == $_.Node.TEXT_NODE) {
			startNode.deleteData(startOffset, getNodeLength(startNode) - startOffset);
		}

		// "Let node list be a list of nodes, initially empty."
		//
		// "For each node contained in range, append node to node list if the last
		// member of node list (if any) is not an ancestor of node; node is
		// editable; and node is not a thead, tbody, tfoot, tr, th, or td."
		var nodeList = getContainedNodes(
			range,
			function (node) {
				return isEditable(node) && !isHtmlElementInArray(node, ["thead", "tbody", "tfoot", "tr", "th", "td"]);
			}
		);

		// "For each node in node list:"
		for (i = 0; i < nodeList.length; i++) {
			var node = nodeList[i];

			// "Let parent be the parent of node."
			parent_ = node.parentNode;

			// "Remove node from parent."
			parent_.removeChild(node);

			// "If strip wrappers is true or parent is not an ancestor container of
			// start node, while parent is an editable inline node with length 0,
			// let grandparent be the parent of parent, then remove parent from
			// grandparent, then set parent to grandparent."
			if (stripWrappers || (!isAncestor(parent_, startNode) && parent_ != startNode)) {
				while (isEditable(parent_) && isInlineNode(parent_) && getNodeLength(parent_) == 0) {
					var grandparent = parent_.parentNode;
					grandparent.removeChild(parent_);
					parent_ = grandparent;
				}
			}

			// "If parent is editable or an editing host, is not an inline node,
			// and has no children, call createElement("br") on the context object
			// and append the result as the last child of parent."
			// only do this, if the offsetHeight is 0
			if ((isEditable(parent_) || isEditingHost(parent_)) && !isInlineNode(parent_)) {
				ensureContainerEditable(parent_);
			}
		}

		// "If end node is an editable Text node, call deleteData(0, end offset) on
		// it."
		if (isEditable(endNode) && endNode.nodeType == $_.Node.TEXT_NODE) {
			endNode.deleteData(0, endOffset);
		}

		// Remove parentNode, if it is an empty inline node
		if (isInlineNode(startNode.parentNode)) {
			removeNodeIfEmptyAndCorrectRange(startNode.parentNode, range);
		}

		// "Canonicalize whitespace at range's start."
		canonicalizeWhitespace(range.startContainer, range.startOffset);

		// "Canonicalize whitespace at range's end."
		canonicalizeWhitespace(range.endContainer, range.endOffset);

		// A reference to the position where a node is removed.
		var pos;

		// "If block merging is false, or start block or end block is null, or
		// start block is not in the same editing host as end block, or start block
		// and end block are the same:"
		if (!blockMerging || !startBlock || !endBlock || !inSameEditingHost(startBlock, endBlock) || startBlock == endBlock) {
			// "Set range's end to its start."
			range.setEnd(range.startContainer, range.startOffset);

			// Calling delete on the give markup:
			// <editable><block><br>[]</block></editable>
			// should result in:
			// <editable>[]</editable>
			var block = startBlock || endBlock;
			if (isEmptyOnlyChildOfEditingHost(block)) {
				pos = removeNode(block);
				range.setStart(pos.node, pos.offset);
				range.setEnd(pos.node, pos.offset);
			}

			// "Restore states and values from overrides."
			restoreStatesAndValues(overrides, range);

			// "Abort these steps."
			return range;
		}

		// "If start block has one child, which is a collapsed block prop, remove
		// its child from it."
		if (startBlock.children.length == 1 && isCollapsedBlockProp(startBlock.firstChild)) {
			startBlock.removeChild(startBlock.firstChild);
		}

		// "If end block has one child, which is a collapsed block prop, remove its
		// child from it."
		if (endBlock.children.length == 1 && isCollapsedBlockProp(endBlock.firstChild)) {
			endBlock.removeChild(endBlock.firstChild);
		}

		var values;
		// "If start block is an ancestor of end block:"
		if (isAncestor(startBlock, endBlock)) {
			// "Let reference node be end block."
			referenceNode = endBlock;

			// "While reference node is not a child of start block, set reference
			// node to its parent."
			while (referenceNode.parentNode != startBlock) {
				referenceNode = referenceNode.parentNode;
			}

			// "Set the start and end of range to (start block, index of reference
			// node)."
			range.setStart(startBlock, Dom.getIndexInParent(referenceNode));
			range.setEnd(startBlock, Dom.getIndexInParent(referenceNode));

			// "If end block has no children:"
			if (!endBlock.hasChildNodes()) {
				// "While end block is editable and is the only child of its parent
				// and is not a child of start block, let parent equal end block,
				// then remove end block from parent, then set end block to
				// parent."
				while (isEditable(endBlock) && endBlock.parentNode.childNodes.length == 1 && endBlock.parentNode != startBlock) {
					parent_ = endBlock;
					parent_.removeChild(endBlock);
					endBlock = parent_;
				}

				// "If end block is editable and is not an inline node, and its
				// previousSibling and nextSibling are both inline nodes, call
				// createElement("br") on the context object and insert it into end
				// block's parent immediately after end block."

				if (isEditable(endBlock) && !isInlineNode(endBlock) && isInlineNode(endBlock.previousSibling) && isInlineNode(endBlock.nextSibling)) {
					endBlock.parentNode.insertBefore(document.createElement("br"), endBlock.nextSibling);
				}

				// "If end block is editable, remove it from its parent."
				if (isEditable(endBlock)) {
					endBlock.parentNode.removeChild(endBlock);
				}

				// "Restore states and values from overrides."
				restoreStatesAndValues(overrides, range);

				// "Abort these steps."
				return range;
			}

			// "If end block's firstChild is not an inline node, restore states and
			// values from overrides, then abort these steps."
			if (!isInlineNode(endBlock.firstChild)) {
				restoreStatesAndValues(overrides, range);
				return range;
			}

			// "Let children be a list of nodes, initially empty."
			var children = [];

			// "Append the first child of end block to children."
			children.push(endBlock.firstChild);

			// "While children's last member is not a br, and children's last
			// member's nextSibling is an inline node, append children's last
			// member's nextSibling to children."
			while (!isNamedHtmlElement(children[children.length - 1], "br") && isInlineNode(children[children.length - 1].nextSibling)) {
				children.push(children[children.length - 1].nextSibling);
			}

			// "Record the values of children, and let values be the result."
			values = recordValues(children);

			// "While children's first member's parent is not start block, split
			// the parent of children."
			while (children[0].parentNode != startBlock) {
				splitParent(children, range);
			}

			// "If children's first member's previousSibling is an editable br,
			// remove that br from its parent."
			if (isEditable(children[0].previousSibling) && isNamedHtmlElement(children[0].previousSibling, "br")) {
				children[0].parentNode.removeChild(children[0].previousSibling);
			}

			// "Otherwise, if start block is a descendant of end block:"
		} else if (isDescendant(startBlock, endBlock)) {
			// "Set the start and end of range to (start block, length of start
			// block)."
			range.setStart(startBlock, getNodeLength(startBlock));
			range.setEnd(startBlock, getNodeLength(startBlock));

			// "Let reference node be start block."
			referenceNode = startBlock;

			// "While reference node is not a child of end block, set reference
			// node to its parent."
			while (referenceNode.parentNode != endBlock) {
				referenceNode = referenceNode.parentNode;
			}

			// "If reference node's nextSibling is an inline node and start block's
			// lastChild is a br, remove start block's lastChild from it."
			if (isInlineNode(referenceNode.nextSibling) && isNamedHtmlElement(startBlock.lastChild, "br")) {
				startBlock.removeChild(startBlock.lastChild);
			}

			// "Let nodes to move be a list of nodes, initially empty."
			var nodesToMove = [];

			// "If reference node's nextSibling is neither null nor a br nor a
			// block node, append it to nodes to move."
			if (referenceNode.nextSibling && !isNamedHtmlElement(referenceNode.nextSibling, "br") && !isBlockNode(referenceNode.nextSibling)) {
				nodesToMove.push(referenceNode.nextSibling);
			}

			// "While nodes to move is nonempty and its last member's nextSibling
			// is neither null nor a br nor a block node, append it to nodes to
			// move."
			if (nodesToMove.length && nodesToMove[nodesToMove.length - 1].nextSibling && !isNamedHtmlElement(nodesToMove[nodesToMove.length - 1].nextSibling, "br") && !isBlockNode(nodesToMove[nodesToMove.length - 1].nextSibling)) {
				nodesToMove.push(nodesToMove[nodesToMove.length - 1].nextSibling);
			}

			// "Record the values of nodes to move, and let values be the result."
			values = recordValues(nodesToMove);

			// "For each node in nodes to move, append node as the last child of
			// start block, preserving ranges."
			$_(nodesToMove).forEach(function (node) {
				movePreservingRanges(node, startBlock, -1, range);
			});

			// "If the nextSibling of reference node is a br, remove it from its
			// parent."
			if (isNamedHtmlElement(referenceNode.nextSibling, "br")) {
				referenceNode.parentNode.removeChild(referenceNode.nextSibling);
			}

			// "Otherwise:"
		} else {
			// "Set the start and end of range to (start block, length of start
			// block)."
			range.setStart(startBlock, getNodeLength(startBlock));
			range.setEnd(startBlock, getNodeLength(startBlock));

			// "If end block's firstChild is an inline node and start block's
			// lastChild is a br, remove start block's lastChild from it."
			if (isInlineNode(endBlock.firstChild) && isNamedHtmlElement(startBlock.lastChild, "br")) {
				startBlock.removeChild(startBlock.lastChild);
			}

			// "Record the values of end block's children, and let values be the
			// result."
			values = recordValues([].slice.call(toArray(endBlock.childNodes)));

			// "While end block has children, append the first child of end block
			// to start block, preserving ranges."
			while (endBlock.hasChildNodes()) {
				movePreservingRanges(endBlock.firstChild, startBlock, -1, range);
			}

			// "While end block has no children, let parent be the parent of end
			// block, then remove end block from parent, then set end block to
			// parent."
			while (!endBlock.hasChildNodes()) {
				parent_ = endBlock.parentNode;
				parent_.removeChild(endBlock);
				endBlock = parent_;
			}
		}

		// "Restore the values from values."
		restoreValues(values, range);

		// Because otherwise calling deleteContents() with the given selection:
		//
		// <editable><block>[foo</block><block>bar]</block></editable>
		//
		// would result in:
		//
		// <editable><block>[]<br /></block></editable>
		//
		// instead of:
		//
		// <editable>[]</editable>
		//
		// Therefore, the below makes it possible to completely empty contents
		// of editing hosts via operations like CTRL+A, DEL.
		//
		// If startBlock is empty, and startBlock is the immediate and only
		// child of its parent editing host, then remove startBlock and collapse
		// the selection at the beginning of the editing post.
		if (isEmptyOnlyChildOfEditingHost(startBlock)) {
			pos = removeNode(startBlock);
			range.setStart(pos.node, pos.offset);
			range.setEnd(pos.node, pos.offset);
			startBlock = pos.node;
		}

		// "If start block has no children, call createElement("br") on the context
		// object and append the result as the last child of start block."
		ensureContainerEditable(startBlock);

		// "Restore states and values from overrides."
		restoreStatesAndValues(overrides, range);

		return range;
	}

	// "To remove a node node while preserving its descendants, split the parent of
	// node's children if it has any. If it has no children, instead remove it from
	// its parent."
	function removePreservingDescendants(node, range) {
		if (node.hasChildNodes()) {
			splitParent([].slice.call(toArray(node.childNodes)), range);
		} else {
			node.parentNode.removeChild(node);
		}
	}

	/**
	 * Determines whether a node must be removed to make the insertparagraph command work in list contexts.
	 *
	 * @param {Node} node DOM node to check
	 * @return {boolean} true if the node should be deleted
	 */
	function isListWhitespaceToRemove(node) {
		// no whitespace node –> no need to remove
		if (!isWhitespaceNode(node)) {
			return false;
		}

		// always remove whitespace children from ul and ol
		if (node.parentNode.nodeName === 'UL' || node.parentNode.nodeName === 'OL') {
			return true;
		}

		if (node.parentNode.nodeName !== 'LI') {
			return false;
		}

		// only remove whitespace from li if it is between (the opening tag/a block level element)
		// and (a block level element/the closing tag)
		var index = Dom.getIndexInParent(node);
		var last = node.parentNode.childNodes.length - 1;
		var cleanElements = Dom.blockLevelElements.concat(Dom.listElements);

		return (index === 0 ||
		         cleanElements.indexOf(node.previousSibling.nodeName.toLowerCase()) > -1) &&
		       (index === last ||
		         cleanElements.indexOf(node.nextSibling.nodeName.toLowerCase()) > -1);
	}

	//@}
	///// Indenting and outdenting /////
	//@{

	function cleanLists(node, range) {
		// remove any whitespace nodes around list nodes
		if (node) {
			jQuery(node).find('ul,ol,li').each(function () {
				jQuery(this).contents().each(function () {
					if (isListWhitespaceToRemove(this)) {
						var index = Dom.getIndexInParent(this);

						// if the range points to somewhere behind the removed text node, we reduce the offset
						if (range.startContainer === this.parentNode && range.startOffset > index) {
							range.startOffset--;
						} else if (range.startContainer === this) {
							// the range starts in the removed text node, let it start right before
							range.startContainer = this.parentNode;
							range.startOffset = index;
						}
						// same thing for end of the range
						if (range.endContainer === this.parentNode && range.endOffset > index) {
							range.endOffset--;
						} else if (range.endContainer === this) {
							range.endContainer = this.parentNode;
							range.endOffset = index;
						}
						// finally remove the whitespace node
						jQuery(this).remove();
					}
				});
			});
		}
	}


	//@}
	///// Indenting and outdenting /////
	//@{

	function indentNodes(nodeList, range) {
		// "If node list is empty, do nothing and abort these steps."
		if (!nodeList.length) {
			return;
		}

		// "Let first node be the first member of node list."
		var firstNode = nodeList[0];

		// "If first node's parent is an ol or ul:"
		if (isHtmlElementInArray(firstNode.parentNode, ["OL", "UL"])) {
			// "Let tag be the local name of the parent of first node."
			var tag = firstNode.parentNode.tagName;

			// "Wrap node list, with sibling criteria returning true for an HTML
			// element with local name tag and false otherwise, and new parent
			// instructions returning the result of calling createElement(tag) on
			// the ownerDocument of first node."
			wrap(
				nodeList,
				function (node) {
					return isHtmlElement_obsolete(node, tag);
				},
				function () {
					return firstNode.ownerDocument.createElement(tag);
				},
				range
			);

			// "Abort these steps."
			return;
		}

		// "Wrap node list, with sibling criteria returning true for a simple
		// indentation element and false otherwise, and new parent instructions
		// returning the result of calling createElement("blockquote") on the
		// ownerDocument of first node. Let new parent be the result."
		var newParent = wrap(
			nodeList,
			function (node) {
				return isSimpleIndentationElement(node);
			},
			function () {
				return firstNode.ownerDocument.createElement("blockquote");
			},
			range
		);

		// "Fix disallowed ancestors of new parent."
		fixDisallowedAncestors(newParent, range);
	}

	function outdentNode(node, range) {
		// "If node is not editable, abort these steps."
		if (!isEditable(node)) {
			return;
		}

		// "If node is a simple indentation element, remove node, preserving its
		// descendants.  Then abort these steps."
		if (isSimpleIndentationElement(node)) {
			removePreservingDescendants(node, range);
			return;
		}

		// "If node is an indentation element:"
		if (isIndentationElement(node)) {
			// "Unset the class and dir attributes of node, if any."
			node.removeAttribute("class");
			node.removeAttribute("dir");

			// "Unset the margin, padding, and border CSS properties of node."
			node.style.margin = "";
			node.style.padding = "";
			node.style.border = "";
			if (node.getAttribute("style") == "") {
				node.removeAttribute("style");
			}

			// "Set the tag name of node to "div"."
			setTagName(node, "div", range);

			// "Abort these steps."
			return;
		}

		// "Let current ancestor be node's parent."
		var currentAncestor = node.parentNode;

		// "Let ancestor list be a list of nodes, initially empty."
		var ancestorList = [];

		// "While current ancestor is an editable Element that is neither a simple
		// indentation element nor an ol nor a ul, append current ancestor to
		// ancestor list and then set current ancestor to its parent."
		while (isEditable(currentAncestor) && currentAncestor.nodeType == $_.Node.ELEMENT_NODE && !isSimpleIndentationElement(currentAncestor) && !isHtmlElementInArray(currentAncestor, ["ol", "ul"])) {
			ancestorList.push(currentAncestor);
			currentAncestor = currentAncestor.parentNode;
		}

		// "If current ancestor is not an editable simple indentation element:"
		if (!isEditable(currentAncestor) || !isSimpleIndentationElement(currentAncestor)) {
			// "Let current ancestor be node's parent."
			currentAncestor = node.parentNode;

			// "Let ancestor list be the empty list."
			ancestorList = [];

			// "While current ancestor is an editable Element that is neither an
			// indentation element nor an ol nor a ul, append current ancestor to
			// ancestor list and then set current ancestor to its parent."
			while (isEditable(currentAncestor) && currentAncestor.nodeType == $_.Node.ELEMENT_NODE && !isIndentationElement(currentAncestor) && !isHtmlElementInArray(currentAncestor, ["ol", "ul"])) {
				ancestorList.push(currentAncestor);
				currentAncestor = currentAncestor.parentNode;
			}
		}

		// "If node is an ol or ul and current ancestor is not an editable
		// indentation element:"
		if (isHtmlElementInArray(node, ["OL", "UL"]) && (!isEditable(currentAncestor) || !isIndentationElement(currentAncestor))) {
			// "Unset the reversed, start, and type attributes of node, if any are
			// set."
			node.removeAttribute("reversed");
			node.removeAttribute("start");
			node.removeAttribute("type");

			// "Let children be the children of node."
			var children = [].slice.call(toArray(node.childNodes));

			// "If node has attributes, and its parent is not an ol or ul, set the
			// tag name of node to "div"."
			if (node.attributes.length && !isHtmlElementInArray(node.parentNode, ["OL", "UL"])) {
				setTagName(node, "div", range);

				// "Otherwise:"
			} else {
				// "Record the values of node's children, and let values be the
				// result."
				var values = recordValues([].slice.call(toArray(node.childNodes)));

				// "Remove node, preserving its descendants."
				removePreservingDescendants(node, range);

				// "Restore the values from values."
				restoreValues(values, range);
			}

			// "Fix disallowed ancestors of each member of children."
			var i;
			for (i = 0; i < children.length; i++) {
				fixDisallowedAncestors(children[i], range);
			}

			// "Abort these steps."
			return;
		}

		// "If current ancestor is not an editable indentation element, abort these
		// steps."
		if (!isEditable(currentAncestor) || !isIndentationElement(currentAncestor)) {
			return;
		}

		// "Append current ancestor to ancestor list."
		ancestorList.push(currentAncestor);

		// "Let original ancestor be current ancestor."
		var originalAncestor = currentAncestor;

		// "While ancestor list is not empty:"
		while (ancestorList.length) {
			// "Let current ancestor be the last member of ancestor list."
			//
			// "Remove the last member of ancestor list."
			currentAncestor = ancestorList.pop();

			// "Let target be the child of current ancestor that is equal to either
			// node or the last member of ancestor list."
			var target = node.parentNode == currentAncestor ? node : ancestorList[ancestorList.length - 1];

			// "If target is an inline node that is not a br, and its nextSibling
			// is a br, remove target's nextSibling from its parent."
			if (isInlineNode(target) && !isNamedHtmlElement(target, 'BR') && isNamedHtmlElement(target.nextSibling, "BR")) {
				target.parentNode.removeChild(target.nextSibling);
			}

			// "Let preceding siblings be the preceding siblings of target, and let
			// following siblings be the following siblings of target."
			var precedingSiblings = [].slice.call(toArray(currentAncestor.childNodes), 0, Dom.getIndexInParent(target));
			var followingSiblings = [].slice.call(toArray(currentAncestor.childNodes), 1 + Dom.getIndexInParent(target));

			// "Indent preceding siblings."
			indentNodes(precedingSiblings, range);

			// "Indent following siblings."
			indentNodes(followingSiblings, range);
		}

		// "Outdent original ancestor."
		outdentNode(originalAncestor, range);
	}


	//@}
	///// Toggling lists /////
	//@{

	function toggleLists(tagName, range) {
		// "Let mode be "disable" if the selection's list state is tag name, and
		// "enable" otherwise."
		var mode = getSelectionListState() == tagName ? "disable" : "enable";

		tagName = tagName.toUpperCase();

		// "Let other tag name be "ol" if tag name is "ul", and "ul" if tag name is
		// "ol"."
		var otherTagName = tagName == "OL" ? "UL" : "OL";

		// "Let items be a list of all lis that are ancestor containers of the
		// range's start and/or end node."
		//
		// It's annoying to get this in tree order using functional stuff without
		// doing getDescendants(document), which is slow, so I do it imperatively.
		var items = [];
		(function () {
			var ancestorContainer;
			for (ancestorContainer = range.endContainer;
				     ancestorContainer != range.commonAncestorContainer;
				     ancestorContainer = ancestorContainer.parentNode) {
				if (isNamedHtmlElement(ancestorContainer, "li")) {
					items.unshift(ancestorContainer);
				}
			}
			for (ancestorContainer = range.startContainer;
				     ancestorContainer;
				     ancestorContainer = ancestorContainer.parentNode) {
				if (isNamedHtmlElement(ancestorContainer, "li")) {
					items.unshift(ancestorContainer);
				}
			}
		}());

		// "For each item in items, normalize sublists of item."
		$_(items).forEach(function (thisArg) {
			normalizeSublists(thisArg, range);
		});

		// "Block-extend the range, and let new range be the result."
		var newRange = blockExtend(range);

		// "If mode is "enable", then let lists to convert consist of every
		// editable HTML element with local name other tag name that is contained
		// in new range, and for every list in lists to convert:"
		if (mode == "enable") {
			$_(getAllContainedNodes(newRange, function (node) {
				return isEditable(node) && isHtmlElement_obsolete(node, otherTagName);
			})).forEach(function (list) {
				// "If list's previousSibling or nextSibling is an editable HTML
				// element with local name tag name:"
				if ((isEditable(list.previousSibling) && isHtmlElement_obsolete(list.previousSibling, tagName)) || (isEditable(list.nextSibling) && isHtmlElement_obsolete(list.nextSibling, tagName))) {
					// "Let children be list's children."
					var children = [].slice.call(toArray(list.childNodes));

					// "Record the values of children, and let values be the
					// result."
					var values = recordValues(children);

					// "Split the parent of children."
					splitParent(children, range);

					// "Wrap children, with sibling criteria returning true for an
					// HTML element with local name tag name and false otherwise."
					wrap(
						children,
						function (node) {
							return isHtmlElement_obsolete(node, tagName);
						},
						function () {
							return null;
						},
						range
					);

					// "Restore the values from values."
					restoreValues(values, range);

					// "Otherwise, set the tag name of list to tag name."
				} else {
					setTagName(list, tagName, range);
				}
			});
		}

		// "Let node list be a list of nodes, initially empty."
		//
		// "For each node node contained in new range, if node is editable; the
		// last member of node list (if any) is not an ancestor of node; node
		// is not an indentation element; and either node is an ol or ul, or its
		// parent is an ol or ul, or it is an allowed child of "li"; then append
		// node to node list."
		var nodeList = getContainedNodes(newRange, function (node) {
			return isEditable(node) && !isIndentationElement(node) && (isHtmlElementInArray(node, ["OL", "UL"]) || isHtmlElementInArray(node.parentNode, ["OL", "UL"]) || isAllowedChild(node, "li"));
		});

		// "If mode is "enable", remove from node list any ol or ul whose parent is
		// not also an ol or ul."
		if (mode == "enable") {
			nodeList = $_(nodeList).filter(function (node) {
				return !isHtmlElementInArray(node, ["ol", "ul"]) || isHtmlElementInArray(node.parentNode, ["ol", "ul"]);
			});
		}

		// "If mode is "disable", then while node list is not empty:"
		var sublist, values;

		function createLi() {
			return document.createElement("li");
		}

		function isOlUl(node) {
			return isHtmlElementInArray(node, ["ol", "ul"]);
		}

		function makeIsElementPred(tagName) {
			return function (node) {
				return isHtmlElement_obsolete(node, tagName);
			};
		}

		function makeCreateElement(tagName) {
			return function () {
				return document.createElement(tagName);
			};
		}

		function makeCreateElementSublist(tagName, sublist, range) {
			return function () {
				// "If sublist's first member's parent is not an editable
				// simple indentation element, or sublist's first member's
				// parent's previousSibling is not an editable HTML element
				// with local name tag name, call createElement(tag name)
				// on the context object and return the result."
				if (!isEditable(sublist[0].parentNode) || !isSimpleIndentationElement(sublist[0].parentNode) || !isEditable(sublist[0].parentNode.previousSibling) || !isHtmlElement_obsolete(sublist[0].parentNode.previousSibling, tagName)) {
					return document.createElement(tagName);
				}

				// "Let list be sublist's first member's parent's
				// previousSibling."
				var list = sublist[0].parentNode.previousSibling;

				// "Normalize sublists of list's lastChild."
				normalizeSublists(list.lastChild, range);

				// "If list's lastChild is not an editable HTML element
				// with local name tag name, call createElement(tag name)
				// on the context object, and append the result as the last
				// child of list."
				if (!isEditable(list.lastChild) || !isHtmlElement_obsolete(list.lastChild, tagName)) {
					list.appendChild(document.createElement(tagName));
				}

				// "Return the last child of list."
				return list.lastChild;
			};
		}

		if (mode == "disable") {
			while (nodeList.length) {
				// "Let sublist be an empty list of nodes."
				sublist = [];

				// "Remove the first member from node list and append it to
				// sublist."
				sublist.push(nodeList.shift());

				// "If the first member of sublist is an HTML element with local
				// name tag name, outdent it and continue this loop from the
				// beginning."
				if (isHtmlElement_obsolete(sublist[0], tagName)) {
					outdentNode(sublist[0], range);
					continue;
				}

				// "While node list is not empty, and the first member of node list
				// is the nextSibling of the last member of sublist and is not an
				// HTML element with local name tag name, remove the first member
				// from node list and append it to sublist."
				while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling && !isHtmlElement_obsolete(nodeList[0], tagName)) {
					sublist.push(nodeList.shift());
				}

				// "Record the values of sublist, and let values be the result."
				values = recordValues(sublist);

				// "Split the parent of sublist."
				splitParent(sublist, range);

				// "Fix disallowed ancestors of each member of sublist."
				var i;
				for (i = 0; i < sublist.length; i++) {
					fixDisallowedAncestors(sublist[i], range);
				}

				// "Restore the values from values."
				restoreValues(values, range);
			}

			// "Otherwise, while node list is not empty:"
		} else {
			while (nodeList.length) {
				// "Let sublist be an empty list of nodes."
				sublist = [];

				// "While either sublist is empty, or node list is not empty and
				// its first member is the nextSibling of sublist's last member:"
				while (!sublist.length || (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling)) {
					// "If node list's first member is a p or div, set the tag name
					// of node list's first member to "li", and append the result
					// to sublist. Remove the first member from node list."
					if (isHtmlElementInArray(nodeList[0], ["p", "div"])) {
						sublist.push(setTagName(nodeList[0], "li", range));
						nodeList.shift();

						// "Otherwise, if the first member of node list is an li or ol
						// or ul, remove it from node list and append it to sublist."
					} else if (isHtmlElementInArray(nodeList[0], ["li", "ol", "ul"])) {
						sublist.push(nodeList.shift());

						// "Otherwise:"
					} else {
						// "Let nodes to wrap be a list of nodes, initially empty."
						var nodesToWrap = [];

						// "While nodes to wrap is empty, or node list is not empty
						// and its first member is the nextSibling of nodes to
						// wrap's last member and the first member of node list is
						// an inline node and the last member of nodes to wrap is
						// an inline node other than a br, remove the first member
						// from node list and append it to nodes to wrap."
						while (!nodesToWrap.length || (nodeList.length && nodeList[0] == nodesToWrap[nodesToWrap.length - 1].nextSibling && isInlineNode(nodeList[0]) && isInlineNode(nodesToWrap[nodesToWrap.length - 1]) && !isNamedHtmlElement(nodesToWrap[nodesToWrap.length - 1], "br"))) {
							nodesToWrap.push(nodeList.shift());
						}

						// "Wrap nodes to wrap, with new parent instructions
						// returning the result of calling createElement("li") on
						// the context object. Append the result to sublist."
						sublist.push(wrap(
							nodesToWrap,
							undefined,
							createLi,
							range
						));
					}
				}

				// "If sublist's first member's parent is an HTML element with
				// local name tag name, or if every member of sublist is an ol or
				// ul, continue this loop from the beginning."
				if (isHtmlElement_obsolete(sublist[0].parentNode, tagName) || $_(sublist).every(isOlUl)) {
					continue;
				}

				// "If sublist's first member's parent is an HTML element with
				// local name other tag name:"
				if (isHtmlElement_obsolete(sublist[0].parentNode, otherTagName)) {
					// "Record the values of sublist, and let values be the
					// result."
					values = recordValues(sublist);

					// "Split the parent of sublist."
					splitParent(sublist, range);

					// "Wrap sublist, with sibling criteria returning true for an
					// HTML element with local name tag name and false otherwise,
					// and new parent instructions returning the result of calling
					// createElement(tag name) on the context object."
					wrap(
						sublist,
						makeIsElementPred(tagName),
						makeCreateElement(tagName),
						range
					);

					// "Restore the values from values."
					restoreValues(values, range);

					// "Continue this loop from the beginning."
					continue;
				}

				// "Wrap sublist, with sibling criteria returning true for an HTML
				// element with local name tag name and false otherwise, and new
				// parent instructions being the following:"
				// . . .
				// "Fix disallowed ancestors of the previous step's result."
				fixDisallowedAncestors(wrap(
					sublist,
					makeIsElementPred(tagName),
					makeCreateElementSublist(tagName, sublist, range),
					range
				), range);
			}
		}
	}


	//@}
	///// Justifying the selection /////
	//@{

	function justifySelection(alignment, range) {

		// "Block-extend the active range, and let new range be the result."
		var newRange = blockExtend(range);

		// "Let element list be a list of all editable Elements contained in new
		// range that either has an attribute in the HTML namespace whose local
		// name is "align", or has a style attribute that sets "text-align", or is
		// a center."
		var elementList = getAllContainedNodes(newRange, function (node) {
			return node.nodeType == $_.Node.ELEMENT_NODE && isEditable(node)
			// Ignoring namespaces here
				&& (hasAttribute(node, "align") || node.style.textAlign != "" || isNamedHtmlElement(node, 'center'));
		});

		// "For each element in element list:"
		var i;
		for (i = 0; i < elementList.length; i++) {
			var element = elementList[i];

			// "If element has an attribute in the HTML namespace whose local name
			// is "align", remove that attribute."
			element.removeAttribute("align");

			// "Unset the CSS property "text-align" on element, if it's set by a
			// style attribute."
			element.style.textAlign = "";
			if (element.getAttribute("style") == "") {
				element.removeAttribute("style");
			}

			// "If element is a div or span or center with no attributes, remove
			// it, preserving its descendants."
			if (isHtmlElementInArray(element, ["div", "span", "center"]) && !element.attributes.length) {
				removePreservingDescendants(element, range);
			}

			// "If element is a center with one or more attributes, set the tag
			// name of element to "div"."
			if (isNamedHtmlElement(element, 'center') && element.attributes.length) {
				setTagName(element, "div", range);
			}
		}

		// "Block-extend the active range, and let new range be the result."
		newRange = blockExtend(globalRange);

		// "Let node list be a list of nodes, initially empty."
		var nodeList = [];

		// "For each node node contained in new range, append node to node list if
		// the last member of node list (if any) is not an ancestor of node; node
		// is editable; node is an allowed child of "div"; and node's alignment
		// value is not alignment."
		nodeList = getContainedNodes(newRange, function (node) {
			return isEditable(node) && isAllowedChild(node, "div") && getAlignmentValue(node) != alignment;
		});

		function makeIsAlignedDiv(alignment) {
			return function (node) {
				return isNamedHtmlElement(node, 'div') && $_(node.attributes).every(function (attr) {
					return (attr.name == "align" && attr.value.toLowerCase() == alignment) || (attr.name == "style" && getStyleLength(node) == 1 && node.style.textAlign == alignment);
				});
			};
		}

		function makeCreateAlignedDiv(alignment) {
			return function () {
				var newParent = document.createElement("div");
				newParent.setAttribute("style", "text-align: " + alignment);
				return newParent;
			};
		}

		// "While node list is not empty:"
		while (nodeList.length) {
			// "Let sublist be a list of nodes, initially empty."
			var sublist = [];

			// "Remove the first member of node list and append it to sublist."
			sublist.push(nodeList.shift());

			// "While node list is not empty, and the first member of node list is
			// the nextSibling of the last member of sublist, remove the first
			// member of node list and append it to sublist."
			while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling) {
				sublist.push(nodeList.shift());
			}

			// "Wrap sublist. Sibling criteria returns true for any div that has
			// one or both of the following two attributes and no other attributes,
			// and false otherwise:"
			//
			//   * "An align attribute whose value is an ASCII case-insensitive
			//     match for alignment.
			//   * "A style attribute which sets exactly one CSS property
			//     (including unrecognized or invalid attributes), which is
			//     "text-align", which is set to alignment.
			//
			// "New parent instructions are to call createElement("div") on the
			// context object, then set its CSS property "text-align" to alignment
			// and return the result."
			wrap(
				sublist,
				makeIsAlignedDiv(alignment),
				makeCreateAlignedDiv(alignment),
				range
			);
		}
	}

	//@}
	///// Move the given collapsed range over adjacent zero-width whitespace characters.
	///// The range is
	//@{
	/**
	 * Move the given collapsed range over adjacent zero-width whitespace characters.
	 * If the range is not collapsed or is not contained in a text node, it is not modified
	 * @param range range to modify
	 * @param forward {Boolean} true to move forward, false to move backward
	 */
	function moveOverZWSP(range, forward) {
		var offset;
		if (!range.collapsed) {
			return;
		}

		offset = range.startOffset;

		if (forward) {
			// check whether the range starts in a text node
			if (range.startContainer && range.startContainer.nodeType === $_.Node.TEXT_NODE) {
				// move forward (i.e. increase offset) as long as we stay in the text node and have zwsp characters to the right
				while (offset < range.startContainer.data.length && range.startContainer.data.charAt(offset) === '\u200b') {
					offset++;
				}
			}
		} else {
			// check whether the range starts in a text node
			if (range.startContainer && range.startContainer.nodeType === $_.Node.TEXT_NODE) {
				// move backward (i.e. decrease offset) as long as we stay in the text node and have zwsp characters to the left
				while (offset > 0 && range.startContainer.data.charAt(offset - 1) === '\u200b') {
					offset--;
				}
			}
		}

		// if the offset was changed, set it back to the collapsed range
		if (offset !== range.startOffset) {
			range.setStart(range.startContainer, offset);
			range.setEnd(range.startContainer, offset);
		}
	}

	/**
	 * implementation of the delete command
	 * will attempt to delete contents within range if non-collapsed
	 * or delete the character left of the cursor position if range
	 * is collapsed. Is used to define the behaviour of the backspace
	 * button.
	 *
	 * @param      value   is just there for compatibility with the commands api. parameter is ignored.
	 * @param      range   the range to execute the delete command for
	 * @return     void
	 */
	commands["delete"] = {
		action: function (value, range) {
			var
				i,
				deleteContentsRange;

			// special behaviour for skipping zero-width whitespaces
			moveOverZWSP(range, false);

			// "If the active range is not collapsed, delete the contents of the
			// active range and abort these steps."
			if (!range.collapsed) {
				deleteContents(range);
				return;
			}

			// "Canonicalize whitespace at (active range's start node, active
			// range's start offset)."
			canonicalizeWhitespace(range.startContainer, range.startOffset);

			// "Let node and offset be the active range's start node and offset."
			var node = range.startContainer;
			var offset = range.startOffset;
			var isBr = false;
			var isHr = false;

			// "Repeat the following steps:"
			while (true) {
				// we need to reset isBr and isHr on every interation of the loop
				if (offset > 0) {
					isBr = isNamedHtmlElement(node.childNodes[offset - 1], "br") || false;
					isHr = isNamedHtmlElement(node.childNodes[offset - 1], "hr") || false;
				}
				// "If offset is zero and node's previousSibling is an editable
				// invisible node, remove node's previousSibling from its parent."
				if (offset == 0 && isEditable(node.previousSibling) && isInvisible(node.previousSibling)) {
					node.parentNode.removeChild(node.previousSibling);
					continue;
				}
				// "Otherwise, if node has a child with index offset − 1 and that
				// child is an editable invisible node, remove that child from
				// node, then subtract one from offset."
				if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isEditable(node.childNodes[offset - 1]) && (isInvisible(node.childNodes[offset - 1]) || isBr || isHr)) {
					node.removeChild(node.childNodes[offset - 1]);
					offset--;
					if (isBr || isHr) {
						range.setStart(node, offset);
						range.setEnd(node, offset);
						return;
					}
					continue;

				}
				// "Otherwise, if offset is zero and node is an inline node, or if
				// node is an invisible node, set offset to the index of node, then
				// set node to its parent."
				if ((offset == 0 && isInlineNode(node)) || isInvisible(node)) {
					offset = Dom.getIndexInParent(node);
					node = node.parentNode;
					continue;
				}
				// "Otherwise, if node has a child with index offset − 1 and that
				// child is an editable a, remove that child from node, preserving
				// its descendants. Then abort these steps."
				if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isEditable(node.childNodes[offset - 1]) && isNamedHtmlElement(node.childNodes[offset - 1], "a")) {
					removePreservingDescendants(node.childNodes[offset - 1], range);
					return;

				}
				// "Otherwise, if node has a child with index offset − 1 and that
				// child is not a block node or a br or an img, set node to that
				// child, then set offset to the length of node."
				if (0 <= offset - 1 && offset - 1 < node.childNodes.length && !isBlockNode(node.childNodes[offset - 1]) && !isHtmlElementInArray(node.childNodes[offset - 1], ["br", "img"])) {
					node = node.childNodes[offset - 1];
					offset = getNodeLength(node);
					continue;
				}
				// "Otherwise, break from this loop."
				// brk is a quick and dirty jslint workaround since I don't want to rewrite this loop
				var brk = true;
				if (brk) {
					break;
				}
			}

			// "If node is a Text node and offset is not zero, call collapse(node,
			// offset) on the Selection. Then delete the contents of the range with
			// start (node, offset − 1) and end (node, offset) and abort these
			// steps."
			if (node.nodeType == $_.Node.TEXT_NODE && offset != 0) {
				// if the place we found is not editable, we stop here
				if (!isEditable(node)) {
					return;
				}

				range.setStart(node, offset - 1);
				range.setEnd(node, offset - 1);
				deleteContentsRange = deleteContents(node, offset - 1, node, offset);
				if (deleteContentsRange) {
					// The new range position can only be determined inside deleteContents,
					// that's why it's necessary to take over the range if one was returned.
					range.setStart(deleteContentsRange.startContainer, deleteContentsRange.startOffset);
					range.setEnd(deleteContentsRange.endContainer, deleteContentsRange.endOffset);
				}
				return;
			}

			// @iebug
			// when inserting a special char via the plugin
			// there where problems deleting them again with backspace after insertation
			// see https://github.com/alohaeditor/Aloha-Editor/issues/517
			if (node.nodeType == $_.Node.TEXT_NODE && offset == 0 && Aloha.browser.msie) {
				offset = 1;
				range.setStart(node, offset);
				range.setEnd(node, offset);
				range.startOffset = 0;
				deleteContents(range);
				return;
			}

			// "If node is an inline node, abort these steps."
			if (isInlineNode(node)) {
				return;
			}

			// "If node has a child with index offset − 1 and that child is a br or
			// hr or img, call collapse(node, offset) on the Selection. Then delete
			// the contents of the range with start (node, offset − 1) and end
			// (node, offset) and abort these steps."
			if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isHtmlElementInArray(node.childNodes[offset - 1], ["br", "hr", "img"])) {
				range.setStart(node, offset);
				range.setEnd(node, offset);
				deleteContents(range);
				return;
			}

			// "If node is an li or dt or dd and is the first child of its parent,
			// and offset is zero:"
			if (isHtmlElementInArray(node, ["li", "dt", "dd"]) && node == node.parentNode.firstChild && offset == 0) {
				// "Let items be a list of all lis that are ancestors of node."
				//
				// Remember, must be in tree order.
				var items = [];
				var ancestor;
				for (ancestor = node.parentNode; ancestor; ancestor = ancestor.parentNode) {
					if (isNamedHtmlElement(ancestor, 'li')) {
						items.unshift(ancestor);
					}
				}

				// "Normalize sublists of each item in items."
				for (i = 0; i < items.length; i++) {
					normalizeSublists(items[i], range);
				}

				// "Record the values of the one-node list consisting of node, and
				// let values be the result."
				var values = recordValues([node]);

				// "Split the parent of the one-node list consisting of node."
				splitParent([node], range);

				// "Restore the values from values."
				restoreValues(values, range);

				// "If node is a dd or dt, and it is not an allowed child of any of
				// its ancestors in the same editing host, set the tag name of node
				// to the default single-line container name and let node be the
				// result."
				if (isHtmlElementInArray(node, ["dd", "dt"]) && $_(getAncestors(node)).every(function (ancestor) { return !inSameEditingHost(node, ancestor) || !isAllowedChild(node, ancestor); })) {
					node = setTagName(node, defaultSingleLineContainerName, range);
				}

				// "Fix disallowed ancestors of node."
				fixDisallowedAncestors(node, range);

				// fix the lists to be html5 conformant
				for (i = 0; i < items.length; i++) {
					unNormalizeSublists(items[i].parentNode, range);
				}

				// "Abort these steps."
				return;
			}

			// "Let start node equal node and let start offset equal offset."
			var startNode = node;
			var startOffset = offset;

			// "Repeat the following steps:"
			while (true) {
				// "If start offset is zero, set start offset to the index of start
				// node and then set start node to its parent."
				if (startOffset == 0) {
					startOffset = Dom.getIndexInParent(startNode);
					startNode = startNode.parentNode;

					// "Otherwise, if start node has an editable invisible child with
					// index start offset minus one, remove it from start node and
					// subtract one from start offset."
				} else if (0 <= startOffset - 1 && startOffset - 1 < startNode.childNodes.length && isEditable(startNode.childNodes[startOffset - 1]) && isInvisible(startNode.childNodes[startOffset - 1])) {
					startNode.removeChild(startNode.childNodes[startOffset - 1]);
					startOffset--;

					// "Otherwise, break from this loop."
				} else {
					break;
				}
			}

			// "If offset is zero, and node has an editable ancestor container in
			// the same editing host that's an indentation element:"
			if (offset == 0 && $_(getAncestors(node).concat(node)).filter(function (ancestor) { return isEditable(ancestor) && inSameEditingHost(ancestor, node) && isIndentationElement(ancestor); }).length) {
				// "Block-extend the range whose start and end are both (node, 0),
				// and let new range be the result."
				var newRange = Aloha.createRange();
				newRange.setStart(node, 0);
				newRange.setEnd(node, 0);
				newRange = blockExtend(newRange);

				// "Let node list be a list of nodes, initially empty."
				//
				// "For each node current node contained in new range, append
				// current node to node list if the last member of node list (if
				// any) is not an ancestor of current node, and current node is
				// editable but has no editable descendants."
				var nodeList = getContainedNodes(newRange, function (currentNode) {
					return isEditable(currentNode) && !hasEditableDescendants(currentNode);
				});

				// "Outdent each node in node list."
				for (i = 0; i < nodeList.length; i++) {
					outdentNode(nodeList[i], range);
				}

				// "Abort these steps."
				return;
			}

			// "If the child of start node with index start offset is a table,
			// abort these steps."
			if (isNamedHtmlElement(startNode.childNodes[startOffset], "table")) {
				return;
			}

			// "If start node has a child with index start offset − 1, and that
			// child is a table:"
			if (0 <= startOffset - 1 && startOffset - 1 < startNode.childNodes.length && isNamedHtmlElement(startNode.childNodes[startOffset - 1], "table")) {
				// "Call collapse(start node, start offset − 1) on the context
				// object's Selection."
				range.setStart(startNode, startOffset - 1);

				// "Call extend(start node, start offset) on the context object's
				// Selection."
				range.setEnd(startNode, startOffset);

				// "Abort these steps."
				return;
			}

			// "If offset is zero; and either the child of start node with index
			// start offset minus one is an hr, or the child is a br whose
			// previousSibling is either a br or not an inline node:"
			if (offset == 0
				    && (isNamedHtmlElement(startNode.childNodes[startOffset - 1], "hr")
						|| (isNamedHtmlElement(startNode.childNodes[startOffset - 1], "br")
							&& (isNamedHtmlElement(startNode.childNodes[startOffset - 1].previousSibling, "br")
								|| !isInlineNode(startNode.childNodes[startOffset - 1].previousSibling))))) {
				// "Call collapse(node, offset) on the Selection."
				range.setStart(node, offset);
				range.setEnd(node, offset);

				// "Delete the contents of the range with start (start node, start
				// offset − 1) and end (start node, start offset)."
				deleteContents(startNode, startOffset - 1, startNode, startOffset);

				// "Abort these steps."
				return;
			}

			// "If the child of start node with index start offset is an li or dt
			// or dd, and that child's firstChild is an inline node, and start
			// offset is not zero:"
			if (isHtmlElementInArray(startNode.childNodes[startOffset], ["li", "dt", "dd"]) && isInlineNode(startNode.childNodes[startOffset].firstChild) && startOffset != 0) {
				// "Let previous item be the child of start node with index start
				// offset minus one."
				var previousItem = startNode.childNodes[startOffset - 1];

				// "If previous item's lastChild is an inline node other than a br,
				// call createElement("br") on the context object and append the
				// result as the last child of previous item."
				if (isInlineNode(previousItem.lastChild) && !isNamedHtmlElement(previousItem.lastChild, "br")) {
					previousItem.appendChild(document.createElement("br"));
				}

				// "If previous item's lastChild is an inline node, call
				// createElement("br") on the context object and append the result
				// as the last child of previous item."
				if (isInlineNode(previousItem.lastChild)) {
					previousItem.appendChild(document.createElement("br"));
				}
			}

			// "If the child of start node with index start offset is an li or dt
			// or dd, and its previousSibling is also an li or dt or dd, set start
			// node to its child with index start offset − 1, then set start offset
			// to start node's length, then set node to start node's nextSibling,
			// then set offset to 0."
			if (isHtmlElementInArray(startNode.childNodes[startOffset], ["li", "dt", "dd"]) && isHtmlElementInArray(startNode.childNodes[startOffset - 1], ["li", "dt", "dd"])) {
				startNode = startNode.childNodes[startOffset - 1];
				startOffset = getNodeLength(startNode);
				node = startNode.nextSibling;
				offset = 0;

				// "Otherwise, while start node has a child with index start offset
				// minus one:"
			} else {
				while (0 <= startOffset - 1 && startOffset - 1 < startNode.childNodes.length) {
					// "If start node's child with index start offset minus one is
					// editable and invisible, remove it from start node, then
					// subtract one from start offset."
					if (isEditable(startNode.childNodes[startOffset - 1]) && isInvisible(startNode.childNodes[startOffset - 1])) {
						startNode.removeChild(startNode.childNodes[startOffset - 1]);
						startOffset--;

						// "Otherwise, set start node to its child with index start
						// offset minus one, then set start offset to the length of
						// start node."
					} else {
						startNode = startNode.childNodes[startOffset - 1];
						startOffset = getNodeLength(startNode);
					}
				}
			}

			// "Delete the contents of the range with start (start node, start
			// offset) and end (node, offset)."
			var delRange = Aloha.createRange();
			delRange.setStart(startNode, startOffset);
			delRange.setEnd(node, offset);
			deleteContents(delRange);

			if (!isAncestorContainer(document.body, range.startContainer)) {
				if (delRange.startContainer.hasChildNodes()
						|| delRange.startContainer.nodeType == $_.Node.TEXT_NODE
							|| isEditingHost(delRange.startContainer)) {
					range.setStart(delRange.startContainer, delRange.startOffset);
					range.setEnd(delRange.startContainer, delRange.startOffset);
				} else {
					range.setStart(delRange.startContainer.parentNode, Dom.getIndexInParent(delRange.startContainer));
					range.setEnd(delRange.startContainer.parentNode, Dom.getIndexInParent(delRange.startContainer));
				}
			}
		}
	};

	//@}
	///// The formatBlock command /////
	//@{
	// "A formattable block name is "address", "dd", "div", "dt", "h1", "h2", "h3",
	// "h4", "h5", "h6", "p", or "pre"."
	var formattableBlockNames = ["address", "dd", "div", "dt", "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"];

	commands.formatblock = {
		action: function (value) {
			var i;

			// "If value begins with a "<" character and ends with a ">" character,
			// remove the first and last characters from it."
			if (/^<.*>$/.test(value)) {
				value = value.slice(1, -1);
			}

			// "Let value be converted to ASCII lowercase."
			value = value.toLowerCase();

			// "If value is not a formattable block name, abort these steps and do
			// nothing."
			if ($_(formattableBlockNames).indexOf(value) == -1) {
				return;
			}

			// "Block-extend the active range, and let new range be the result."
			var newRange = blockExtend(getActiveRange());

			// "Let node list be an empty list of nodes."
			//
			// "For each node node contained in new range, append node to node list
			// if it is editable, the last member of original node list (if any) is
			// not an ancestor of node, node is either a non-list single-line
			// container or an allowed child of "p" or a dd or dt, and node is not
			// the ancestor of a prohibited paragraph child."
			var nodeList = getContainedNodes(newRange, function (node) {
				return isEditable(node) && (isNonListSingleLineContainer(node) || isAllowedChild(node, "p") || isHtmlElementInArray(node, ["dd", "dt"])) && !$_(getDescendants(node)).some(isProhibitedParagraphChild);
			});

			// "Record the values of node list, and let values be the result."
			var values = recordValues(nodeList);

			function makeIsEditableElementInSameEditingHostDoesNotContainProhibitedParagraphChildren(node) {
				return function (ancestor) {
					return (isEditable(ancestor)
							&& inSameEditingHost(ancestor, node)
							&& isHtmlElement_obsolete(ancestor, formattableBlockNames)
							&& !$_(getDescendants(ancestor)).some(isProhibitedParagraphChild));
				};
			}

			function makeIsElementWithoutAttributes(value) {
				return function (node) {
					return isHtmlElement_obsolete(node, value) && !node.attributes.length;
				};
			}

			function returnFalse() {
				return false;
			}

			function makeCreateElement(value) {
				return function () {
					return document.createElement(value);
				};
			}

			// "For each node in node list, while node is the descendant of an
			// editable HTML element in the same editing host, whose local name is
			// a formattable block name, and which is not the ancestor of a
			// prohibited paragraph child, split the parent of the one-node list
			// consisting of node."
			for (i = 0; i < nodeList.length; i++) {
				var node = nodeList[i];
				while ($_(getAncestors(node)).some(makeIsEditableElementInSameEditingHostDoesNotContainProhibitedParagraphChildren(node))) {
					splitParent([node], newRange);
				}
			}

			// "Restore the values from values."
			restoreValues(values, newRange);

			// "While node list is not empty:"
			while (nodeList.length) {
				var sublist;

				// "If the first member of node list is a single-line
				// container:"
				if (isSingleLineContainer(nodeList[0])) {
					// "Let sublist be the children of the first member of node
					// list."
					sublist = [].slice.call(toArray(nodeList[0].childNodes));

					// "Record the values of sublist, and let values be the
					// result."
					values = recordValues(sublist);

					// "Remove the first member of node list from its parent,
					// preserving its descendants."
					removePreservingDescendants(nodeList[0], newRange);

					// "Restore the values from values."
					restoreValues(values, newRange);

					// "Remove the first member from node list."
					nodeList.shift();

					// "Otherwise:"
				} else {
					// "Let sublist be an empty list of nodes."
					sublist = [];

					// "Remove the first member of node list and append it to
					// sublist."
					sublist.push(nodeList.shift());

					// "While node list is not empty, and the first member of
					// node list is the nextSibling of the last member of
					// sublist, and the first member of node list is not a
					// single-line container, and the last member of sublist is
					// not a br, remove the first member of node list and
					// append it to sublist."
					while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling && !isSingleLineContainer(nodeList[0]) && !isNamedHtmlElement(sublist[sublist.length - 1], "BR")) {
						sublist.push(nodeList.shift());
					}
				}

				// "Wrap sublist. If value is "div" or "p", sibling criteria
				// returns false; otherwise it returns true for an HTML element
				// with local name value and no attributes, and false otherwise.
				// New parent instructions return the result of running
				// createElement(value) on the context object. Then fix disallowed
				// ancestors of the result."
				fixDisallowedAncestors(wrap(
					sublist,
					jQuery.inArray(value, ["div", "p"]) == -1 ? makeIsElementWithoutAttributes(value) : returnFalse,
					makeCreateElement(value),
					newRange
				), newRange);
			}
		},
		indeterm: function () {
			// "Block-extend the active range, and let new range be the result."
			var newRange = blockExtend(getActiveRange());

			// "Let node list be all visible editable nodes that are contained in
			// new range and have no children."
			var nodeList = getAllContainedNodes(newRange, function (node) {
				return isVisible(node) && isEditable(node) && !node.hasChildNodes();
			});

			// "If node list is empty, return false."
			if (!nodeList.length) {
				return false;
			}

			// "Let type be null."
			var type = null;

			// "For each node in node list:"
			var i;
			for (i = 0; i < nodeList.length; i++) {
				var node = nodeList[i];

				// "While node's parent is editable and in the same editing host as
				// node, and node is not an HTML element whose local name is a
				// formattable block name, set node to its parent."
				while (isEditable(node.parentNode) && inSameEditingHost(node, node.parentNode) && !isHtmlElement_obsolete(node, formattableBlockNames)) {
					node = node.parentNode;
				}

				// "Let current type be the empty string."
				var currentType = "";

				// "If node is an editable HTML element whose local name is a
				// formattable block name, and node is not the ancestor of a
				// prohibited paragraph child, set current type to node's local
				// name."
				if (isEditable(node) && isHtmlElement_obsolete(node, formattableBlockNames) && !$_(getDescendants(node)).some(isProhibitedParagraphChild)) {
					currentType = node.tagName;
				}

				// "If type is null, set type to current type."
				if (type === null) {
					type = currentType;

					// "Otherwise, if type does not equal current type, return true."
				} else if (type != currentType) {
					return true;
				}
			}

			// "Return false."
			return false;
		},
		value: function () {
			// "Block-extend the active range, and let new range be the result."
			var newRange = blockExtend(getActiveRange());

			// "Let node be the first visible editable node that is contained in
			// new range and has no children. If there is no such node, return the
			// empty string."
			var nodes = getAllContainedNodes(newRange, function (node) {
				return isVisible(node) && isEditable(node) && !node.hasChildNodes();
			});
			if (!nodes.length) {
				return "";
			}
			var node = nodes[0];

			// "While node's parent is editable and in the same editing host as
			// node, and node is not an HTML element whose local name is a
			// formattable block name, set node to its parent."
			while (isEditable(node.parentNode) && inSameEditingHost(node, node.parentNode) && !isHtmlElement_obsolete(node, formattableBlockNames)) {
				node = node.parentNode;
			}

			// "If node is an editable HTML element whose local name is a
			// formattable block name, and node is not the ancestor of a prohibited
			// paragraph child, return node's local name, converted to ASCII
			// lowercase."
			if (isEditable(node) && isHtmlElement_obsolete(node, formattableBlockNames) && !$_(getDescendants(node)).some(isProhibitedParagraphChild)) {
				return node.tagName.toLowerCase();
			}

			// "Return the empty string."
			return "";
		}
	};

	//@}
	///// The forwardDelete command /////
	//@{
	commands.forwarddelete = {
		action: function (value, range) {
			var deleteContentsRange;
			// special behaviour for skipping zero-width whitespaces
			moveOverZWSP(range, true);

			// "If the active range is not collapsed, delete the contents of the
			// active range and abort these steps."
			if (!range.collapsed) {
				deleteContents(range);
				return;
			}

			// "Canonicalize whitespace at (active range's start node, active
			// range's start offset)."
			canonicalizeWhitespace(range.startContainer, range.startOffset);

			// small selection hack: If the selection is in an empty text node, we move it after the text node
			if (range.startContainer.nodeType === 3 && range.startContainer.data.length === 0) {
				range.startOffset = range.endOffset = Dom.getIndexInParent(range.startContainer) + 1;
				range.startContainer = range.endContainer = range.startContainer.parentNode;
			}

			// "Let node and offset be the active range's start node and offset."
			var node = range.startContainer;
			var offset = range.startOffset;
			var isBr = false;
			var isHr = false;

			// special behaviour: if we are in an empty block-level element (empty, but for the single ending br), we just remove that
			// this will fix situations where it was not possible to delete an empty block level element right before a non-editable part
			// but never delete the editing host itself
			if (Dom.isEmptyBlockLevelElement(node) && !isEditingHost(node)) {
				range.startContainer = node.parentElement;
				range.startOffset = Dom.getIndexInParent(node);
				range.endContainer = node.parentElement;
				range.endOffset = range.startOffset + 1;
				deleteContents(range);

				// currently, the selection would probably be put in between two paragraphs, which is not suitable
				// try to move the cursor into the next text node
				var nextText = Dom.searchAdjacentTextNode(range.startContainer, range.startOffset, false, {blocklevel: false, list: false});
				if (nextText) {
					range.startContainer = range.endContainer = nextText;
					range.startOffset = range.endOffset = 0;
					return;
				}

				// check whether the next element is an editable blocklevel element, if yes, put the cursor into it
				var nextElement = range.startContainer.childNodes.length > range.startOffset ? range.startContainer.childNodes[range.startOffset] : null;
				if (Dom.isBlockLevelElement(nextElement) && Dom.isEditable(nextElement)) {
					range.startContainer = range.endContainer = nextElement;
					range.startOffset = range.endOffset = 0;
					return;
				}

				// try the previous text node
				nextText = Dom.searchAdjacentTextNode(range.startContainer, range.startOffset, true, {blocklevel: false, list: false});
				if (nextText) {
					range.startContainer = range.endContainer = nextText;
					range.startOffset = range.endOffset = nextText.data.length;
				}

				return;
			}

			// "Repeat the following steps:"
			while (true) {
				// check whether the next element is a br or hr
				// Commented out for unknown reason.
				//if (offset < node.childNodes.length) {
				//				isBr = isHtmlElement_obsolete(node.childNodes[offset], "br") || false;
				//				isHr = isHtmlElement_obsolete(node.childNodes[offset], "hr") || false;
				//}

				// "If offset is the length of node and node's nextSibling is an
				// editable invisible node, remove node's nextSibling from its
				// parent."
				if (offset == getNodeLength(node) && isEditable(node.nextSibling) && isInvisible(node.nextSibling)) {
					node.parentNode.removeChild(node.nextSibling);

					// "Otherwise, if node has a child with index offset and that child
					// is an editable invisible node, remove that child from node."
				} else if (offset < node.childNodes.length && isEditable(node.childNodes[offset]) && (isInvisible(node.childNodes[offset]) || isBr || isHr)) {
					node.removeChild(node.childNodes[offset]);
					if (isBr || isHr) {
						ensureContainerEditable(node);
						range.setStart(node, offset);
						range.setEnd(node, offset);
						return;
					}

					// "Otherwise, if node has a child with index offset and that child
					// is a collapsed block prop, add one to offset."
				} else if (offset < node.childNodes.length && isCollapsedBlockProp(node.childNodes[offset])) {
					offset++;

					// "Otherwise, if offset is the length of node and node is an
					// inline node, or if node is invisible, set offset to one plus the
					// index of node, then set node to its parent."
				} else if ((offset == getNodeLength(node) && isInlineNode(node)) || isInvisible(node)) {
					offset = 1 + Dom.getIndexInParent(node);
					node = node.parentNode;

					// "Otherwise, if node has a child with index offset and that child
					// is not a block node or a br or an img, set node to that child,
					// then set offset to zero."
				} else if (offset < node.childNodes.length && !isBlockNode(node.childNodes[offset]) && !isHtmlElementInArray(node.childNodes[offset], ["br", "img"])) {
					node = node.childNodes[offset];
					offset = 0;

					// "Otherwise, break from this loop."
				} else {
					break;
				}
			}

			// collapse whitespace in the node, if it is a text node
			canonicalizeWhitespace(range.startContainer, range.startOffset);

			var endOffset;
			// "If node is a Text node and offset is not node's length:"
			if (node.nodeType == $_.Node.TEXT_NODE && offset != getNodeLength(node)) {
				// if the place we found (which we want to delete) is not editable, we stop here
				if (!isEditable(node)) {
					return;
				}

				// "Call collapse(node, offset) on the Selection."
				range.setStart(node, offset);
				range.setEnd(node, offset);

				// "Let end offset be offset plus one."
				endOffset = offset + 1;

				// "While end offset is not node's length and the end offsetth
				// element of node's data has general category M when interpreted
				// as a Unicode code point, add one to end offset."
				//
				// TODO: Not even going to try handling anything beyond the most
				// basic combining marks, since I couldn't find a good list.  I
				// special-case a few Hebrew diacritics too to test basic coverage
				// of non-Latin stuff.
				while (endOffset != node.length && /^[\u0300-\u036f\u0591-\u05bd\u05c1\u05c2]$/.test(node.data[endOffset])) {
					endOffset++;
				}

				// "Delete the contents of the range with start (node, offset) and
				// end (node, end offset)."
				deleteContentsRange = deleteContents(node, offset, node, endOffset);

				if (deleteContentsRange) {
					// The new range position can only be determined inside deleteContents,
					// that's why it's necessary to take over the range if one was returned.
					range.setStart(deleteContentsRange.startContainer, deleteContentsRange.startOffset);
					range.setEnd(deleteContentsRange.endContainer, deleteContentsRange.endOffset);
				}
				// "Abort these steps."
				return;
			}

			// "If node has a child with index offset and that child is a br or hr
			// or img, call collapse(node, offset) on the Selection. Then delete
			// the contents of the range with start (node, offset) and end (node,
			// offset + 1) and abort these steps."
			if (offset < node.childNodes.length && isHtmlElementInArray(node.childNodes[offset], ["br", "hr", "img"])) {
				range.setStart(node, offset);
				range.setEnd(node, offset);
				deleteContents(node, offset, node, offset + 1);
				return;
			}

			// "If node is an inline node, abort these steps."
			if (isInlineNode(node)) {
				return;
			}

			// "Let end node equal node and let end offset equal offset."
			var endNode = node;
			endOffset = offset;

			// "Repeat the following steps:"
			while (true) {
				// "If end offset is the length of end node, set end offset to one
				// plus the index of end node and then set end node to its parent."
				if (endOffset == getNodeLength(endNode)) {
					endOffset = 1 + Dom.getIndexInParent(endNode);
					endNode = endNode.parentNode;

					// "Otherwise, if end node has a an editable invisible child with
					// index end offset, remove it from end node."
				} else if (endOffset < endNode.childNodes.length && isEditable(endNode.childNodes[endOffset]) && isInvisible(endNode.childNodes[endOffset])) {
					endNode.removeChild(endNode.childNodes[endOffset]);

					// "Otherwise, break from this loop."
				} else {
					break;
				}
			}

			// "If the child of end node with index end offset minus one is a
			// table, abort these steps."
			if (isNamedHtmlElement(endNode.childNodes[endOffset - 1], "table")) {
				return;
			}

			// "If the child of end node with index end offset is a table:"
			if (isNamedHtmlElement(endNode.childNodes[endOffset], "table")) {
				// "Call collapse(end node, end offset) on the context object's
				// Selection."
				range.setStart(endNode, endOffset);

				// "Call extend(end node, end offset + 1) on the context object's
				// Selection."
				range.setEnd(endNode, endOffset + 1);

				// "Abort these steps."
				return;
			}

			// "If offset is the length of node, and the child of end node with
			// index end offset is an hr or br:"
			if (offset == getNodeLength(node) && isHtmlElementInArray(endNode.childNodes[endOffset], ["br", "hr"])) {
				// "Call collapse(node, offset) on the Selection."
				range.setStart(node, offset);
				range.setEnd(node, offset);

				// "Delete the contents of the range with end (end node, end
				// offset) and end (end node, end offset + 1)."
				deleteContents(endNode, endOffset, endNode, endOffset + 1);

				// "Abort these steps."
				return;
			}

			// "While end node has a child with index end offset:"
			while (endOffset < endNode.childNodes.length) {
				// "If end node's child with index end offset is editable and
				// invisible, remove it from end node."
				if (isEditable(endNode.childNodes[endOffset]) && isInvisible(endNode.childNodes[endOffset])) {
					endNode.removeChild(endNode.childNodes[endOffset]);

					// prevent deleting non-editable elements
				} else if (!isEditable(endNode.childNodes[endOffset]) || jQuery(endNode.childNodes[endOffset]).hasClass('aloha-table-wrapper')) {
					return;

					// "Otherwise, set end node to its child with index end offset and
					// set end offset to zero."
				} else {
					endNode = endNode.childNodes[endOffset];
					endOffset = 0;
				}
			}

			// "Delete the contents of the range with start (node, offset) and end
			// (end node, end offset)."
			var newRange = deleteContents(node, offset, endNode, endOffset);
			range.setStart(newRange.startContainer, newRange.startOffset);
			range.setEnd(newRange.endContainer, newRange.endOffset);
		}
	};

	//@}
	///// The indent command /////
	//@{
	commands.indent = {
		action: function () {
			// "Let items be a list of all lis that are ancestor containers of the
			// active range's start and/or end node."
			//
			// Has to be in tree order, remember!
			var items = [];
			var node;
			for (node = getActiveRange().endContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {
				if (isNamedHtmlElement(node, "LI")) {
					items.unshift(node);
				}
			}
			for (node = getActiveRange().startContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {
				if (isNamedHtmlElement(node, "LI")) {
					items.unshift(node);
				}
			}
			for (node = getActiveRange().commonAncestorContainer; node; node = node.parentNode) {
				if (isNamedHtmlElement(node, "LI")) {
					items.unshift(node);
				}
			}

			// "For each item in items, normalize sublists of item."
			var i;
			for (i = 0; i < items.length; i++) {
				normalizeSublists(items[i], getActiveRange());
			}

			// "Block-extend the active range, and let new range be the result."
			var newRange = blockExtend(getActiveRange());

			// "Let node list be a list of nodes, initially empty."
			var nodeList = [];

			// "For each node node contained in new range, if node is editable and
			// is an allowed child of "div" or "ol" and if the last member of node
			// list (if any) is not an ancestor of node, append node to node list."
			nodeList = getContainedNodes(newRange, function (node) {
				return isEditable(node) && (isAllowedChild(node, "div") || isAllowedChild(node, "ol"));
			});

			// "If the first member of node list is an li whose parent is an ol or
			// ul, and its previousSibling is an li as well, normalize sublists of
			// its previousSibling."
			if (nodeList.length && isNamedHtmlElement(nodeList[0], "LI") && isHtmlElementInArray(nodeList[0].parentNode, ["OL", "UL"]) && isNamedHtmlElement(nodeList[0].previousSibling, "LI")) {
				normalizeSublists(nodeList[0].previousSibling, newRange);
			}

			// "While node list is not empty:"
			while (nodeList.length) {
				// "Let sublist be a list of nodes, initially empty."
				var sublist = [];

				// "Remove the first member of node list and append it to sublist."
				sublist.push(nodeList.shift());

				// "While the first member of node list is the nextSibling of the
				// last member of sublist, remove the first member of node list and
				// append it to sublist."
				while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling) {
					sublist.push(nodeList.shift());
				}

				// "Indent sublist."
				indentNodes(sublist, newRange);
			}
		}
	};

	//@}
	///// The insertHorizontalRule command /////
	//@{
	commands.inserthorizontalrule = {
		action: function (value, range) {

			// "While range's start offset is 0 and its start node's parent is not
			// null, set range's start to (parent of start node, index of start
			// node)."
			while (range.startOffset == 0 && range.startContainer.parentNode) {
				range.setStart(range.startContainer.parentNode, Dom.getIndexInParent(range.startContainer));
			}

			// "While range's end offset is the length of its end node, and its end
			// node's parent is not null, set range's end to (parent of end node, 1
			// + index of start node)."
			while (range.endOffset == getNodeLength(range.endContainer) && range.endContainer.parentNode) {
				range.setEnd(range.endContainer.parentNode, 1 + Dom.getIndexInParent(range.endContainer));
			}

			// "Delete the contents of range, with block merging false."
			deleteContents(range, {
				blockMerging: false
			});

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(getActiveRange().startContainer) && !isEditingHost(getActiveRange().startContainer)) {
				return;
			}

			// "If the active range's start node is a Text node and its start
			// offset is zero, set the active range's start and end to (parent of
			// start node, index of start node)."
			if (getActiveRange().startContainer.nodeType == $_.Node.TEXT_NODE && getActiveRange().startOffset == 0) {
				getActiveRange().setStart(getActiveRange().startContainer.parentNode, Dom.getIndexInParent(getActiveRange().startContainer));
				getActiveRange().collapse(true);
			}

			// "If the active range's start node is a Text node and its start
			// offset is the length of its start node, set the active range's start
			// and end to (parent of start node, 1 + index of start node)."
			if (getActiveRange().startContainer.nodeType == $_.Node.TEXT_NODE && getActiveRange().startOffset == getNodeLength(getActiveRange().startContainer)) {
				getActiveRange().setStart(getActiveRange().startContainer.parentNode, 1 + Dom.getIndexInParent(getActiveRange().startContainer));
				getActiveRange().collapse(true);
			}

			// "Let hr be the result of calling createElement("hr") on the
			// context object."
			var hr = document.createElement("hr");

			// "Run insertNode(hr) on the range."
			range.insertNode(hr);

			// "Fix disallowed ancestors of hr."
			fixDisallowedAncestors(hr, range);

			// "Run collapse() on the Selection, with first argument equal to the
			// parent of hr and the second argument equal to one plus the index of
			// hr."
			//
			// Not everyone actually supports collapse(), so we do it manually
			// instead.  Also, we need to modify the actual range we're given as
			// well, for the sake of autoimplementation.html's range-filling-in.
			range.setStart(hr.parentNode, 1 + Dom.getIndexInParent(hr));
			range.setEnd(hr.parentNode, 1 + Dom.getIndexInParent(hr));
			Aloha.getSelection().removeAllRanges();
			Aloha.getSelection().addRange(range);
		}
	};

	/**
	 * Publish a message for the links that will be inserted at paste.
	 * @param {DocumentFragment} frag
	 */
	function publishPastedLinks(frag) {
		var children = frag.childNodes;
		var links;
		var c;
		var i;
		var len;

		for (c = 0; c < children.length; c++) {
			links = jQuery(children[c]).find('a');
			for (i = 0, len = links.length; i < len; i++) {
				PubSub.pub('aloha.link.pasted', {
					href: links[i].getAttribute('href'),
					element: links[i]
				});
			}
		}
	}

	//@}
	///// The insertHTML command /////
	//@{
	commands.inserthtml = {
		action: function (raw, range) {
			var editable = Dom.getEditingHostOf(range.commonAncestorContainer);
			var value = editable ? ContentRules.applyRules(raw, editable) : raw;

			// "Delete the contents of the active range."
			deleteContents(range);

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
				return;
			}

			// "Let frag be the result of calling createContextualFragment(value)
			// on the active range."
			var frag = range.createContextualFragment(value);

			publishPastedLinks(frag);

			// "Let last child be the lastChild of frag."
			var lastChild = frag.lastChild;

			// "If last child is null, abort these steps."
			if (!lastChild) {
				return;
			}

			// "Let descendants be all descendants of frag."
			var descendants = getDescendants(frag);

			// "If the active range's start node is a block node:"
			if (isBlockNode(range.startContainer)) {
				// "Let collapsed block props be all editable collapsed block prop
				// children of the active range's start node that have index
				// greater than or equal to the active range's start offset."
				//
				// "For each node in collapsed block props, remove node from its
				// parent."
				$_(range.startContainer.childNodes).filter(function (node, range) {
					return isEditable(node) && isCollapsedBlockProp(node) && Dom.getIndexInParent(node) >= range.startOffset;
				}, true).forEach(function (node) {
					node.parentNode.removeChild(node);
				});
			}

			// "Call insertNode(frag) on the active range."
			range.insertNode(frag);

			// "If the active range's start node is a block node with no visible
			// children, call createElement("br") on the context object and append
			// the result as the last child of the active range's start node."
			if (isBlockNode(range.startContainer)) {
				ensureContainerEditable(range.startContainer);
			}

			// "Call collapse() on the context object's Selection, with last
			// child's parent as the first argument and one plus its index as the
			// second."
			range.setStart(lastChild.parentNode, 1 + Dom.getIndexInParent(lastChild));
			range.setEnd(lastChild.parentNode, 1 + Dom.getIndexInParent(lastChild));

			// "Fix disallowed ancestors of each member of descendants."
			var i;
			for (i = 0; i < descendants.length; i++) {
				fixDisallowedAncestors(descendants[i], range);
			}

			setActiveRange(range);
		}
	};

	//@}
	///// The insertImage command /////
	//@{
	commands.insertimage = {
		action: function (value) {
			// "If value is the empty string, abort these steps and do nothing."
			if (value === "") {
				return;
			}

			// "Let range be the active range."
			var range = getActiveRange();

			// "Delete the contents of range, with strip wrappers false."
			deleteContents(range, {
				stripWrappers: false
			});

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(getActiveRange().startContainer) && !isEditingHost(getActiveRange().startContainer)) {
				return;
			}

			// "If range's start node is a block node whose sole child is a br, and
			// its start offset is 0, remove its start node's child from it."
			if (isBlockNode(range.startContainer) && range.startContainer.childNodes.length == 1 && isNamedHtmlElement(range.startContainer.firstChild, "br") && range.startOffset == 0) {
				range.startContainer.removeChild(range.startContainer.firstChild);
			}

			// "Let img be the result of calling createElement("img") on the
			// context object."
			var img = document.createElement("img");

			// "Run setAttribute("src", value) on img."
			img.setAttribute("src", value);

			// "Run insertNode(img) on the range."
			range.insertNode(img);

			// "Run collapse() on the Selection, with first argument equal to the
			// parent of img and the second argument equal to one plus the index of
			// img."
			//
			// Not everyone actually supports collapse(), so we do it manually
			// instead.  Also, we need to modify the actual range we're given as
			// well, for the sake of autoimplementation.html's range-filling-in.
			range.setStart(img.parentNode, 1 + Dom.getIndexInParent(img));
			range.setEnd(img.parentNode, 1 + Dom.getIndexInParent(img));
			Aloha.getSelection().removeAllRanges();
			Aloha.getSelection().addRange(range);

			// IE adds width and height attributes for some reason, so remove those
			// to actually do what the spec says.
			img.removeAttribute("width");
			img.removeAttribute("height");
		}
	};

	//@}
	///// The insertLineBreak command /////
	//@{
	commands.insertlinebreak = {
		action: function (value, range) {
			// "Delete the contents of the active range, with strip wrappers false."
			deleteContents(range, {
				stripWrappers: false
			});

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
				return;
			}

			// "If the active range's start node is an Element, and "br" is not an
			// allowed child of it, abort these steps."
			if (range.startContainer.nodeType == $_.Node.ELEMENT_NODE && !isAllowedChild("br", range.startContainer)) {
				return;
			}

			// "If the active range's start node is not an Element, and "br" is not
			// an allowed child of the active range's start node's parent, abort
			// these steps."
			if (range.startContainer.nodeType != $_.Node.ELEMENT_NODE && !isAllowedChild("br", range.startContainer.parentNode)) {
				return;
			}

			// "If the active range's start node is a Text node and its start
			// offset is zero, call collapse() on the context object's Selection,
			// with first argument equal to the active range's start node's parent
			// and second argument equal to the active range's start node's index."
			var newNode, newOffset;
			if (range.startContainer.nodeType == $_.Node.TEXT_NODE && range.startOffset == 0) {
				newNode = range.startContainer.parentNode;
				newOffset = Dom.getIndexInParent(range.startContainer);
				Aloha.getSelection().collapse(newNode, newOffset);
				range.setStart(newNode, newOffset);
				range.setEnd(newNode, newOffset);
			}

			// "If the active range's start node is a Text node and its start
			// offset is the length of its start node, call collapse() on the
			// context object's Selection, with first argument equal to the active
			// range's start node's parent and second argument equal to one plus
			// the active range's start node's index."
			if (range.startContainer.nodeType == $_.Node.TEXT_NODE && range.startOffset == getNodeLength(range.startContainer)) {
				newNode = range.startContainer.parentNode;
				newOffset = 1 + Dom.getIndexInParent(range.startContainer);
				Aloha.getSelection().collapse(newNode, newOffset);
				range.setStart(newNode, newOffset);
				range.setEnd(newNode, newOffset);
			}

			// "Let br be the result of calling createElement("br") on the context
			// object."
			var br = document.createElement("br");

			// "Call insertNode(br) on the active range."
			range.insertNode(br);

			// "Call collapse() on the context object's Selection, with br's parent
			// as the first argument and one plus br's index as the second
			// argument."
			Aloha.getSelection().collapse(br.parentNode, 1 + Dom.getIndexInParent(br));
			range.setStart(br.parentNode, 1 + Dom.getIndexInParent(br));
			range.setEnd(br.parentNode, 1 + Dom.getIndexInParent(br));

			// "If br is a collapsed line break, call createElement("br") on the
			// context object and let extra br be the result, then call
			// insertNode(extra br) on the active range."
			if (isCollapsedLineBreak(br)) {
				// TODO
				range.insertNode(createEndBreak());

				// Compensate for nonstandard implementations of insertNode
				Aloha.getSelection().collapse(br.parentNode, 1 + Dom.getIndexInParent(br));
				range.setStart(br.parentNode, 1 + Dom.getIndexInParent(br));
				range.setEnd(br.parentNode, 1 + Dom.getIndexInParent(br));
			}

			// IE7 is adding this styles: height: auto; min-height: 0px; max-height: none;
			// with that there is the ugly "IE-editable-outline"
			if (Aloha.browser.msie && Aloha.browser.version < 8) {
				br.parentNode.removeAttribute("style");
			}
		}
	};

	//@}
	///// The insertOrderedList command /////
	//@{
	commands.insertorderedlist = {
		// "Toggle lists with tag name "ol"."
		action: function (value, range) {
			toggleLists("ol", range);
		},
		// "True if the selection's list state is "mixed" or "mixed ol", false
		// otherwise."
		indeterm: function () {
			return (/^mixed( ol)?$/).test(getSelectionListState());
		},
		// "True if the selection's list state is "ol", false otherwise."
		state: function () {
			return getSelectionListState() == "ol";
		}
	};

	var listRelatedElements = {
		"LI": true,
		"DT": true,
		"DD": true
	};

	//@}
	///// The insertParagraph command /////
	//@{
	commands.insertparagraph = {
		action: function (value, range) {
			var i;

			// "Delete the contents of the active range."
			deleteContents(range);

			// clean lists in the editing host, this will remove any whitespace nodes around lists
			// because the following algorithm is not prepared to deal with them
			cleanLists(getEditingHostOf(range.startContainer), range);

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
				return;
			}

			var editable = Dom.getEditingHostOf(range.commonAncestorContainer);

			if (!editable) {
				return;
			}

			// "Let node and offset be the active range's start node and offset."
			var node = range.startContainer;
			var offset = range.startOffset;

			// "If node is a Text node, and offset is neither 0 nor the length of
			// node, call splitText(offset) on node."
			if (node.nodeType == $_.Node.TEXT_NODE && offset != 0 && offset != getNodeLength(node)) {
				splitText(node, offset);
			}

			// "If node is a Text node and offset is its length, set offset to one
			// plus the index of node, then set node to its parent."
			if (node.nodeType == $_.Node.TEXT_NODE && offset == getNodeLength(node)) {
				offset = 1 + Dom.getIndexInParent(node);
				node = node.parentNode;
			}

			// "If node is a Text or Comment node, set offset to the index of node,
			// then set node to its parent."
			if (node.nodeType == $_.Node.TEXT_NODE || node.nodeType == $_.Node.COMMENT_NODE) {
				offset = Dom.getIndexInParent(node);
				node = node.parentNode;
			}

			// "Call collapse(node, offset) on the context object's Selection."
			Aloha.getSelection().collapse(node, offset);
			range.setStart(node, offset);
			range.setEnd(node, offset);

			// "Let container equal node."
			var container = node;

			// "While container is not a single-line container, and container's
			// parent is editable and in the same editing host as node, set
			// container to its parent."
			while (!isSingleLineContainer(container) && isEditable(container.parentNode) && inSameEditingHost(node, container.parentNode)) {
				container = container.parentNode;
			}

			// "If container is not editable or not in the same editing host as
			// node or is not a single-line container:"
			if (!isEditable(container) || !inSameEditingHost(container, node) || !isSingleLineContainer(container)) {
				editable = Dom.getEditingHostOf(container);

				if (!editable || !ContentRules.isAllowed(editable, defaultSingleLineContainerName)) {
					return;
				}

				// "Let tag be the default single-line container name."
				var tag = defaultSingleLineContainerName;

				// "Block-extend the active range, and let new range be the
				// result."
				var newRange = blockExtend(range);

				// "Let node list be a list of nodes, initially empty."
				//
				// "Append to node list the first node in tree order that is
				// contained in new range and is an allowed child of "p", if any."
				var nodeList = getContainedNodes(newRange, function (node) {
					return isAllowedChild(node, "p");
				}).slice(0, 1);

				// "If node list is empty:"
				if (!nodeList.length) {
					// "If tag is not an allowed child of the active range's start
					// node, abort these steps."
					if (!isAllowedChild(tag, range.startContainer)) {
						return;
					}

					// "Set container to the result of calling createElement(tag)
					// on the context object."
					container = document.createElement(tag);

					// "Call insertNode(container) on the active range."
					range.insertNode(container);

					// "Call createElement("br") on the context object, and append
					// the result as the last child of container."
					// TODO not always
					container.appendChild(createEndBreak());

					// "Call collapse(container, 0) on the context object's
					// Selection."
					// TODO: remove selection from command
					Aloha.getSelection().collapse(container, 0);
					range.setStart(container, 0);
					range.setEnd(container, 0);

					// "Abort these steps."
					return;
				}

				// "While the nextSibling of the last member of node list is not
				// null and is an allowed child of "p", append it to node list."
				while (nodeList[nodeList.length - 1].nextSibling && isAllowedChild(nodeList[nodeList.length - 1].nextSibling, "p")) {
					nodeList.push(nodeList[nodeList.length - 1].nextSibling);
				}

				// "Wrap node list, with sibling criteria returning false and new
				// parent instructions returning the result of calling
				// createElement(tag) on the context object. Set container to the
				// result."
				container = wrap(
					nodeList,
					function () {
						return false;
					},
					function () {
						return document.createElement(tag);
					},
					range
				);
			}

			// If no container has been set yet, it is not possible to insert a paragraph at this position;
			// the following steps are skipped in order to prevent critical errors from occurring;
			if (!container) {
				return;
			}

			// "If container's local name is "address", "listing", or "pre":"
			var oldHeight, newHeight;
			if (container.tagName == "ADDRESS" || container.tagName == "LISTING" || container.tagName == "PRE") {

				if (!ContentRules.isAllowed(editable, 'br')) {
					return;
				}

				// "Let br be the result of calling createElement("br") on the
				// context object."
				var br = document.createElement("br");

				// remember the old height
				oldHeight = container.offsetHeight;

				// "Call insertNode(br) on the active range."
				range.insertNode(br);

				// determine the new height
				newHeight = container.offsetHeight;

				// "Call collapse(node, offset + 1) on the context object's
				// Selection."
				Aloha.getSelection().collapse(node, offset + 1);
				range.setStart(node, offset + 1);
				range.setEnd(node, offset + 1);

				// "If br is the last descendant of container, let br be the result
				// of calling createElement("br") on the context object, then call
				// insertNode(br) on the active range." (Fix: only do this, if the container height did not change by inserting a single <br/>)
				//
				// Work around browser bugs: some browsers select the
				// newly-inserted node, not per spec.
				if (oldHeight == newHeight && !isDescendant(nextNode(br), container)) {
					// TODO check
					range.insertNode(createEndBreak());
					Aloha.getSelection().collapse(node, offset + 1);
					range.setEnd(node, offset + 1);
				}

				// "Abort these steps."
				return;
			}

			// "If container's local name is "li", "dt", or "dd"; and either it has
			// no children or it has a single child and that child is a br:"
			if (listRelatedElements[container.tagName] && (!container.hasChildNodes() || (container.childNodes.length == 1 && isNamedHtmlElement(container.firstChild, "br")))) {
				// "Split the parent of the one-node list consisting of container."
				splitParent([container], range);

				// "If container has no children, call createElement("br") on the
				// context object and append the result as the last child of
				// container."
				// only do this, if inserting the br does NOT modify the offset height of the container
				//			if (!container.hasChildNodes()) {
				//				var oldHeight = container.offsetHeight, endBr = createEndBreak();
				//				container.appendChild(endBr);
				//				if (container.offsetHeight !== oldHeight) {
				//					container.removeChild(endBr);
				//				}
				//			}

				// "If container is a dd or dt, and it is not an allowed child of
				// any of its ancestors in the same editing host, set the tag name
				// of container to the default single-line container name and let
				// container be the result."
				if (isHtmlElementInArray(container, ["dd", "dt"]) && $_(getAncestors(container)).every(function (ancestor) { return !inSameEditingHost(container, ancestor) || !isAllowedChild(container, ancestor); })) {
					container = setTagName(container, defaultSingleLineContainerName, range);
				}

				// "Fix disallowed ancestors of container."
				fixDisallowedAncestors(container, range);

				// fix invalid nested lists
				if (isNamedHtmlElement(container, 'li') && isNamedHtmlElement(container.nextSibling, "li") && isHtmlElementInArray(container.nextSibling.firstChild, ["ol", "ul"])) {
					// we found a li containing only a br followed by a li containing a list as first element: merge the two li's
					var listParent = container.nextSibling,
						length = container.nextSibling.childNodes.length;
					for (i = 0; i < length; i++) {
						// we always move the first child into the container
						container.appendChild(listParent.childNodes[0]);
					}
					listParent.parentNode.removeChild(listParent);
				}

				// "Abort these steps."
				return;
			}

			// special behaviour when pressing enter in the last empty paragraph, that is nested in a blockquote
			if (isNamedHtmlElement(container, "p")
					&& isNamedHtmlElement(container.parentNode, "blockquote")
						&& !container.nextSibling
							&& (!container.hasChildNodes()
									|| (container.childNodes.length === 1
											&& isNamedHtmlElement(container.firstChild, "br")))) {
				jQuery(container.parentNode).after(container);
				return;
			}

			// "Let new line range be a new range whose start is the same as
			// the active range's, and whose end is (container, length of
			// container)."
			var newLineRange = Aloha.createRange();
			newLineRange.setStart(range.startContainer, range.startOffset);
			newLineRange.setEnd(container, getNodeLength(container));

			// "While new line range's start offset is zero and its start node is
			// not container, set its start to (parent of start node, index of
			// start node)."
			while (newLineRange.startOffset == 0 && newLineRange.startContainer != container) {
				newLineRange.setStart(newLineRange.startContainer.parentNode, Dom.getIndexInParent(newLineRange.startContainer));
			}

			// "While new line range's start offset is the length of its start node
			// and its start node is not container, set its start to (parent of
			// start node, 1 + index of start node)."
			while (newLineRange.startOffset == getNodeLength(newLineRange.startContainer) && newLineRange.startContainer != container) {
				newLineRange.setStart(newLineRange.startContainer.parentNode, 1 + Dom.getIndexInParent(newLineRange.startContainer));
			}

			// "Let end of line be true if new line range contains either nothing
			// or a single br, and false otherwise."
			var containedInNewLineRange = getContainedNodes(newLineRange);
			var endOfLine = !containedInNewLineRange.length || (containedInNewLineRange.length == 1 && isNamedHtmlElement(containedInNewLineRange[0], "br"));

			// "If the local name of container is "h1", "h2", "h3", "h4", "h5", or
			// "h6", and end of line is true, let new container name be the default
			// single-line container name."
			var newContainerName;
			if (/^H[1-6]$/.test(container.tagName) && endOfLine) {
				newContainerName = defaultSingleLineContainerName;

				// "Otherwise, if the local name of container is "dt" and end of line
				// is true, let new container name be "dd"."
			} else if (container.tagName == "DT" && endOfLine) {
				newContainerName = "dd";

				// "Otherwise, if the local name of container is "dd" and end of line
				// is true, let new container name be "dt"."
			} else if (container.tagName == "DD" && endOfLine) {
				newContainerName = "dt";

				// "Otherwise, let new container name be the local name of container."
			} else {
				newContainerName = container.tagName.toLowerCase();
			}

			if (!listRelatedElements[newContainerName.toUpperCase()]
					&& !ContentRules.isAllowed(editable, newContainerName)) {
				return;
			}

			// "Let new container be the result of calling createElement(new
			// container name) on the context object."
			var newContainer = document.createElement(newContainerName);

			// "Copy all non empty attributes of the container to new container."
			copyAttributes(container, newContainer);

			// "If new container has an id attribute, unset it."
			newContainer.removeAttribute("id");

			// "Insert new container into the parent of container immediately after
			// container."
			container.parentNode.insertBefore(newContainer, container.nextSibling);

			// "Let contained nodes be all nodes contained in new line range."
			var containedNodes = getAllContainedNodes(newLineRange);

			// "Let frag be the result of calling extractContents() on new line
			// range."
			var frag = newLineRange.extractContents();

			// "Unset the id attribute (if any) of each Element descendant of frag
			// that is not in contained nodes."
			var descendants = getDescendants(frag);
			for (i = 0; i < descendants.length; i++) {
				if (descendants[i].nodeType == $_.Node.ELEMENT_NODE && $_(containedNodes).indexOf(descendants[i]) == -1) {
					descendants[i].removeAttribute("id");
				}
			}

			var fragChildren = [],
				fragChild = frag.firstChild;
			if (fragChild) {
				do {
					if (!isWhitespaceNode(fragChild)) {
						fragChildren.push(fragChild);
					}
				} while (null != (fragChild = fragChild.nextSibling));
			}

			// if newContainer is a li and frag contains only a list, we add a br in the li (but only if the height would not change)
			if (isNamedHtmlElement(newContainer, 'li') && fragChildren.length && isHtmlElementInArray(fragChildren[0], ["ul", "ol"])) {
				oldHeight = newContainer.offsetHeight;
				var endBr = createEndBreak();
				newContainer.appendChild(endBr);
				newHeight = newContainer.offsetHeight;
				if (oldHeight !== newHeight) {
					newContainer.removeChild(endBr);
				}
			}

			// "Call appendChild(frag) on new container."
			newContainer.appendChild(frag);

			// "If container has no visible children, call createElement("br") on
			// the context object, and append the result as the last child of
			// container."
			ensureContainerEditable(container);

			// "If new container has no visible children, call createElement("br")
			// on the context object, and append the result as the last child of
			// new container."
			ensureContainerEditable(newContainer);

			// "Call collapse(new container, 0) on the context object's Selection."
			Aloha.getSelection().collapse(newContainer, 0);
			range.setStart(newContainer, 0);
			range.setEnd(newContainer, 0);
		}
	};

	//@}
	///// The insertText command /////
	//@{
	commands.inserttext = {
		action: function (value, range) {
			var i;

			// "Delete the contents of the active range, with strip wrappers
			// false."
			deleteContents(range, {
				stripWrappers: false
			});

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
				return;
			}

			// "If value's length is greater than one:"
			if (value.length > 1) {
				// "For each element el in value, take the action for the
				// insertText command, with value equal to el."
				for (i = 0; i < value.length; i++) {
					commands.inserttext.action(value[i], range);
				}

				// "Abort these steps."
				return;
			}

			// "If value is the empty string, abort these steps."
			if (value == "") {
				return;
			}

			// "If value is a newline (U+00A0), take the action for the
			// insertParagraph command and abort these steps."
			if (value == "\n") {
				commands.insertparagraph.action('', range);
				return;
			}

			// "Let node and offset be the active range's start node and offset."
			var node = range.startContainer;
			var offset = range.startOffset;

			// "If node has a child whose index is offset − 1, and that child is a
			// Text node, set node to that child, then set offset to node's
			// length."
			if (0 <= offset - 1 && offset - 1 < node.childNodes.length && node.childNodes[offset - 1].nodeType == $_.Node.TEXT_NODE) {
				node = node.childNodes[offset - 1];
				offset = getNodeLength(node);
			}

			// "If node has a child whose index is offset, and that child is a Text
			// node, set node to that child, then set offset to zero."
			if (0 <= offset && offset < node.childNodes.length && node.childNodes[offset].nodeType == $_.Node.TEXT_NODE) {
				node = node.childNodes[offset];
				offset = 0;
			}

			// "If value is a space (U+0020), and either node is an Element whose
			// resolved value for "white-space" is neither "pre" nor "pre-wrap" or
			// node is not an Element but its parent is an Element whose resolved
			// value for "white-space" is neither "pre" nor "pre-wrap", set value
			// to a non-breaking space (U+00A0)."
			var refElement = node.nodeType == $_.Node.ELEMENT_NODE ? node : node.parentNode;
			if (value == " " && refElement.nodeType == $_.Node.ELEMENT_NODE && jQuery.inArray($_.getComputedStyle(refElement).whiteSpace, ["pre", "pre-wrap"]) == -1) {
				value = "\xa0";
			}

			// "Record current overrides, and let overrides be the result."
			var overrides = recordCurrentOverrides(range);

			// "If node is a Text node:"
			if (node.nodeType == $_.Node.TEXT_NODE) {
				// "Call insertData(offset, value) on node."
				node.insertData(offset, value);

				// "Call collapse(node, offset) on the context object's Selection."
				Aloha.getSelection().collapse(node, offset);
				range.setStart(node, offset);

				// "Call extend(node, offset + 1) on the context object's
				// Selection."
				Aloha.getSelection().extend(node, offset + 1);
				range.setEnd(node, offset + 1);

				// "Otherwise:"
			} else {
				// "If node has only one child, which is a collapsed line break,
				// remove its child from it."
				//
				// FIXME: IE incorrectly returns false here instead of true
				// sometimes?
				if (node.childNodes.length == 1 && isCollapsedLineBreak(node.firstChild)) {
					node.removeChild(node.firstChild);
				}

				// "Let text be the result of calling createTextNode(value) on the
				// context object."
				var text = document.createTextNode(value);

				// "Call insertNode(text) on the active range."
				range.insertNode(text);

				// "Call collapse(text, 0) on the context object's Selection."
				Aloha.getSelection().collapse(text, 0);
				range.setStart(text, 0);

				// "Call extend(text, 1) on the context object's Selection."
				Aloha.getSelection().extend(text, 1);
				range.setEnd(text, 1);
			}

			// "Restore states and values from overrides."
			restoreStatesAndValues(overrides, range);

			// "Canonicalize whitespace at the active range's start."
			canonicalizeWhitespace(range.startContainer, range.startOffset);

			// "Canonicalize whitespace at the active range's end."
			canonicalizeWhitespace(range.endContainer, range.endOffset);

			// "Call collapseToEnd() on the context object's Selection."
			Aloha.getSelection().collapseToEnd();
			range.collapse(false);
		}
	};

	//@}
	///// The insertUnorderedList command /////
	//@{
	commands.insertunorderedlist = {
		// "Toggle lists with tag name "ul"."
		action: function (value, range) {
			toggleLists("ul", range);
		},
		// "True if the selection's list state is "mixed" or "mixed ul", false
		// otherwise."
		indeterm: function () {
			return (/^mixed( ul)?$/).test(getSelectionListState());
		},
		// "True if the selection's list state is "ul", false otherwise."
		state: function () {
			return getSelectionListState() == "ul";
		}
	};

	//@}
	///// The justifyCenter command /////
	//@{
	commands.justifycenter = {
		// "Justify the selection with alignment "center"."
		action: function (value, range) {
			justifySelection("center", range);
		},
		indeterm: function () {
			// "Block-extend the active range. Return true if among visible
			// editable nodes that are contained in the result and have no
			// children, at least one has alignment value "center" and at least one
			// does not. Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "center"; })
				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "center"; });
		},
		state: function () {
			// "Block-extend the active range. Return true if there is at least one
			// visible editable node that is contained in the result and has no
			// children, and all such nodes have alignment value "center".
			// Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return nodes.length && $_(nodes).every(function (node) {
				return getAlignmentValue(node) == "center";
			});
		},
		value: function () {
			// "Block-extend the active range, and return the alignment value of
			// the first visible editable node that is contained in the result and
			// has no children. If there is no such node, return "left"."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			if (nodes.length) {
				return getAlignmentValue(nodes[0]);
			}
			return "left";
		}
	};

	//@}
	///// The justifyFull command /////
	//@{
	commands.justifyfull = {
		// "Justify the selection with alignment "justify"."
		action: function (value, range) {
			justifySelection("justify", range);
		},
		indeterm: function () {
			// "Block-extend the active range. Return true if among visible
			// editable nodes that are contained in the result and have no
			// children, at least one has alignment value "justify" and at least
			// one does not. Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "justify"; })
				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "justify"; });
		},
		state: function () {
			// "Block-extend the active range. Return true if there is at least one
			// visible editable node that is contained in the result and has no
			// children, and all such nodes have alignment value "justify".
			// Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return nodes.length && $_(nodes).every(function (node) {
				return getAlignmentValue(node) == "justify";
			});
		},
		value: function () {
			// "Block-extend the active range, and return the alignment value of
			// the first visible editable node that is contained in the result and
			// has no children. If there is no such node, return "left"."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			if (nodes.length) {
				return getAlignmentValue(nodes[0]);
			}
			return "left";
		}
	};

	//@}
	///// The justifyLeft command /////
	//@{
	commands.justifyleft = {
		// "Justify the selection with alignment "left"."
		action: function (value, range) {
			justifySelection("left", range);
		},
		indeterm: function () {
			// "Block-extend the active range. Return true if among visible
			// editable nodes that are contained in the result and have no
			// children, at least one has alignment value "left" and at least one
			// does not. Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "left"; })
				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "left"; });
		},
		state: function () {
			// "Block-extend the active range. Return true if there is at least one
			// visible editable node that is contained in the result and has no
			// children, and all such nodes have alignment value "left".  Otherwise
			// return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return nodes.length && $_(nodes).every(function (node) {
				return getAlignmentValue(node) == "left";
			});
		},
		value: function () {
			// "Block-extend the active range, and return the alignment value of
			// the first visible editable node that is contained in the result and
			// has no children. If there is no such node, return "left"."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			if (nodes.length) {
				return getAlignmentValue(nodes[0]);
			}
			return "left";
		}
	};

	//@}
	///// The justifyRight command /////
	//@{
	commands.justifyright = {
		// "Justify the selection with alignment "right"."
		action: function (value, range) {
			justifySelection("right", range);
		},
		indeterm: function () {
			// "Block-extend the active range. Return true if among visible
			// editable nodes that are contained in the result and have no
			// children, at least one has alignment value "right" and at least one
			// does not. Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "right"; })
				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "right"; });
		},
		state: function () {
			// "Block-extend the active range. Return true if there is at least one
			// visible editable node that is contained in the result and has no
			// children, and all such nodes have alignment value "right".
			// Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return nodes.length && $_(nodes).every(function (node) {
				return getAlignmentValue(node) == "right";
			});
		},
		value: function () {
			// "Block-extend the active range, and return the alignment value of
			// the first visible editable node that is contained in the result and
			// has no children. If there is no such node, return "left"."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			if (nodes.length) {
				return getAlignmentValue(nodes[0]);
			}
			return "left";
		}
	};

	//@}
	///// The outdent command /////
	//@{
	commands.outdent = {
		action: function () {
			// "Let items be a list of all lis that are ancestor containers of the
			// range's start and/or end node."
			//
			// It's annoying to get this in tree order using functional stuff
			// without doing getDescendants(document), which is slow, so I do it
			// imperatively.
			var items = [];
			(function () {
				var ancestorContainer;
				for (ancestorContainer = getActiveRange().endContainer;
					     ancestorContainer != getActiveRange().commonAncestorContainer;
					     ancestorContainer = ancestorContainer.parentNode) {
					if (isNamedHtmlElement(ancestorContainer, "li")) {
						items.unshift(ancestorContainer);
					}
				}
				for (ancestorContainer = getActiveRange().startContainer;
					     ancestorContainer;
					     ancestorContainer = ancestorContainer.parentNode) {
					if (isNamedHtmlElement(ancestorContainer, "li")) {
						items.unshift(ancestorContainer);
					}
				}
			}());

			// "For each item in items, normalize sublists of item."
			$_(items).forEach(function (thisArg) {
				normalizeSublists(thisArg, getActiveRange());
			});

			// "Block-extend the active range, and let new range be the result."
			var newRange = blockExtend(getActiveRange());

			// "Let node list be a list of nodes, initially empty."
			//
			// "For each node node contained in new range, append node to node list
			// if the last member of node list (if any) is not an ancestor of node;
			// node is editable; and either node has no editable descendants, or is
			// an ol or ul, or is an li whose parent is an ol or ul."
			var nodeList = getContainedNodes(newRange, function (node) {
				return isEditable(node) && (!$_(getDescendants(node)).some(isEditable) || isHtmlElementInArray(node, ["ol", "ul"]) || (isNamedHtmlElement(node, 'li') && isHtmlElementInArray(node.parentNode, ["ol", "ul"])));
			});

			// "While node list is not empty:"
			while (nodeList.length) {
				// "While the first member of node list is an ol or ul or is not
				// the child of an ol or ul, outdent it and remove it from node
				// list."
				while (nodeList.length && (isHtmlElementInArray(nodeList[0], ["OL", "UL"]) || !isHtmlElementInArray(nodeList[0].parentNode, ["OL", "UL"]))) {
					outdentNode(nodeList.shift(), newRange);
				}

				// "If node list is empty, break from these substeps."
				if (!nodeList.length) {
					break;
				}

				// "Let sublist be a list of nodes, initially empty."
				var sublist = [];

				// "Remove the first member of node list and append it to sublist."
				sublist.push(nodeList.shift());

				// "While the first member of node list is the nextSibling of the
				// last member of sublist, and the first member of node list is not
				// an ol or ul, remove the first member of node list and append it
				// to sublist."
				while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling && !isHtmlElementInArray(nodeList[0], ["OL", "UL"])) {
					sublist.push(nodeList.shift());
				}

				// "Record the values of sublist, and let values be the result."
				var values = recordValues(sublist);

				// "Split the parent of sublist, with new parent null."
				splitParent(sublist, newRange);

				// "Fix disallowed ancestors of each member of sublist."
				$_(sublist).forEach(fixDisallowedAncestors);

				// "Restore the values from values."
				restoreValues(values, newRange);
			}
		}
	};

	//@}

	//////////////////////////////////
	///// Miscellaneous commands /////
	//////////////////////////////////

	///// The selectAll command /////
	//@{
	commands.selectall = {
		// Note, this ignores the whole globalRange/getActiveRange() thing and
		// works with actual selections.  Not suitable for autoimplementation.html.
		action: function () {
			// "Let target be the body element of the context object."
			var target = document.body;

			// "If target is null, let target be the context object's
			// documentElement."
			if (!target) {
				target = document.documentElement;
			}

			// "If target is null, call getSelection() on the context object, and
			// call removeAllRanges() on the result."
			if (!target) {
				Aloha.getSelection().removeAllRanges();

				// "Otherwise, call getSelection() on the context object, and call
				// selectAllChildren(target) on the result."
			} else {
				Aloha.getSelection().selectAllChildren(target);
			}
		}
	};

	//@}
	///// The styleWithCSS command /////
	//@{
	commands.stylewithcss = {
		action: function (value) {
			// "If value is an ASCII case-insensitive match for the string
			// "false", set the CSS styling flag to false. Otherwise, set the
			// CSS styling flag to true."
			cssStylingFlag = String(value).toLowerCase() != "false";
		},
		state: function () {
			return cssStylingFlag;
		}
	};

	//@}
	///// The useCSS command /////
	//@{
	commands.usecss = {
		action: function (value) {
			// "If value is an ASCII case-insensitive match for the string "false",
			// set the CSS styling flag to true. Otherwise, set the CSS styling
			// flag to false."
			cssStylingFlag = String(value).toLowerCase() == "false";
		}
	};
	//@}

	// Some final setup
	//@{
	(function () {
		// Opera 11.50 doesn't implement Object.keys, so I have to make an explicit
		// temporary, which means I need an extra closure to not leak the temporaries
		// into the global namespace.  >:(
		var commandNames = [];
		var command;
		for (command in commands) {
			if (commands.hasOwnProperty(command)) {
				commandNames.push(command);
			}
		}
		$_(commandNames).forEach(function (command) {
			// "If a command does not have a relevant CSS property specified, it
			// defaults to null."
			if (null == commands[command].relevantCssProperty) {
				commands[command].relevantCssProperty = null;
			}

			// "If a command has inline command activated values defined but
			// nothing else defines when it is indeterminate, it is indeterminate
			// if among editable Text nodes effectively contained in the active
			// range, there is at least one whose effective command value is one of
			// the given values and at least one whose effective command value is
			// not one of the given values."
			if (null != commands[command].inlineCommandActivatedValues && null == commands[command].indeterm) {
				commands[command].indeterm = function (range) {
					var values = $_(getAllEffectivelyContainedNodes(range, function (node) { return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE; }))
						.map(function (node) { return getEffectiveCommandValue(node, command); });

					var matchingValues = $_(values).filter(function (value) {
						return $_(commands[command].inlineCommandActivatedValues).indexOf(value) != -1;
					});

					return matchingValues.length >= 1 && values.length - matchingValues.length >= 1;
				};
			}

			// "If a command has inline command activated values defined, its state
			// is true if either no editable Text node is effectively contained in
			// the active range, and the active range's start node's effective
			// command value is one of the given values; or if there is at least
			// one editable Text node effectively contained in the active range,
			// and all of them have an effective command value equal to one of the
			// given values."
			if (null != commands[command].inlineCommandActivatedValues) {
				commands[command].state = function (range) {
					var nodes = getAllEffectivelyContainedNodes(range, function (node) {
						return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
					});

					if (nodes.length == 0) {
						return $_(commands[command].inlineCommandActivatedValues).indexOf(getEffectiveCommandValue(range.startContainer, command)) != -1;
					}
					return $_(nodes).every(function (node) {
						return $_(commands[command].inlineCommandActivatedValues).indexOf(getEffectiveCommandValue(node, command)) != -1;
					});
				};
			}

			// "If a command is a standard inline value command, it is
			// indeterminate if among editable Text nodes that are effectively
			// contained in the active range, there are two that have distinct
			// effective command values. Its value is the effective command value
			// of the first editable Text node that is effectively contained in the
			// active range, or if there is no such node, the effective command
			// value of the active range's start node."
			if (null != commands[command].standardInlineValueCommand) {
				commands[command].indeterm = function () {
					var values = $_(getAllEffectivelyContainedNodes(getActiveRange())).filter(function (node) { return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE; }, true)
						.map(function (node) { return getEffectiveCommandValue(node, command); });
					var i;
					for (i = 1; i < values.length; i++) {
						if (values[i] != values[i - 1]) {
							return true;
						}
					}
					return false;
				};

				commands[command].value = function (range) {
					var refNode = getAllEffectivelyContainedNodes(range, function (node) {
						return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
					})[0];

					if (typeof refNode == "undefined") {
						refNode = range.startContainer;
					}

					return getEffectiveCommandValue(refNode, command);
				};
			}
		});
	}());
	//@}
	return {
		commands: commands,
		execCommand: myExecCommand,
		queryCommandIndeterm: myQueryCommandIndeterm,
		queryCommandState: myQueryCommandState,
		queryCommandValue: myQueryCommandValue,
		queryCommandEnabled: myQueryCommandEnabled,
		queryCommandSupported: myQueryCommandSupported,
		copyAttributes: copyAttributes,
		createEndBreak: createEndBreak,
		isEndBreak: isEndBreak,
		ensureContainerEditable: ensureContainerEditable,
		isEditingHost: isEditingHost,
		isEditable: isEditable,
		getStateOverride: getStateOverride,
		setStateOverride: setStateOverride,
		resetOverrides: resetOverrides,
		unsetStateOverride: unsetStateOverride
	};
}); // end define
// vim: foldmarker=@{,@} foldmethod=marker
;
/* selection.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/selection',[
	'aloha/core',
	'jquery',
	'util/class',
	'util/range',
	'util/arrays',
	'util/strings',
	'util/dom',
	'util/dom2',
	'util/browser',
	'util/html',
	'aloha/console',
	'PubSub',
	'aloha/engine',
	'aloha/ecma5shims',
	'aloha/rangy-core'
], function (
	Aloha,
	jQuery,
	Class,
	Range,
	Arrays,
	Strings,
	Dom,
	Dom2,
	Browser,
	Html,
	console,
	PubSub,
	Engine,
	e5s
) {
	

	var GENTICS = window.GENTICS;

	function isCollapsedAndEmptyOrEndBr(rangeObject) {
		var firstChild;
		if (rangeObject.startContainer !== rangeObject.endContainer) {
			return false;
		}
		// check whether the container starts in an element node
		if (rangeObject.startContainer.nodeType != 1) {
			return false;
		}
		firstChild = rangeObject.startContainer.firstChild;
		return (!firstChild || (!firstChild.nextSibling && firstChild.nodeName == 'BR'));
	}

	function isCollapsedAndEndBr(rangeObject) {
		if (rangeObject.startContainer !== rangeObject.endContainer) {
			return false;
		}
		if (rangeObject.startContainer.nodeType != 1) {
			return false;
		}
		return Engine.isEndBreak(rangeObject.startContainer);
	}

	var prevStartContext = null;
	var prevEndContext = null;

	function makeContextHtml(node, parents) {
		var result = [],
			parent,
			len,
			i;
		if (1 === node.nodeType && node.nodeName !== 'BODY' && node.nodeName !== 'HTML') {
			result.push(node.cloneNode(false).outerHTML);
		} else {
			result.push('#' + node.nodeType);
		}
		for (i = 0, len = parents.length; i < len; i++) {
			parent = parents[i];
			if (parent.nodeName === 'BODY' || parent.nodeName === 'HTML') {
				// Although we limit the ancestors in most cases to the
				// active editable, in some cases (copy&paste) the
				// parent may be outside.
				// On IE7 this means the following code may clone the
				// HTML node too, which causes the browser to crash.
				// On other browsers, this is just an optimization
				// because the body and html elements should probably
				// not be considered part of the context of an edit
				// operation.
				break;
			}
			result.push(parent.cloneNode(false).outerHTML);
		}
		return result.join('');
	}

	function getChangedContext(node, context) {
		var until = Aloha.activeEditable ? Aloha.activeEditable.obj.parent()[0] : null;
		var parents = jQuery(node).parentsUntil(until).get();
		var html = makeContextHtml(node, parents);
		var equal = (context && node === context.node && Arrays.equal(context.parents, parents) && html === context.html);
		return equal ? null : {
			node: node,
			parents: parents,
			html: html
		};
	}

	function triggerSelectionContextChanged(rangeObject, event) {
		var startContainer = rangeObject.startContainer;
		var endContainer = rangeObject.endContainer;
		if (!startContainer || !endContainer) {
			console.warn("aloha/selection", "encountered range object without start or end container");
			return;
		}
		var startContext = getChangedContext(startContainer, prevStartContext);
		var endContext = getChangedContext(endContainer, prevEndContext);
		if (!startContext && !endContext) {
			return;
		}
		prevStartContext = startContext;
		prevEndContext = endContext;

		/**
		 * @api documented in the guides
		 */
		PubSub.pub('aloha.selection.context-change', {
			range: rangeObject,
			event: event
		});
	}

	/**
	 * Checks if `range` is contained inside an Aloha-Block
	 * @param {Range} range
	 * @return {*}
	 */
	function rangeStartInBlock(range) {
		return jQuery(range.startContainer).closest('.aloha-editable,.aloha-block,.aloha-table-cell-editable,.aloha-table-cell_active')
		                   .first()
			               .hasClass('aloha-block');
	}

	/**
	 * Gets parent block element
	 *
	 * @param {Element} element
	 * @return {*}
	 */
	function getBlockElement(element) {
		while (element && !Html.isBlock(element)) {
			element = element.parentNode;
		}
		return element;
	}

	/**
	 * Checks if `rangeObject` ends at the beginning of a text Node.
	 *
	 * @param {RangeObject} rangeObject
	 * @return {boolean}
	 */
	function isEndContainerAtBeginTexNode(rangeObject) {
		var endContainer = rangeObject.endContainer;
		var endOffset = rangeObject.endOffset;
		var i;

		if (!Dom2.isTextNode(endContainer)) {
			return false;
		}

		for (i = endOffset - 1; i >= 0; i--) {
			if (jQuery.trim(endContainer.textContent[i]).length !== 0) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Checks if ranges ends in the beginning of a block element.
	 *
	 * @param {RangeObejct} rangeObejct
	 * @return {boolean}
	 */
	function rangeEndsInBeginningBlockElement(rangeObejct) {
		var endContainer = rangeObejct.endContainer;
		var node = endContainer;

		var blockElement = getBlockElement(rangeObejct.endContainer);

		if (!isEndContainerAtBeginTexNode(rangeObejct)) {
			return false;
		}

		node = node.previousSibling || node.parentNode;

		while (node !== blockElement) {
			if (node.previousSibling) {
				node = node.previousSibling;
				if (Html.isRenderedNode(node)) {
					return false;
				}
			} else {
				node = node.parentNode;
			}
		}

		return node === blockElement;
	}

	/**
	 * Sets the end of `range` before `element`.
	 * @param {Range} range
	 * @param {Element} element
	 */
	function setEndRangeBeforeElement(range, element) {
		range.setEndBefore(element);

		Aloha.getSelection().removeAllRanges();
		Aloha.getSelection().addRange(range);
	}

	/**
	 * Corrects the range if the range is expanded and it ends in the beginning of a
	 * block element.
	 *
	 * @param {RangeObject} rangeObject
	 */
	function correctFirefoxRangeIssue(rangeObject) {
		if (!rangeObject.isCollapsed() && rangeEndsInBeginningBlockElement(rangeObject)) {
			var blockElement = getBlockElement(rangeObject.endContainer);
			var range = Aloha.getSelection().getRangeAt(0);

			setEndRangeBeforeElement(range, blockElement);
		}
	}

	/**
	 * @namespace Aloha
	 * @class Selection
	 * This singleton class always represents the current user selection
	 * @singleton
	 */
	var Selection = Class.extend({
		_constructor: function () {
			// Pseudo Range Clone being cleaned up for better HTML wrapping support
			this.rangeObject = {};

			this.preventSelectionChangedFlag = false; // will remember if someone urged us to skip the next aloha-selection-changed event

			this.correctSelectionFlag = false; // this is true, when the current selection is corrected (to prevent endless loops)

			// define basics first
			this.tagHierarchy = {
				'textNode': {},
				'abbr': {
					'textNode': true
				},
				'b': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'a': true,
					'del': true,
					'ins': true,
					'u': true,
					'cite': true,
					'q': true,
					'code': true,
					'abbr': true,
					'strong': true
				},
				'pre': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'a': true,
					'del': true,
					'ins': true,
					'u': true,
					'cite': true,
					'q': true,
					'code': true,
					'abbr': true
				},
				'blockquote': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'a': true,
					'del': true,
					'ins': true,
					'u': true,
					'cite': true,
					'q': true,
					'code': true,
					'abbr': true,
					'p': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true
				},
				'ins': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'a': true,
					'u': true,
					'p': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true
				},
				'ul': {
					'li': true
				},
				'ol': {
					'li': true
				},
				'dl': {
					'dt': true,
					'dd': true
				},
				'dt': {
					'textNode': true
				},
				'dd': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'ul': true,
					'ol': true,
					'dt': true,
					'table': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true,
					'del': true,
					'ins': true,
					'u': true,
					'p': true,
					'div': true,
					'pre': true,
					'blockquote': true,
					'a': true
				},
				'li': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'ul': true,
					'ol': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true,
					'p': true,
					'del': true,
					'ins': true,
					'u': true,
					'a': true
				},
				'tr': {
					'td': true,
					'th': true
				},
				'table': {
					'tr': true
				},
				'div': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'ul': true,
					'ol': true,
					'dt': true,
					'table': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true,
					'del': true,
					'ins': true,
					'u': true,
					'p': true,
					'div': true,
					'pre': true,
					'blockquote': true,
					'a': true
				},
				'h1': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'a': true,
					'del': true,
					'ins': true,
					'u': true
				}
			};

			// now reference the basics for all other equal tags (important: don't forget to include
			// the basics itself as reference: 'b' : this.tagHierarchy.b
			this.tagHierarchy = {
				'textNode': this.tagHierarchy.textNode,
				'abbr': this.tagHierarchy.abbr,
				'br': this.tagHierarchy.textNode,
				'img': this.tagHierarchy.textNode,
				'b': this.tagHierarchy.b,
				'strong': this.tagHierarchy.b,
				'code': this.tagHierarchy.b,
				'q': this.tagHierarchy.b,
				'blockquote': this.tagHierarchy.blockquote,
				'cite': this.tagHierarchy.b,
				'i': this.tagHierarchy.b,
				'em': this.tagHierarchy.b,
				'sup': this.tagHierarchy.b,
				'sub': this.tagHierarchy.b,
				'span': this.tagHierarchy.b,
				'del': this.tagHierarchy.del,
				'ins': this.tagHierarchy.ins,
				'u': this.tagHierarchy.b,
				'p': this.tagHierarchy.b,
				'pre': this.tagHierarchy.pre,
				'a': this.tagHierarchy.b,
				'ul': this.tagHierarchy.ul,
				'ol': this.tagHierarchy.ol,
				'dl': this.tagHierarchy.dl,
				'li': this.tagHierarchy.li,
				'div': this.tagHierarchy.div,
				'h1': this.tagHierarchy.h1,
				'h2': this.tagHierarchy.h1,
				'h3': this.tagHierarchy.h1,
				'h4': this.tagHierarchy.h1,
				'h5': this.tagHierarchy.h1,
				'h6': this.tagHierarchy.h1,
				// for tables (and all related tags) we set the hierarchy to div
				// this enables to add anything into tables. We also need to set this
				// for tr, td and th, because the check in canTag1WrapTag2 does not check
				// transitively
				'table': this.tagHierarchy.div,
				'tr': this.tagHierarchy.div,
				'th': this.tagHierarchy.div,
				'td': this.tagHierarchy.div
			};

			// When applying this elements to selection they will replace the assigned elements
			this.replacingElements = {
				'h1': {
					'p': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true,
					'pre': true,
					'blockquote': true
				}
			};
			this.replacingElements = {
				'h1': this.replacingElements.h1,
				'h2': this.replacingElements.h1,
				'h3': this.replacingElements.h1,
				'h4': this.replacingElements.h1,
				'h5': this.replacingElements.h1,
				'h6': this.replacingElements.h1,
				'pre': this.replacingElements.h1,
				'p': this.replacingElements.h1,
				'blockquote': this.replacingElements.h1
			};
			this.allowedToStealElements = {
				'h1': {
					'textNode': true
				}
			};
			this.allowedToStealElements = {
				'h1': this.allowedToStealElements.h1,
				'h2': this.allowedToStealElements.h1,
				'h3': this.allowedToStealElements.h1,
				'h4': this.allowedToStealElements.h1,
				'h5': this.allowedToStealElements.h1,
				'h6': this.allowedToStealElements.h1,
				'p': this.tagHierarchy.b
			};
		},

		/**
		 * Class definition of a SelectionTree (relevant for all formatting / markup changes)
		 * TODO: remove this (was moved to range.js)
		 * Structure:
		 * +
		 * |-domobj: <reference to the DOM Object> (NOT jQuery)
		 * |-selection: defines if this node is marked by user [none|partial|full]
		 * |-children: recursive structure like this
		 * @hide
		 */
		SelectionTree: function () {
			this.domobj = {};
			this.selection = undefined;
			this.children = [];
		},

		/**
		 * INFO: Method is used for integration with Gentics Aloha, has no use otherwise
		 * Updates the rangeObject according to the current user selection
		 * Method is always called on selection change
		 * @param objectClicked Object that triggered the selectionChange event
		 * @return true when rangeObject was modified, false otherwise
		 * @hide
		 */
		onChange: function (objectClicked, event, timeout, editableChanged) {
			if (this.updateSelectionTimeout) {
				window.clearTimeout(this.updateSelectionTimeout);
			}

			// We get no valid selection from IE it the event target was
			// a text input element, so the following setTimeout() approach
			// would never terminate. We let the browser handle all events
			// in such elements, but we still have to make sure the
			// editable containing the event target is active.
			if (event && (event.target.nodeName === 'INPUT' || event.target.nodeName === 'TEXTAREA')) {
				var editable = Aloha.getEditableHost(objectClicked);

				if (editable && Aloha.activeEditable !== editable) {
					editable.activate();
				}

				return;
			}

			// We have to update the selection in a timeout due to an IE
			// bug that is is caused by selecting some text and then
			// clicking once inside the selection (which collapses the
			// selection inside the previous selection).
			var selection = this;
			this.updateSelectionTimeout = window.setTimeout(function () {
				var range = new Aloha.Selection.SelectionRange(true);
				// We have to work around an IE bug that causes the user
				// selection to be incorrectly set on the body element
				// when the updateSelectionTimeout triggers. The
				// selection corrects itself after waiting a while.
				if (!range.startContainer || 'HTML' === range.startContainer.nodeName || 'BODY' === range.startContainer.nodeName) {
					if (!this.updateSelectionTimeout) {
						// First wait 5 millis, then 20 millis, 50 millis, 110 millis etc.
						selection.onChange(objectClicked, event, 10 + (timeout || 5) * 2);
					}
					return;
				} else {
					// activate the editable host of the selection
					var editable = Aloha.getEditableHost(jQuery(range.startContainer));
					if (editable) {
						editable.activate();
					}

					// And yet another IE workaround. Somehow the caret is not
					// positioned inside the clicked editable. This occures only
					// when switching editables in IE. In those cases the caret is
					// invisible. I tried to trace the origin of the issue but i
					// could not find the place where the caret is mispositioned.
					// I noticed that IE is sometimes adding drag handles to
					// editables. Aloha is removing those handles.
					// If those handles are visible it apears that two clicks are needed
					// to activate the editable. The first click is to select the
					// editable and the second to enable it and activeate it. I added a
					// range select call that will cirumvent this issue by resetting
					// the selection. I also checked the range object. In all cases
					// i found the range object contained correct properties. The
					// workaround will only be applied for IE.
					if (Aloha.browser.msie && editableChanged) {
						range.select();
					}
				}
				Aloha.Selection._updateSelection(event, range);
			}, timeout || 5);
		},

		/**
		 * prevents the next aloha-selection-changed event from being triggered
		 */
		preventSelectionChanged: function () {
			this.preventSelectionChangedFlag = true;
		},

		/**
		 * will return wheter selection change event was prevented or not, and reset the preventSelectionChangedFlag
		 * @return {Boolean} true if aloha-selection-change event was prevented
		 */
		isSelectionChangedPrevented: function () {
			var prevented = this.preventSelectionChangedFlag;
			this.preventSelectionChangedFlag = false;
			return prevented;
		},

		/**
		 * Checks if the current rangeObject common ancector container is edtiable
		 * @return {Boolean} true if current common ancestor is editable
		 */
		isSelectionEditable: function () {
			return (this.rangeObject.commonAncestorContainer && jQuery(this.rangeObject.commonAncestorContainer).contentEditable());
		},

		/**
		 * This method checks, if the current rangeObject common ancestor container has a 'data-aloha-floatingmenu-visible' Attribute.
		 * Needed in Floating Menu for exceptional display of floatingmenu.
		 */
		isFloatingMenuVisible: function () {
			var visible = jQuery(Aloha.Selection.rangeObject.commonAncestorContainer).attr('data-aloha-floatingmenu-visible');
			if (visible !== 'undefined') {
				if (visible === 'true') {
					return true;
				}
				return false;
			}
			return false;
		},

		/**
		 * INFO: Method is used for integration with Gentics Aloha, has no use otherwise
		 * Updates the rangeObject according to the current user selection
		 * Method is always called on selection change
		 * @param event jQuery browser event object
		 * @return true when rangeObject was modified, false otherwise
		 * @hide
		 */
		updateSelection: function (event) {
			return this._updateSelection(event, null);
		},

		/**
		 * Internal version of updateSelection that adds the range parameter to be
		 * able to work around an IE bug that caused the current user selection
		 * sometimes to be on the body element.
		 * @param {Object} event
		 * @param {Object} range a substitute for the current user selection. if not provided,
		 *   the current user selection will be used.
		 * @hide
		 */
		_updateSelection: function (event, range) {
			if (event) {
				if (event.target.nodeName === 'INPUT' || event.target.nodeName === 'TEXTAREA'
						|| (event.originalEvent && true === event.originalEvent.stopSelectionUpdate)) {
					return false;
				}
			}

			if (typeof range === 'undefined') {
				return false;
			}

			this.rangeObject = range =
					range || new Aloha.Selection.SelectionRange(true);

			// workaround for FF selection bug, where it is possible to move the selection INTO a hr
			if (range && range.startContainer
					&& 'HR' === range.startContainer.nodeName
					&& range.endContainer
					&& 'HR' === range.endContainer.nodeName) {
				Aloha.getSelection().removeAllRanges();
				return true;
			}

			// Determine the common ancestor container and update the selection
			// tree.
			range.update();

			// Workaround for nasty IE bug that allows the user to select
			// text nodes inside areas with contenteditable "false"
			if (range && range.startContainer && range.endContainer && !this.correctSelectionFlag) {
				var inEditable =
						jQuery(range.commonAncestorContainer)
							.closest('.aloha-editable').length > 0;

				// we only will move the selection out of the non-editable area (in an editable)
				// if the range is collapsed (blinking cursor)
				if (inEditable && range.isCollapsed()) {
					var validStartPosition = this._validEditablePosition(range.startContainer);
					var validEndPosition = this._validEditablePosition(range.endContainer);
					var newPos;
					// when we are moving down (with the cursor down key), we want to position the
					// cursor AFTER the non-editable area
					// otherwise BEFORE the non-editable area
					var movingDown = event && (event.keyCode === 40 || event.keyCode === 39);

					if (!validStartPosition) {
						newPos = this._getNearestEditablePosition(range.startContainer, movingDown);
						if (newPos) {
							range.startContainer = newPos.container;
							range.startOffset = newPos.offset;
						}
					}
					if (!validEndPosition) {
						newPos = this._getNearestEditablePosition(range.endContainer, movingDown);
						if (newPos) {
							range.endContainer = newPos.container;
							range.endOffset = newPos.offset;
						}
					}
					if (!validStartPosition || !validEndPosition) {
						this.correctSelectionFlag = true;
						range.correctRange();
						range.select();
					}
				}
			}
			this.correctSelectionFlag = false;

			// check if aloha-selection-changed event has been prevented
			if (this.isSelectionChangedPrevented()) {
				return true;
			}

			Aloha.trigger('aloha-selection-changed-before', [this.rangeObject, event]);

			// throw the event that the selection has changed. Plugins now have the
			// chance to react on the currentElements[childCount].children.lengthged selection
			Aloha.trigger('aloha-selection-changed', [this.rangeObject, event]);

			triggerSelectionContextChanged(this.rangeObject, event);

			Aloha.trigger('aloha-selection-changed-after', [this.rangeObject, event]);

			return true;
		},

		/**
		 * Check whether a position with the given node as container is a valid editable position
		 * @param {DOMObject} node DOM node
		 * @return true if the position is editable, false if not
		 */
		_validEditablePosition: function (node) {
			if (!node) {
				return false;
			}
			switch (node.nodeType) {
			case 1:
				return jQuery(node).contentEditable();
			case 3:
				return jQuery(node.parentNode).contentEditable();
			default:
				return false;
			}
		},

		/**
		 * Starting with the given node (which is supposed to be not editable)
		 * find the nearest editable position
		 * 
		 * @param {DOMObject} node DOM node
		 * @param {Boolean} forward true for searching forward, false for searching backward
		 */
		_getNearestEditablePosition: function (node, forward) {
			var current = node;
			var parent = current.parentNode;
			while (parent !== null && !jQuery(parent).contentEditable()) {
				current = parent;
				parent = parent.parentNode;
			}
			if (current === null) {
				return false;
			}
			if (forward) {
				// check whether the element after the non editable element is editable and a blocklevel element
				if (Dom.isBlockLevelElement(current.nextSibling) && jQuery(current.nextSibling).contentEditable()) {
					return {
						container: current.nextSibling,
						offset: 0
					};
				} else {
					return {
						container: parent,
						offset: Dom.getIndexInParent(current) + 1
					};
				}
			} else {
				// check whether the element before the non editable element is editable and a blocklevel element
				if (Dom.isBlockLevelElement(current.previousSibling) && jQuery(current.previousSibling).contentEditable()) {
					return {
						container: current.previousSibling,
						offset: current.previousSibling.childNodes.length
					};
				} else {
					return {
						container: parent,
						offset: Dom.getIndexInParent(current)
					};
				}
			}
		},

		/**
		 * creates an object with x items containing all relevant dom objects.
		 * Structure:
		 * +
		 * |-domobj: <reference to the DOM Object> (NOT jQuery)
		 * |-selection: defines if this node is marked by user [none|partial|full]
		 * |-children: recursive structure like this ("x.." because it's then shown last in DOM Browsers...)
		 * TODO: remove this (was moved to range.js)
		 *
		 * @param rangeObject "Aloha clean" range object including a commonAncestorContainer
		 * @return obj selection
		 * @hide
		 */
		getSelectionTree: function (rangeObject) {
			if (!rangeObject) { // if called without any parameters, the method acts as getter for this.selectionTree
				return this.rangeObject.getSelectionTree();
			}
			if (!rangeObject.commonAncestorContainer) {
				Aloha.Log.error(this, 'the rangeObject is missing the commonAncestorContainer');
				return false;
			}

			this.inselection = false;

			// before getting the selection tree, we do a cleanup
			if (GENTICS.Utils.Dom.doCleanup({ 'merge': true }, rangeObject)) {
				rangeObject.update();
				rangeObject.select();
			}

			return this.recursiveGetSelectionTree(rangeObject, rangeObject.commonAncestorContainer);
		},

		/**
		 * Recursive inner function for generating the selection tree.
		 * TODO: remove this (was moved to range.js)
		 * @param rangeObject range object
		 * @param currentObject current DOM object for which the selection tree shall be generated
		 * @return array of SelectionTree objects for the children of the current DOM object
		 * @hide
		 */
		recursiveGetSelectionTree: function (rangeObject, currentObject) {
			// get all direct children of the given object
			var jQueryCurrentObject = jQuery(currentObject),
				childCount = 0,
				that = this,
				currentElements = [];

			jQueryCurrentObject.contents().each(function (index) {
				var selectionType = 'none',
					startOffset = false,
					endOffset = false,
					collapsedFound = false,
					i,
				    elementsLength,
					noneFound = false,
					partialFound = false,
					fullFound = false;

				// check for collapsed selections between nodes
				if (rangeObject.isCollapsed() && currentObject === rangeObject.startContainer && rangeObject.startOffset == index) {
					// insert an extra selectiontree object for the collapsed selection here
					currentElements[childCount] = new Aloha.Selection.SelectionTree();
					currentElements[childCount].selection = 'collapsed';
					currentElements[childCount].domobj = undefined;
					that.inselection = false;
					collapsedFound = true;
					childCount++;
				}

				if (!that.inselection && !collapsedFound) {
					// the start of the selection was not yet found, so look for it now
					// check whether the start of the selection is found here

					// Try to read the nodeType property and return if we do not have permission
					// ie.: frame document to an external URL
					var nodeType;
					try {
						nodeType = this.nodeType;
					} catch (e) {
						return;
					}

					// check is dependent on the node type
					switch (nodeType) {
					case 3:
						// text node
						if (this === rangeObject.startContainer) {
							// the selection starts here
							that.inselection = true;

							// when the startoffset is > 0, the selection type is only partial
							selectionType = rangeObject.startOffset > 0 ? 'partial' : 'full';
							startOffset = rangeObject.startOffset;
							endOffset = this.length;
						}
						break;
					case 1:
						// element node
						if (this === rangeObject.startContainer && rangeObject.startOffset === 0) {
							// the selection starts here
							that.inselection = true;
							selectionType = 'full';
						}
						if (currentObject === rangeObject.startContainer && rangeObject.startOffset === index) {
							// the selection starts here
							that.inselection = true;
							selectionType = 'full';
						}
						break;
					}
				}

				if (that.inselection && !collapsedFound) {
					if (selectionType == 'none') {
						selectionType = 'full';
					}
					// we already found the start of the selection, so look for the end of the selection now
					// check whether the end of the selection is found here

					switch (this.nodeType) {
					case 3:
						// text node
						if (this === rangeObject.endContainer) {
							// the selection ends here
							that.inselection = false;

							// check for partial selection here
							if (rangeObject.endOffset < this.length) {
								selectionType = 'partial';
							}
							if (startOffset === false) {
								startOffset = 0;
							}
							endOffset = rangeObject.endOffset;
						}
						break;
					case 1:
						// element node
						if (this === rangeObject.endContainer && rangeObject.endOffset === 0) {
							that.inselection = false;
						}
						break;
					}
					if (currentObject === rangeObject.endContainer && rangeObject.endOffset <= index) {
						that.inselection = false;
						selectionType = 'none';
					}
				}

				// create the current selection tree entry
				currentElements[childCount] = new Aloha.Selection.SelectionTree();
				currentElements[childCount].domobj = this;
				currentElements[childCount].selection = selectionType;
				if (selectionType == 'partial') {
					currentElements[childCount].startOffset = startOffset;
					currentElements[childCount].endOffset = endOffset;
				}

				// now do the recursion step into the current object
				currentElements[childCount].children = that.recursiveGetSelectionTree(rangeObject, this);
				elementsLength = currentElements[childCount].children.length;

				// check whether a selection was found within the children
				if (elementsLength > 0) {
					for (i = 0; i < elementsLength; ++i) {
						switch (currentElements[childCount].children[i].selection) {
						case 'none':
							noneFound = true;
							break;
						case 'full':
							fullFound = true;
							break;
						case 'partial':
							partialFound = true;
							break;
						}
					}

					if (partialFound || (fullFound && noneFound)) {
						// found at least one 'partial' selection in the children, or both 'full' and 'none', so this element is also 'partial' selected
						currentElements[childCount].selection = 'partial';
					} else if (fullFound && !partialFound && !noneFound) {
						// only found 'full' selected children, so this element is also 'full' selected
						currentElements[childCount].selection = 'full';
					}
				}

				childCount++;
			});

			// extra check for collapsed selections at the end of the current element
			if (rangeObject.isCollapsed() && currentObject === rangeObject.startContainer && rangeObject.startOffset == currentObject.childNodes.length) {
				currentElements[childCount] = new Aloha.Selection.SelectionTree();
				currentElements[childCount].selection = 'collapsed';
				currentElements[childCount].domobj = undefined;
			}

			return currentElements;
		},

		/**
		 * Get the currently selected range
		 * @return {Aloha.Selection.SelectionRange} currently selected range
		 * @method
		 */
		getRangeObject: function () {
			return this.rangeObject;
		},

		/**
		 * method finds out, if a node is within a certain markup or not
		 * @param rangeObj Aloha rangeObject
		 * @param startOrEnd boolean; defines, if start or endContainer should be used: false for start, true for end
		 * @param markupObject jQuery object of the markup to look for
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @param limitObject dom object which limits the search are within the dom. normally this will be the active Editable
		 * @return true, if the markup is effective on the range objects start or end node
		 * @hide
		 */
		isRangeObjectWithinMarkup: function (rangeObject, startOrEnd, markupObject, tagComparator, limitObject) {
			var domObj = !startOrEnd ? rangeObject.startContainer : rangeObject.endContainer,
				that = this,
				parents = jQuery(domObj).parents(),
				returnVal = false,
				i = -1;

			// check if a comparison method was passed as parameter ...
			if (typeof tagComparator !== 'undefined' && typeof tagComparator !== 'function') {
				Aloha.Log.error(this, 'parameter tagComparator is not a function');
			}
			// ... if not use this as standard tag comparison method
			if (typeof tagComparator === 'undefined') {
				tagComparator = function (domobj, markupObject) {
					return that.standardTextLevelSemanticsComparator(domobj, markupObject); // TODO should actually be this.getStandardTagComparator(markupObject)
				};
			}

			if (parents.length > 0) {
				parents.each(function () {
					// the limit object was reached (normally the Editable Element)
					if (this === limitObject) {
						Aloha.Log.debug(that, 'reached limit dom obj');
						return false; // break() of jQuery .each(); THIS IS NOT THE FUNCTION RETURN VALUE
					}
					if (tagComparator(this, markupObject)) {
						if (returnVal === false) {
							returnVal = [];
						}
						Aloha.Log.debug(that, 'reached object equal to markup');
						i++;
						returnVal[i] = this;
						return true; // continue() of jQuery .each(); THIS IS NOT THE FUNCTION RETURN VALUE
					}
				});
			}
			return returnVal;
		},

		/**
		 * standard method, to compare a domobj and a jquery object for sections and grouping content (e.g. p, h1, h2, ul, ....).
		 * is always used when no other tag comparator is passed as parameter
		 * @param domobj domobject to compare with markup
		 * @param markupObject jQuery object of the markup to compare with domobj
		 * @return true if objects are equal and false if not
		 * @hide
		 */
		standardSectionsAndGroupingContentComparator: function (domobj, markupObject) {
			if (domobj.nodeType !== 1) {
				Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
				return false;
			}
			if (!markupObject[0].nodeName) {
				return false;
			}
			var elemMap = Aloha.Selection.replacingElements[domobj.nodeName.toLowerCase()];
			return elemMap && elemMap[markupObject[0].nodeName.toLowerCase()];
		},

		/**
		 * standard method, to compare a domobj and a jquery object for their tagName (aka span elements, e.g. b, i, sup, span, ...).
		 * is always used when no other tag comparator is passed as parameter
		 * @param domobj domobject to compare with markup
		 * @param markupObject jQuery object of the markup to compare with domobj
		 * @return true if objects are equal and false if not
		 * @hide
		 */
		standardTagNameComparator: function (domobj, markupObject) {
			if (domobj.nodeType === 1) {
				if (domobj.nodeName != markupObject[0].nodeName) {
					return false;
				}
				return true;
			}
			Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
			return false;
		},

		/**
		 * standard method, to compare a domobj and a jquery object for text level semantics (aka span elements, e.g. b, i, sup, span, ...).
		 * is always used when no other tag comparator is passed as parameter
		 * @param domobj domobject to compare with markup
		 * @param markupObject jQuery object of the markup to compare with domobj
		 * @return true if objects are equal and false if not
		 * @hide
		 */
		standardTextLevelSemanticsComparator: function (domobj, markupObject) {
			// only element nodes can be compared
			if (domobj.nodeType === 1) {
				if (domobj.nodeName != markupObject[0].nodeName) {
					return false;
				}
				if (!this.standardAttributesComparator(domobj, markupObject)) {
					return false;
				}
				return true;
			}
			Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
			return false;
		},


		/**
		 * standard method, to compare attributes of one dom obj and one markup obj (jQuery)
		 * @param domobj domobject to compare with markup
		 * @param markupObject jQuery object of the markup to compare with domobj
		 * @return true if objects are equal and false if not
		 * @hide
		 */
		standardAttributesComparator: function (domobj, markupObject) {
			var classesA = Strings.words((domobj && domobj.className) || '');
			var classesB = Strings.words((markupObject.length && markupObject[0].className) || '');
			Arrays.sortUnique(classesA);
			Arrays.sortUnique(classesB);
			return Arrays.equal(classesA, classesB);
		},

		/**
		 * method finds out, if a node is within a certain markup or not
		 * @param rangeObj Aloha rangeObject
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return void; TODO: should return true if the markup applied successfully and false if not
		 * @hide
		 */
		changeMarkup: function (rangeObject, markupObject, tagComparator) {
			var tagName = markupObject[0].tagName.toLowerCase(),
				newCAC,
			    limitObject,
				backupRangeObject,
				relevantMarkupObjectsAtSelectionStart = this.isRangeObjectWithinMarkup(rangeObject, false, markupObject, tagComparator, limitObject),
				relevantMarkupObjectsAtSelectionEnd = this.isRangeObjectWithinMarkup(rangeObject, true, markupObject, tagComparator, limitObject),
				nextSibling,
			    relevantMarkupObjectAfterSelection,
				prevSibling,
			    relevantMarkupObjectBeforeSelection,
				extendedRangeObject;
			var parentElement;

			// if the element is a replacing element (like p/h1/h2/h3/h4/h5/h6...), which must not wrap each other
			// use a clone of rangeObject
			if (this.replacingElements[tagName]) {
				// backup rangeObject for later selection;
				backupRangeObject = rangeObject;

				// create a new range object to not modify the orginal
				rangeObject = new this.SelectionRange(rangeObject);

				// either select the active Editable as new commonAncestorContainer (CAC) or use the body
				if (Aloha.activeEditable) {
					newCAC = Aloha.activeEditable.obj.get(0);
				} else {
					newCAC = jQuery('body');
				}
				// update rangeObject by setting the newCAC and automatically recalculating the selectionTree
				rangeObject.update(newCAC);

				// store the information, that the markupObject can be replaced (not must be!!) inside the jQuery markup object
				markupObject.isReplacingElement = true;
			} else {
				// if the element is NOT a replacing element, then something needs to be selected, otherwise it can not be wrapped
				// therefor the method can return false, if nothing is selected ( = rangeObject is collapsed)
				if (rangeObject.isCollapsed()) {
					Aloha.Log.debug(this, 'early returning from applying markup because nothing is currently selected');
					return false;
				}
			}

			// is Start/End DOM Obj inside the markup to change
			if (Aloha.activeEditable) {
				limitObject = Aloha.activeEditable.obj[0];
			} else {
				limitObject = jQuery('body');
			}

			if (!markupObject.isReplacingElement && rangeObject.startOffset === 0) { // don't care about replacers, because they never extend
				if (null != (prevSibling = this.getTextNodeSibling(false, rangeObject.commonAncestorContainer.parentNode, rangeObject.startContainer))) {
					relevantMarkupObjectBeforeSelection = this.isRangeObjectWithinMarkup({
						startContainer: prevSibling,
						startOffset: 0
					}, false, markupObject, tagComparator, limitObject);
				}
			}
			if (!markupObject.isReplacingElement && (rangeObject.endOffset === rangeObject.endContainer.length)) { // don't care about replacers, because they never extend
				if (null != (nextSibling = this.getTextNodeSibling(true, rangeObject.commonAncestorContainer.parentNode, rangeObject.endContainer))) {
					relevantMarkupObjectAfterSelection = this.isRangeObjectWithinMarkup({
						startContainer: nextSibling,
						startOffset: 0
					}, false, markupObject, tagComparator, limitObject);
				}
			}

			// decide what to do (expand or reduce markup)
			// Alternative A: from markup to no-markup: markup will be removed in selection;
			// reapplied from original markup start to selection start
			if (!markupObject.isReplacingElement && (relevantMarkupObjectsAtSelectionStart && !relevantMarkupObjectsAtSelectionEnd)) {
				Aloha.Log.info(this, 'markup 2 non-markup');
				this.prepareForRemoval(rangeObject.getSelectionTree(), markupObject, tagComparator);
				jQuery(relevantMarkupObjectsAtSelectionStart).addClass('preparedForRemoval');
				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionStart, rangeObject, false, tagComparator);
			} else if (!markupObject.isReplacingElement && relevantMarkupObjectsAtSelectionStart && relevantMarkupObjectsAtSelectionEnd) {
				// Alternative B: from markup to markup:
				// remove selected markup (=split existing markup if single, shrink if two different)
				Aloha.Log.info(this, 'markup 2 markup');
				this.prepareForRemoval(rangeObject.getSelectionTree(), markupObject, tagComparator);
				this.splitRelevantMarkupObject(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject, tagComparator);
			} else if (!markupObject.isReplacingElement && ((!relevantMarkupObjectsAtSelectionStart && relevantMarkupObjectsAtSelectionEnd) || relevantMarkupObjectAfterSelection || relevantMarkupObjectBeforeSelection)) { //
				// Alternative C: from no-markup to markup OR with next2markup:
				// new markup is wrapped from selection start to end of originalmarkup, original is remove afterwards
				Aloha.Log.info(this, 'non-markup 2 markup OR with next2markup');
				// move end of rangeObject to end of relevant markups
				if (relevantMarkupObjectBeforeSelection && relevantMarkupObjectAfterSelection) {
					extendedRangeObject = new Aloha.Selection.SelectionRange(rangeObject);
					extendedRangeObject.startContainer = jQuery(relevantMarkupObjectBeforeSelection[relevantMarkupObjectBeforeSelection.length - 1]).textNodes()[0];
					extendedRangeObject.startOffset = 0;
					extendedRangeObject.endContainer = jQuery(relevantMarkupObjectAfterSelection[relevantMarkupObjectAfterSelection.length - 1]).textNodes().last()[0];
					extendedRangeObject.endOffset = extendedRangeObject.endContainer.length;
					extendedRangeObject.update();
					this.applyMarkup(extendedRangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator);
					Aloha.Log.info(this, 'double extending previous markup(previous and after selection), actually wrapping it ...');

				} else if (relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection && !relevantMarkupObjectsAtSelectionEnd) {
					this.extendExistingMarkupWithSelection(relevantMarkupObjectBeforeSelection, rangeObject, false, tagComparator);
					Aloha.Log.info(this, 'extending previous markup');

				} else if (relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection && relevantMarkupObjectsAtSelectionEnd) {
					extendedRangeObject = new Aloha.Selection.SelectionRange(rangeObject);
					extendedRangeObject.startContainer = jQuery(relevantMarkupObjectBeforeSelection[relevantMarkupObjectBeforeSelection.length - 1]).textNodes()[0];
					extendedRangeObject.startOffset = 0;
					extendedRangeObject.endContainer = jQuery(relevantMarkupObjectsAtSelectionEnd[relevantMarkupObjectsAtSelectionEnd.length - 1]).textNodes().last()[0];
					extendedRangeObject.endOffset = extendedRangeObject.endContainer.length;
					extendedRangeObject.update();
					this.applyMarkup(extendedRangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator);
					Aloha.Log.info(this, 'double extending previous markup(previous and relevant at the end), actually wrapping it ...');

				} else if (!relevantMarkupObjectBeforeSelection && relevantMarkupObjectAfterSelection) {
					this.extendExistingMarkupWithSelection(relevantMarkupObjectAfterSelection, rangeObject, true, tagComparator);
					Aloha.Log.info(this, 'extending following markup backwards');

				} else {
					this.extendExistingMarkupWithSelection(relevantMarkupObjectsAtSelectionEnd, rangeObject, true, tagComparator);
				}
			} else if (markupObject.isReplacingElement || (!relevantMarkupObjectsAtSelectionStart && !relevantMarkupObjectsAtSelectionEnd && !relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection)) {
				// Alternative D: no-markup to no-markup: easy
				Aloha.Log.info(this, 'non-markup 2 non-markup');

				// workaround to keep the caret at the right position if it's an empty element
				// applyMarkup was not working correctly and has a lot of overhead we don't need in that case
				if (isCollapsedAndEmptyOrEndBr(rangeObject)) {
					var newMarkup = markupObject.clone();

					if (isCollapsedAndEndBr(rangeObject)) {
						newMarkup[0].appendChild(Engine.createEndBreak());
					}

					// setting the focus is needed for mozilla and IE 7 to have a working rangeObject.select()
					if (Aloha.activeEditable && Aloha.browser.mozilla && document.activeElement !== Aloha.activeEditable.obj[0]) {
						Aloha.activeEditable.obj.focus();
					}

					if (Engine.isEditable(rangeObject.startContainer)) {
						Engine.copyAttributes(rangeObject.startContainer, newMarkup[0]);
						jQuery(rangeObject.startContainer).after(newMarkup[0]).remove();
						Engine.ensureContainerEditable(newMarkup[0]);
					} else if (Engine.isEditingHost(rangeObject.startContainer)) {
						jQuery(rangeObject.startContainer).append(newMarkup[0]);
						Engine.ensureContainerEditable(newMarkup[0]);
					}

					backupRangeObject.startContainer = newMarkup[0];
					backupRangeObject.endContainer = newMarkup[0];
					backupRangeObject.startOffset = 0;
					backupRangeObject.endOffset = 0;
					return;
				}
				this.applyMarkup(rangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator, {
					setRangeObject2NewMarkup: true
				});
				backupRangeObject.startContainer = rangeObject.startContainer;
				backupRangeObject.endContainer = rangeObject.endContainer;
				backupRangeObject.startOffset = rangeObject.startOffset;
				backupRangeObject.endOffset = rangeObject.endOffset;
			}

			if (markupObject.isReplacingElement) {
				//Check if the startContainer is one of the zapped elements
				if (backupRangeObject && backupRangeObject.startContainer.className && backupRangeObject.startContainer.className.indexOf('preparedForRemoval') > -1) {
					//var parentElement = jQuery(backupRangeObject.startContainer).closest(markupObject[0].tagName).get(0);
					parentElement = jQuery(backupRangeObject.startContainer).parents(markupObject[0].tagName).get(0);
					backupRangeObject.startContainer = parentElement;
					rangeObject.startContainer = parentElement;
				}
				//check if the endContainer is one of the zapped elements
				if (backupRangeObject && backupRangeObject.endContainer.className && backupRangeObject.endContainer.className.indexOf('preparedForRemoval') > -1) {
					//var parentElement = jQuery(backupRangeObject.endContainer).closest(markupObject[0].tagName).get(0);
					parentElement = jQuery(backupRangeObject.endContainer).parents(markupObject[0].tagName).get(0);
					backupRangeObject.endContainer = parentElement;
					rangeObject.endContainer = parentElement;
				}
			}
			// remove all marked items
			jQuery('.preparedForRemoval').zap();

			// recalculate cac and selectionTree

			// update selection
			if (markupObject.isReplacingElement) {
				//After the zapping we have to check for wrong offsets
				if (e5s.Node.ELEMENT_NODE === backupRangeObject.startContainer.nodeType && backupRangeObject.startContainer.childNodes && backupRangeObject.startContainer.childNodes.length < backupRangeObject.startOffset) {
					backupRangeObject.startOffset = backupRangeObject.startContainer.childNodes.length;
					rangeObject.startOffset = backupRangeObject.startContainer.childNodes.length;
				}
				if (e5s.Node.ELEMENT_NODE === backupRangeObject.endContainer.nodeType && backupRangeObject.endContainer.childNodes && backupRangeObject.endContainer.childNodes.length < backupRangeObject.endOffset) {
					backupRangeObject.endOffset = backupRangeObject.endContainer.childNodes.length;
					rangeObject.endOffset = backupRangeObject.endContainer.childNodes.length;
				}
				rangeObject.endContainer = backupRangeObject.endContainer;
				rangeObject.endOffset = backupRangeObject.endOffset;
				rangeObject.startContainer = backupRangeObject.startContainer;
				rangeObject.startOffset = backupRangeObject.startOffset;
				backupRangeObject.update();
				backupRangeObject.select();
			} else {
				rangeObject.update();
				rangeObject.select();
			}
		},

		/**
		 * method compares a JS array of domobjects with a range object and decides, if the rangeObject spans the whole markup objects. method is used to decide if a markup2markup selection can be completely remove or if it must be splitted into 2 separate markups
		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects, which are parents to the rangeObject.startContainer
		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects, which are parents to the rangeObject.endContainer
		 * @param rangeObj Aloha rangeObject
		 * @return true, if rangeObjects and markup objects are identical, false otherwise
		 * @hide
		 */
		areMarkupObjectsAsLongAsRangeObject: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject) {
			var i, el, textNode, relMarkupEnd, relMarkupStart;

			if (rangeObject.startOffset !== 0) {
				return false;
			}

			for (i = 0, relMarkupStart = relevantMarkupObjectsAtSelectionStart.length; i < relMarkupStart; i++) {
				el = jQuery(relevantMarkupObjectsAtSelectionStart[i]);
				if (el.textNodes().first()[0] !== rangeObject.startContainer) {
					return false;
				}
			}

			for (i = 0, relMarkupEnd = relevantMarkupObjectsAtSelectionEnd.length; i < relMarkupEnd; i++) {
				el = jQuery(relevantMarkupObjectsAtSelectionEnd[i]);
				textNode = el.textNodes().last()[0];
				if (textNode !== rangeObject.endContainer || textNode.length != rangeObject.endOffset) {
					return false;
				}
			}

			return true;
		},

		/**
		 * method used to remove/split markup from a "markup2markup" selection
		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects, which are parents to the rangeObject.startContainer
		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects, which are parents to the rangeObject.endContainer
		 * @param rangeObj Aloha rangeObject
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return true (always, since no "false" case is currently known...but might be added)
		 * @hide
		 */
		splitRelevantMarkupObject: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject, tagComparator) {
			// mark them to be deleted
			jQuery(relevantMarkupObjectsAtSelectionStart).addClass('preparedForRemoval');
			jQuery(relevantMarkupObjectsAtSelectionEnd).addClass('preparedForRemoval');

			// check if the rangeObject is identical with the relevantMarkupObjects (in this case the markup can simply be removed)
			if (this.areMarkupObjectsAsLongAsRangeObject(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject)) {
				return true;
			}

			// find intersection (this can always only be one dom element (namely the highest) because all others will be removed
			var relevantMarkupObjectAtSelectionStartAndEnd = this.intersectRelevantMarkupObjects(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd);

			if (relevantMarkupObjectAtSelectionStartAndEnd) {
				this.insertCroppedMarkups([relevantMarkupObjectAtSelectionStartAndEnd], rangeObject, false, tagComparator);
				this.insertCroppedMarkups([relevantMarkupObjectAtSelectionStartAndEnd], rangeObject, true, tagComparator);
			} else {
				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionStart, rangeObject, false, tagComparator);
				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionEnd, rangeObject, true, tagComparator);
			}
			return true;
		},

		/**
		 * method takes two arrays of bottom up dom objects, compares them and returns either the object closest to the root or false
		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects
		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects
		 * @return dom object closest to the root or false
		 * @hide
		 */
		intersectRelevantMarkupObjects: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd) {
			var intersection = false, i, elStart, j, elEnd, relMarkupStart, relMarkupEnd;
			if (!relevantMarkupObjectsAtSelectionStart || !relevantMarkupObjectsAtSelectionEnd) {
				return intersection; // we can only intersect, if we have to arrays!
			}
			relMarkupStart = relevantMarkupObjectsAtSelectionStart.length;
			relMarkupEnd = relevantMarkupObjectsAtSelectionEnd.length;
			for (i = 0; i < relMarkupStart; i++) {
				elStart = relevantMarkupObjectsAtSelectionStart[i];
				for (j = 0; j < relMarkupEnd; j++) {
					elEnd = relevantMarkupObjectsAtSelectionEnd[j];
					if (elStart === elEnd) {
						intersection = elStart;
					}
				}
			}
			return intersection;
		},

		/**
		 * method used to add markup to a nonmarkup2markup selection
		 * @param relevantMarkupObjects JS Array of dom objects effecting either the start or endContainer of a selection (which should be extended)
		 * @param rangeObject Aloha rangeObject the markups should be extended to
		 * @param startOrEnd boolean; defines, if the existing markups should be extended forwards or backwards (is propably redundant and could be found out by comparing start or end container with the markup array dom objects)
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return true
		 * @hide
		 */
		extendExistingMarkupWithSelection: function (relevantMarkupObjects, rangeObject, startOrEnd, tagComparator) {
			var extendMarkupsAtStart, extendMarkupsAtEnd, objects, i, relMarkupLength, el, textnodes, nodeNr;
			if (!startOrEnd) { // = Start
				// start part of rangeObject should be used, therefor existing markups are cropped at the end
				extendMarkupsAtStart = true;
			}
			if (startOrEnd) { // = End
				// end part of rangeObject should be used, therefor existing markups are cropped at start (beginning)
				extendMarkupsAtEnd = true;
			}
			objects = [];
			for (i = 0, relMarkupLength = relevantMarkupObjects.length; i < relMarkupLength; i++) {
				objects[i] = new this.SelectionRange();
				el = relevantMarkupObjects[i];
				if (extendMarkupsAtEnd && !extendMarkupsAtStart) {
					objects[i].startContainer = rangeObject.startContainer; // jQuery(el).contents()[0];
					objects[i].startOffset = rangeObject.startOffset;
					textnodes = jQuery(el).textNodes(true);

					nodeNr = textnodes.length - 1;
					objects[i].endContainer = textnodes[nodeNr];
					objects[i].endOffset = textnodes[nodeNr].length;
					objects[i].update();
					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
						setRangeObject2NewMarkup: true
					});
				}
				if (!extendMarkupsAtEnd && extendMarkupsAtStart) {
					textnodes = jQuery(el).textNodes(true);
					objects[i].startContainer = textnodes[0]; // jQuery(el).contents()[0];
					objects[i].startOffset = 0;
					objects[i].endContainer = rangeObject.endContainer;
					objects[i].endOffset = rangeObject.endOffset;
					objects[i].update();
					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
						setRangeObject2NewMarkup: true
					});
				}
			}
			return true;
		},

		/**
		 * method creates an empty markup jQuery object from a dom object passed as paramter
		 * @param domobj domobject to be cloned, cleaned and emptied
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return jQuery wrapper object to be passed to e.g. this.applyMarkup(...)
		 * @hide
		 */
		getClonedMarkup4Wrapping: function (domobj) {
			var wrapper = jQuery(domobj.outerHTML).removeClass('preparedForRemoval').empty();
			if (wrapper.attr('class').length === 0) {
				wrapper.removeAttr('class');
			}
			return wrapper;
		},

		/**
		 * method used to subtract the range object from existing markup. in other words: certain markup is removed from the selections defined by the rangeObject
		 * @param relevantMarkupObjects JS Array of dom objects effecting either the start or endContainer of a selection (which should be extended)
		 * @param rangeObject Aloha rangeObject the markups should be removed from
		 * @param startOrEnd boolean; defines, if the existing markups should be reduced at the beginning of the tag or at the end (is propably redundant and could be found out by comparing start or end container with the markup array dom objects)
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return true
		 * @hide
		 */
		insertCroppedMarkups: function (relevantMarkupObjects, rangeObject, startOrEnd, tagComparator) {
			var cropMarkupsAtEnd, cropMarkupsAtStart, textnodes, objects, i, el, textNodes;
			if (!startOrEnd) { // = Start
				// start part of rangeObject should be used, therefor existing markups are cropped at the end
				cropMarkupsAtEnd = true;
			} else { // = End
				// end part of rangeObject should be used, therefor existing markups are cropped at start (beginning)
				cropMarkupsAtStart = true;
			}
			objects = [];
			for (i = 0; i < relevantMarkupObjects.length; i++) {
				objects[i] = new this.SelectionRange();
				el = relevantMarkupObjects[i];
				if (cropMarkupsAtEnd && !cropMarkupsAtStart) {
					textNodes = jQuery(el).textNodes(true);
					objects[i].startContainer = textNodes[0];
					objects[i].startOffset = 0;
					// if the existing markup startContainer & startOffset are equal to the rangeObject startContainer and startOffset,
					// then markupobject does not have to be added again, because it would have no content (zero-length)
					if (objects[i].startContainer === rangeObject.startContainer && objects[i].startOffset === rangeObject.startOffset) {
						continue;
					}
					if (rangeObject.startOffset === 0) {
						objects[i].endContainer = this.getTextNodeSibling(false, el, rangeObject.startContainer);
						objects[i].endOffset = objects[i].endContainer.length;
					} else {
						objects[i].endContainer = rangeObject.startContainer;
						objects[i].endOffset = rangeObject.startOffset;
					}

					objects[i].update();

					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
						setRangeObject2NextSibling: true
					});
				}

				if (!cropMarkupsAtEnd && cropMarkupsAtStart) {
					objects[i].startContainer = rangeObject.endContainer; // jQuery(el).contents()[0];
					objects[i].startOffset = rangeObject.endOffset;
					textnodes = jQuery(el).textNodes(true);
					objects[i].endContainer = textnodes[textnodes.length - 1];
					objects[i].endOffset = textnodes[textnodes.length - 1].length;
					objects[i].update();
					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
						setRangeObject2PreviousSibling: true
					});
				}
			}
			return true;
		},

		/**
		 * Checks for Firefox incorrect range. When selecting a paragraph with the
		 * command 'shift+keydown', the selection ends in the start of the next paragraph
		 * instead of at the end of the selected paragraph. This produces an unexpected
		 * behaviour when formatting the selected text to a heading or a list, because the
		 * result included one extra paragraph.
		 */
		checkForFirefoxIncorrectRange: function () {
			if (Browser.mozilla && Aloha.getSelection().getRangeCount() !== 0) {
				correctFirefoxRangeIssue(this.getRangeObject());
			}
		},

		/**
		 * apply a certain markup to the current selection
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @return void
		 * @hide
		 */
		changeMarkupOnSelection: function (markupObject) {
			this.checkForFirefoxIncorrectRange();
			var rangeObject = this.getRangeObject();

			// change the markup
			this.changeMarkup(rangeObject, markupObject, this.getStandardTagComparator(markupObject));

			// merge text nodes
			GENTICS.Utils.Dom.doCleanup({
				'merge': true
			}, rangeObject);

			// update the range and select it
			rangeObject.update();
			rangeObject.select();
			this.rangeObject = rangeObject;
		},

		/**
		 * apply a certain markup to the selection Tree
		 * @param selectionTree SelectionTree Object markup should be applied to
		 * @param rangeObject Aloha rangeObject which will be modified to reflect the dom changes, after the markup was applied (only if activated via options)
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @param options JS object, with the following boolean properties: setRangeObject2NewMarkup, setRangeObject2NextSibling, setRangeObject2PreviousSibling
		 * @return void
		 * @hide
		 */
		applyMarkup: function (selectionTree, rangeObject, markupObject, tagComparator, options) {
			var optimizedSelectionTree, i, el, breakpoint;
			options = options || {};
			// first same tags from within fully selected nodes for removal
			this.prepareForRemoval(selectionTree, markupObject, tagComparator);

			// first let's optimize the selection Tree in useful groups which can be wrapped together
			optimizedSelectionTree = this.optimizeSelectionTree4Markup(selectionTree, markupObject, tagComparator);
			breakpoint = true;

			// now iterate over grouped elements and either recursively dive into object or wrap it as a whole
			for (i = 0; i < optimizedSelectionTree.length; i++) {
				el = optimizedSelectionTree[i];
				if (el.wrappable) {
					this.wrapMarkupAroundSelectionTree(el.elements, rangeObject, markupObject, tagComparator, options);
				} else {
					Aloha.Log.debug(this, 'dive further into non-wrappable object');
					this.applyMarkup(el.element.children, rangeObject, markupObject, tagComparator, options);
				}
			}
		},

		/**
		 * returns the type of the given markup (trying to match HTML5)
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @return string name of the markup type
		 * @hide
		 */
		getMarkupType: function (markupObject) {
			var nn = jQuery(markupObject)[0].nodeName.toLowerCase();
			if (markupObject.outerHtml) {
				Aloha.Log.debug(this, 'Node name detected: ' + nn + ' for: ' + markupObject.outerHtml());
			}
			if (nn == '#text') {
				return 'textNode';
			}
			if (this.replacingElements[nn]) {
				return 'sectionOrGroupingContent';
			}
			if (this.tagHierarchy[nn]) {
				return 'textLevelSemantics';
			}
			Aloha.Log.warn(this, 'unknown markup passed to this.getMarkupType(...): ' + markupObject.outerHtml());
		},

		/**
		 * returns the standard tag comparator for the given markup object
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @return function tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @hide
		 */
		getStandardTagComparator: function (markupObject) {
			var that = this,
				result;
			switch (this.getMarkupType(markupObject)) {
			case 'textNode':
				result = function (p1, p2) {
					return false;
				};
				break;

			case 'sectionOrGroupingContent':
				result = function (domobj, markupObject) {
					return that.standardSectionsAndGroupingContentComparator(domobj, markupObject);
				};
				break;

			//case 'textLevelSemantics' covered by default
			default:
				result = function (domobj, markupObject) {
					return that.standardTextLevelSemanticsComparator(domobj, markupObject);
				};
				break;
			}
			return result;
		},

		/**
		 * searches for fully selected equal markup tags
		 * @param selectionTree SelectionTree Object markup should be applied to
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return void
		 * @hide
		 */
		prepareForRemoval: function (selectionTree, markupObject, tagComparator) {
			var that = this, i, el;

			// check if a comparison method was passed as parameter ...
			if (typeof tagComparator !== 'undefined' && typeof tagComparator !== 'function') {
				Aloha.Log.error(this, 'parameter tagComparator is not a function');
			}
			// ... if not use this as standard tag comparison method
			if (typeof tagComparator === 'undefined') {
				tagComparator = this.getStandardTagComparator(markupObject);
			}
			for (i = 0; i < selectionTree.length; i++) {
				el = selectionTree[i];
				if (el.domobj && (el.selection == 'full' || (el.selection == 'partial' && markupObject.isReplacingElement))) {
					// mark for removal
					if (el.domobj.nodeType === 1 && tagComparator(el.domobj, markupObject)) {
						Aloha.Log.debug(this, 'Marking for removal: ' + el.domobj.nodeName);
						jQuery(el.domobj).addClass('preparedForRemoval');
					}
				}
				if (el.selection != 'none' && el.children.length > 0) {
					this.prepareForRemoval(el.children, markupObject, tagComparator);
				}

			}
		},

		/**
		 * searches for fully selected equal markup tags
		 * @param selectionTree SelectionTree Object markup should be applied to
		 * @param rangeObject Aloha rangeObject the markup will be applied to
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @param options JS object, with the following boolean properties: setRangeObject2NewMarkup, setRangeObject2NextSibling, setRangeObject2PreviousSibling
		 * @return void
		 * @hide
		 */
		wrapMarkupAroundSelectionTree: function (selectionTree, rangeObject, markupObject, tagComparator, options) {
			// first let's find out if theoretically the whole selection can be wrapped with one tag and save it for later use
			var objects2wrap = [], // // this will be used later to collect objects
				j = -1, // internal counter,
				breakpoint = true,
				preText = '',
				postText = '',
				prevOrNext,
				textNode2Start,
				textnodes,
				newMarkup,
				i,
			    el,
			    middleText;

			Aloha.Log.debug(this, 'The formatting <' + markupObject[0].tagName + '> will be wrapped around the selection');

			// now lets iterate over the elements
			for (i = 0; i < selectionTree.length; i++) {
				el = selectionTree[i];

				// check if markup is allowed inside the elements parent
				if (el.domobj && !this.canTag1WrapTag2(el.domobj.parentNode.tagName.toLowerCase(), markupObject[0].tagName.toLowerCase())) {
					Aloha.Log.info(this, 'Skipping the wrapping of <' + markupObject[0].tagName.toLowerCase() + '> because this tag is not allowed inside <' + el.domobj.parentNode.tagName.toLowerCase() + '>');
					continue;
				}

				// skip empty text nodes
				if (el.domobj && el.domobj.nodeType === 3 && jQuery.trim(el.domobj.nodeValue).length === 0) {
					continue;
				}

				// partial element, can either be a textnode and therefore be wrapped (at least partially)
				// or can be a nodeType == 1 (tag) which must be dived into
				if (el.domobj && el.selection == 'partial' && !markupObject.isReplacingElement) {
					if (el.startOffset !== undefined && el.endOffset === undefined) {
						j++;
						preText += el.domobj.data.substr(0, el.startOffset);
						el.domobj.data = el.domobj.data.substr(el.startOffset, el.domobj.data.length - el.startOffset);
						objects2wrap[j] = el.domobj;
					} else if (el.endOffset !== undefined && el.startOffset === undefined) {
						j++;
						postText += el.domobj.data.substr(el.endOffset, el.domobj.data.length - el.endOffset);
						el.domobj.data = el.domobj.data.substr(0, el.endOffset);
						objects2wrap[j] = el.domobj;
					} else if (el.endOffset !== undefined && el.startOffset !== undefined) {
						if (el.startOffset == el.endOffset) { // do not wrap empty selections
							Aloha.Log.debug(this, 'skipping empty selection');
							continue;
						}
						j++;
						preText += el.domobj.data.substr(0, el.startOffset);
						middleText = el.domobj.data.substr(el.startOffset, el.endOffset - el.startOffset);
						postText += el.domobj.data.substr(el.endOffset, el.domobj.data.length - el.endOffset);
						el.domobj.data = middleText;
						objects2wrap[j] = el.domobj;
					} else {
						// a partially selected item without selectionStart/EndOffset is a nodeType 1 Element on the way to the textnode
						Aloha.Log.debug(this, 'diving into object');
						this.applyMarkup(el.children, rangeObject, markupObject, tagComparator, options);
					}
				}
				// fully selected dom elements can be wrapped as whole element
				if (el.domobj && (el.selection == 'full' || (el.selection == 'partial' && markupObject.isReplacingElement))) {
					j++;
					objects2wrap[j] = el.domobj;
				}
			}

			if (objects2wrap.length > 0) {
				// wrap collected DOM object with markupObject
				objects2wrap = jQuery(objects2wrap);

				// make a fix for text nodes in <li>'s in ie
				jQuery.each(objects2wrap, function (index, element) {
					if (Aloha.browser.msie && element.nodeType == 3 && !element.nextSibling && !element.previousSibling && element.parentNode && element.parentNode.nodeName.toLowerCase() == 'li') {
						element.data = jQuery.trim(element.data);
					}
				});

				newMarkup = objects2wrap.wrapAll(markupObject).parent();
				newMarkup.before(preText).after(postText);

				if (options.setRangeObject2NewMarkup) { // this is used, when markup is added to normal/normal Text
					textnodes = objects2wrap.textNodes();

					if (textnodes.index(rangeObject.startContainer) != -1) {
						rangeObject.startOffset = 0;
					}
					if (textnodes.index(rangeObject.endContainer) != -1) {
						rangeObject.endOffset = rangeObject.endContainer.length;
					}
					breakpoint = true;
				}
				if (options.setRangeObject2NextSibling) {
					prevOrNext = true;
					textNode2Start = newMarkup.textNodes(true).last()[0];
					if (objects2wrap.index(rangeObject.startContainer) != -1) {
						rangeObject.startContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
						rangeObject.startOffset = 0;
					}
					if (objects2wrap.index(rangeObject.endContainer) != -1) {
						rangeObject.endContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
						rangeObject.endOffset = rangeObject.endOffset - textNode2Start.length;
					}
				}
				if (options.setRangeObject2PreviousSibling) {
					prevOrNext = false;
					textNode2Start = newMarkup.textNodes(true).first()[0];
					if (objects2wrap.index(rangeObject.startContainer) != -1) {
						rangeObject.startContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
						rangeObject.startOffset = 0;
					}
					if (objects2wrap.index(rangeObject.endContainer) != -1) {
						rangeObject.endContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
						rangeObject.endOffset = rangeObject.endContainer.length;
					}
				}
			}
		},

		/**
		 * takes a text node and return either the next recursive text node sibling or the previous
		 * @param previousOrNext boolean, false for previous, true for next sibling
		 * @param commonAncestorContainer dom object to be used as root for the sibling search
		 * @param currentTextNode dom object of the originating text node
		 * @return dom object of the sibling text node
		 * @hide
		 */
		getTextNodeSibling: function (previousOrNext, commonAncestorContainer, currentTextNode) {
			var textNodes = jQuery(commonAncestorContainer).textNodes(true), newIndex, index;

			index = textNodes.index(currentTextNode);
			if (index == -1) { // currentTextNode was not found
				return false;
			}
			newIndex = index + (!previousOrNext ? -1 : 1);
			return textNodes[newIndex] || false;
		},

		/**
		 * takes a selection tree and groups it into markup wrappable selection trees
		 * @param selectionTree rangeObject selection tree
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @return JS array of wrappable selection trees
		 * @hide
		 */
		optimizeSelectionTree4Markup: function (selectionTree, markupObject, tagComparator) {
			var groupMap = [],
				outerGroupIndex = 0,
				innerGroupIndex = 0,
				that = this,
				i,
			    j,
				endPosition,
			    startPosition;

			if (typeof tagComparator === 'undefined') {
				tagComparator = function (domobj, markupObject) {
					return that.standardTextLevelSemanticsComparator(markupObject);
				};
			}
			for (i = 0; i < selectionTree.length; i++) {
				// we are just interested in selected item, but not in non-selected items
				if (selectionTree[i].domobj && selectionTree[i].selection != 'none') {
					if (markupObject.isReplacingElement && tagComparator(markupObject[0], jQuery(selectionTree[i].domobj))) {
						if (groupMap[outerGroupIndex] !== undefined) {
							outerGroupIndex++;
						}
						groupMap[outerGroupIndex] = {};
						groupMap[outerGroupIndex].wrappable = true;
						groupMap[outerGroupIndex].elements = [];
						groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[i];
						outerGroupIndex++;

					} else if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[i]], markupObject)) {
						// now check, if the children of our item could be wrapped all together by the markup object
						// if yes, add it to the current group
						if (groupMap[outerGroupIndex] === undefined) {
							groupMap[outerGroupIndex] = {};
							groupMap[outerGroupIndex].wrappable = true;
							groupMap[outerGroupIndex].elements = [];
						}
						if (markupObject.isReplacingElement) { //  && selectionTree[i].domobj.nodeType === 3
							/* we found the node to wrap for a replacing element. however there might
							 * be siblings which should be included as well
							 * although they are actually not selected. example:
							 * li
							 * |-textNode ( .selection = 'none')
							 * |-textNode (cursor inside, therefor .selection = 'partial')
							 * |-textNode ( .selection = 'none')
							 *
							 * in this case it would be useful to select the previous and following textNodes as well (they might result from a previous DOM manipulation)
							 * Think about other cases, where the parent is the Editable. In this case we propably only want to select from and until the next <br /> ??
							 * .... many possibilities, here I realize the two described cases
							 */

							// first find start element starting from the current element going backwards until sibling 0
							startPosition = i;
							for (j = i - 1; j >= 0; j--) {
								if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[j]], markupObject) && this.isMarkupAllowedToStealSelectionTreeElement(selectionTree[j], markupObject)) {
									startPosition = j;
								} else {
									break;
								}
							}

							// now find the end element starting from the current element going forward until the last sibling
							endPosition = i;
							for (j = i + 1; j < selectionTree.length; j++) {
								if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[j]], markupObject) && this.isMarkupAllowedToStealSelectionTreeElement(selectionTree[j], markupObject)) {
									endPosition = j;
								} else {
									break;
								}
							}

							// now add the elements to the groupMap
							innerGroupIndex = 0;
							for (j = startPosition; j <= endPosition; j++) {
								groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[j];
								groupMap[outerGroupIndex].elements[innerGroupIndex].selection = 'full';
								innerGroupIndex++;
							}
							innerGroupIndex = 0;
						} else {
							// normal text level semantics object, no siblings need to be selected
							groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[i];
							innerGroupIndex++;
						}
					} else {
						// if no, isolate it in its own group
						if (groupMap[outerGroupIndex] !== undefined) {
							outerGroupIndex++;
						}
						groupMap[outerGroupIndex] = {};
						groupMap[outerGroupIndex].wrappable = false;
						groupMap[outerGroupIndex].element = selectionTree[i];
						innerGroupIndex = 0;
						outerGroupIndex++;
					}
				}
			}
			return groupMap;
		},

		/**
		 * very tricky method, which decides, if a certain markup (normally a replacing markup element like p, h1, blockquote)
		 * is allowed to extend the user selection to other dom objects (represented as selectionTreeElement)
		 * to understand the purpose: if the user selection is collapsed inside e.g. some text, which is currently not
		 * wrapped by the markup to be applied, and therefor the markup does not have an equal markup to replace, then the DOM
		 * manipulator has to decide which objects to wrap. real example:
		 * <div>
		 *	<h1>headline</h1>
		 *	some text blabla bla<br>
		 *	more text HERE THE | CURSOR BLINKING and <b>even more bold text</b>
		 * </div>
		 * when the user now wants to apply e.g. a <p> tag, what will be wrapped? it could be useful if the manipulator would actually
		 * wrap everything inside the div except the <h1>. but for this purpose someone has to decide, if the markup is
		 * allowed to wrap certain dom elements in this case the question would be, if the <p> is allowed to wrap
		 * textNodes, <br> and <b> and <h1>. therefore this tricky method should answer the question for those 3 elements
		 * with true, but for for the <h1> it should return false. and since the method does not know this, there is a configuration
		 * for this
		 *
		 * @param selectionTree rangeObject selection tree element (only one, not an array of)
		 * @param markupObject lowercase string of the tag to be verified (e.g. "b")
		 * @return true if the markup is allowed to wrap the selection tree element, false otherwise
		 * @hide
		 */
		isMarkupAllowedToStealSelectionTreeElement: function (selectionTreeElement, markupObject) {
			if (!selectionTreeElement.domobj) {
				return false;
			}
			var maybeTextNodeName = selectionTreeElement.domobj.nodeName.toLowerCase(),
				nodeName = (maybeTextNodeName == '#text') ? 'textNode' : maybeTextNodeName,
				markupName = markupObject[0].nodeName.toLowerCase(),
				elemMap = this.allowedToStealElements[markupName];
			return elemMap && elemMap[nodeName];
		},

		/**
		 * checks if a selection can be completey wrapped by a certain html tags (helper method for this.optimizeSelectionTree4Markup
		 * @param selectionTree rangeObject selection tree
		 * @param markupObject lowercase string of the tag to be verified (e.g. "b")
		 * @return true if selection can be applied as whole, false otherwise
		 * @hide
		 */
		canMarkupBeApplied2ElementAsWhole: function (selectionTree, markupObject) {
			var htmlTag, i, el, returnVal;

			if (markupObject.jquery) {
				htmlTag = markupObject[0].tagName;
			}
			if (markupObject.tagName) {
				htmlTag = markupObject.tagName;
			}

			returnVal = true;
			for (i = 0; i < selectionTree.length; i++) {
				el = selectionTree[i];
				if (el.domobj && (el.selection != "none" || markupObject.isReplacingElement)) {
					// Aloha.Log.debug(this, 'Checking, if  <' + htmlTag + '> can be applied to ' + el.domobj.nodeName);
					if (!this.canTag1WrapTag2(htmlTag, el.domobj.nodeName)) {
						return false;
					}
					if (el.children.length > 0 && !this.canMarkupBeApplied2ElementAsWhole(el.children, markupObject)) {
						return false;
					}
				}
			}
			return returnVal;
		},

		/**
		 * checks if a tag 1 (first parameter) can wrap tag 2 (second parameter).
		 * IMPORTANT: the method does not verify, if there have to be other tags in between
		 * Example: this.canTag1WrapTag2("table", "td") will return true, because the method does not take into account, that there has to be a "tr" in between
		 * @param t1 string: tagname of outer tag to verify, e.g. "b"
		 * @param t2 string: tagname of inner tag to verify, e.g. "b"
		 * @return true if tag 1 can wrap tag 2, false otherwise
		 * @hide
		 */
		canTag1WrapTag2: function (t1, t2) {
			t1 = (t1 == '#text') ? 'textNode' : t1.toLowerCase();
			t2 = (t2 == '#text') ? 'textNode' : t2.toLowerCase();
			var t1Map = this.tagHierarchy[t1];
			if (!t1Map) {
				return true;
			}
			if (!this.tagHierarchy[t2]) {
				return true;
			}
			return t1Map[t2];
		},

		/**
		 * Check whether it is allowed to insert the given tag at the start of the
		 * current selection. This method will check whether the markup effective for
		 * the start and outside of the editable part (starting with the editable tag
		 * itself) may wrap the given tag.
		 * @param tagName {String} name of the tag which shall be inserted
		 * @return true when it is allowed to insert that tag, false if not
		 * @hide
		 */
		mayInsertTag: function (tagName) {
			var i;
			if (typeof this.rangeObject.unmodifiableMarkupAtStart == 'object') {
				// iterate over all DOM elements outside of the editable part
				for (i = 0; i < this.rangeObject.unmodifiableMarkupAtStart.length; ++i) {
					// check whether an element may not wrap the given
					if (!this.canTag1WrapTag2(this.rangeObject.unmodifiableMarkupAtStart[i].nodeName, tagName)) {
						// found a DOM element which forbids to insert the given tag, we are done
						return false;
					}
				}

				// all of the found DOM elements allow inserting the given tag
				return true;
			}
			Aloha.Log.warn(this, 'Unable to determine whether tag ' + tagName + ' may be inserted');
			return true;
		},

		/**
		 * Sets prevStartContext & prevEndContext to null
		 */
		resetPrevSelectionContexts: function () {
			prevStartContext = null;
			prevEndContext   = null;
		},

		/**
		 * String representation
		 * @return "Aloha.Selection"
		 * @hide
		 */
		toString: function () {
			return 'Aloha.Selection';
		},

		/**
		 * @namespace Aloha.Selection
		 * @class SelectionRange
		 * @extends GENTICS.Utils.RangeObject
		 * Constructor for a range object.
		 * Optionally you can pass in a range object that's properties will be assigned to the new range object.
		 * @param rangeObject A range object thats properties will be assigned to the new range object.
		 * @constructor
		 */
		SelectionRange: GENTICS.Utils.RangeObject.extend({
			_constructor: function (rangeObject) {
				this._super(rangeObject);
				// If a range object was passed in we apply the values to the new range object
				if (rangeObject) {
					if (rangeObject.commonAncestorContainer) {
						this.commonAncestorContainer = rangeObject.commonAncestorContainer;
					}
					if (rangeObject.selectionTree) {
						this.selectionTree = rangeObject.selectionTree;
					}
					if (rangeObject.limitObject) {
						this.limitObject = rangeObject.limitObject;
					}
					if (rangeObject.markupEffectiveAtStart) {
						this.markupEffectiveAtStart = rangeObject.markupEffectiveAtStart;
					}
					if (rangeObject.unmodifiableMarkupAtStart) {
						this.unmodifiableMarkupAtStart = rangeObject.unmodifiableMarkupAtStart;
					}
					if (rangeObject.splitObject) {
						this.splitObject = rangeObject.splitObject;
					}
				}
			},

			/**
			 * DOM object of the common ancestor from startContainer and endContainer
			 * @hide
			 */
			commonAncestorContainer: undefined,

			/**
			 * The selection tree
			 * @hide
			 */
			selectionTree: undefined,

			/**
			 * Array of DOM objects effective for the start container and inside the
			 * editable part (inside the limit object). relevant for the button status
			 * @hide
			 */
			markupEffectiveAtStart: [],

			/**
			 * Array of DOM objects effective for the start container, which lies
			 * outside of the editable portion (starting with the limit object)
			 * @hide
			 */
			unmodifiableMarkupAtStart: [],

			/**
			 * DOM object being the limit for all markup relevant activities
			 * @hide
			 */
			limitObject: undefined,

			/**
			 * DOM object being split when enter key gets hit
			 * @hide
			 */
			splitObject: undefined,

			/**
			 * Sets the visible selection in the Browser based on the range object.
			 * If the selection is collapsed, this will result in a blinking cursor,
			 * otherwise in a text selection.
			 * @method
			 */
			select: function () {
				// Call Utils' select()
				this._super();

				// update the selection
				Aloha.Selection.updateSelection();
			},

			/**
			 * Method to update a range object internally
			 * @param commonAncestorContainer (DOM Object); optional Parameter; if set, the parameter
			 * will be used instead of the automatically calculated CAC
			 * @return void
			 * @hide
			 */
			update: function (commonAncestorContainer) {
				this.updatelimitObject();
				this.updateMarkupEffectiveAtStart();
				this.updateCommonAncestorContainer(commonAncestorContainer);

				// reset the selectiontree (must be recalculated)
				this.selectionTree = undefined;
			},

			/**
			 * Get the selection tree for this range
			 * TODO: remove this (was moved to range.js)
			 * @return selection tree
			 * @hide
			 */
			getSelectionTree: function () {
				// if not yet calculated, do this now
				if (!this.selectionTree) {
					this.selectionTree = Aloha.Selection.getSelectionTree(this);
				}

				return this.selectionTree;
			},

			/**
			 * TODO: move this to range.js
			 * Get an array of domobj (in dom tree order) of siblings of the given domobj, which are contained in the selection
			 * @param domobj dom object to start with
			 * @return array of siblings of the given domobj, which are also selected
			 * @hide
			 */
			getSelectedSiblings: function (domobj) {
				var selectionTree = this.getSelectionTree();

				return this.recursionGetSelectedSiblings(domobj, selectionTree);
			},

			/**
			 * TODO: move this to range.js
			 * Recursive method to find the selected siblings of the given domobj (which should be selected as well)
			 * @param domobj dom object for which the selected siblings shall be found
			 * @param selectionTree current level of the selection tree
			 * @return array of selected siblings of dom objects or false if none found
			 * @hide
			 */
			recursionGetSelectedSiblings: function (domobj, selectionTree) {
				var selectedSiblings = false,
					foundObj = false,
					i;

				for (i = 0; i < selectionTree.length; ++i) {
					if (selectionTree[i].domobj === domobj) {
						foundObj = true;
						selectedSiblings = [];
					} else if (!foundObj && selectionTree[i].children) {
						// do the recursion
						selectedSiblings = this.recursionGetSelectedSiblings(domobj, selectionTree[i].children);
						if (selectedSiblings !== false) {
							break;
						}
					} else if (foundObj && selectionTree[i].domobj && selectionTree[i].selection != 'collapsed' && selectionTree[i].selection != 'none') {
						selectedSiblings.push(selectionTree[i].domobj);
					} else if (foundObj && selectionTree[i].selection == 'none') {
						break;
					}
				}

				return selectedSiblings;
			},

			/**
			 * TODO: move this to range.js
			 * Method updates member var markupEffectiveAtStart and splitObject, which is relevant primarily for button status and enter key behaviour
			 * @return void
			 * @hide
			 */
			updateMarkupEffectiveAtStart: function () {
				// reset the current markup
				this.markupEffectiveAtStart = [];
				this.unmodifiableMarkupAtStart = [];

				var parents = this.getStartContainerParents(),
					limitFound = false,
					splitObjectWasSet,
					i,
				    el;

				for (i = 0; i < parents.length; i++) {
					el = parents[i];
					if (!limitFound && (el !== this.limitObject)) {
						this.markupEffectiveAtStart[i] = el;
						if (!splitObjectWasSet && GENTICS.Utils.Dom.isSplitObject(el)) {
							splitObjectWasSet = true;
							this.splitObject = el;
						}
					} else {
						limitFound = true;
						this.unmodifiableMarkupAtStart.push(el);
					}
				}
				if (!splitObjectWasSet) {
					this.splitObject = false;
				}
				return;
			},

			/**
			 * TODO: remove this
			 * Method updates member var markupEffectiveAtStart, which is relevant primarily for button status
			 * @return void
			 * @hide
			 */
			updatelimitObject: function () {
				if (Aloha.editables && Aloha.editables.length > 0) {
					var parents = this.getStartContainerParents(),
						editables = Aloha.editables,
						i,
					    el,
					    j,
					    editable;
					for (i = 0; i < parents.length; i++) {
						el = parents[i];
						for (j = 0; j < editables.length; j++) {
							editable = editables[j].obj[0];
							if (el === editable) {
								this.limitObject = el;
								return true;
							}
						}
					}
				}
				this.limitObject = jQuery('body');
				return true;
			},

			/**
			 * string representation of the range object
			 * @param	verbose	set to true for verbose output
			 * @return string representation of the range object
			 * @hide
			 */
			toString: function (verbose) {
				if (!verbose) {
					return 'Aloha.Selection.SelectionRange';
				}
				return 'Aloha.Selection.SelectionRange {start [' + this.startContainer.nodeValue + '] offset ' + this.startOffset + ', end [' + this.endContainer.nodeValue + '] offset ' + this.endOffset + '}';
			}

		}) // SelectionRange

	}); // Selection


	/**
	 * This method implements an ugly workaround for a selection problem in ie:
	 * when the cursor shall be placed at the end of a text node in a li element, that is followed by a nested list,
	 * the selection would always snap into the first li of the nested list
	 * therefore, we make sure that the text node ends with a space and place the cursor right before it
	 */
	function nestedListInIEWorkaround(range) {
		var nextSibling;
		if (Aloha.browser.msie && range.startContainer === range.endContainer && range.startOffset === range.endOffset && range.startContainer.nodeType == 3 && range.startOffset == range.startContainer.data.length && range.startContainer.nextSibling) {
			nextSibling = range.startContainer.nextSibling;
			if ('OL' === nextSibling.nodeName || 'UL' === nextSibling.nodeName) {
				if (range.startContainer.data[range.startContainer.data.length - 1] == ' ') {
					range.startOffset = range.endOffset = range.startOffset - 1;
				} else {
					range.startContainer.data = range.startContainer.data + ' ';
				}
			}
		}
	}

	function correctRange(range) {
		nestedListInIEWorkaround(range);
		return range;
	}

	/**
	 * Implements Selection http://html5.org/specs/dom-range.html#selection
	 * @namespace Aloha
	 * @class Selection This singleton class always represents the
	 *        current user selection
	 * @singleton
	 */
	var AlohaSelection = Class.extend({

		_constructor: function (nativeSelection) {

			this._nativeSelection = nativeSelection;
			this.ranges = [];

			// will remember if urged to not change the selection
			this.preventChange = false;

		},

		/**
		 * Returns the element that contains the start of the selection. Returns null if there's no selection.
		 * @readonly
		 * @type Node
		 */
		anchorNode: null,

		/**
		 * Returns the offset of the start of the selection relative to the element that contains the start
		 * of the selection. Returns 0 if there's no selection.
		 * @readonly
		 * @type int
		 */
		anchorOffset: 0,

		/**
		 * Returns the element that contains the end of the selection.
		 * Returns null if there's no selection.
		 * @readonly
		 * @type Node
		 */
		focusNode: null,

		/**
		 * Returns the offset of the end of the selection relative to the element that contains the end
		 * of the selection. Returns 0 if there's no selection.
		 * @readonly
		 * @type int
		 */
		focusOffset: 0,

		/**
		 * Returns true if there's no selection or if the selection is empty. Otherwise, returns false.
		 * @readonly
		 * @type boolean
		 */
		isCollapsed: false,

		/**
		 * Returns the number of ranges in the selection.
		 * @readonly
		 * @type int
		 */
		rangeCount: 0,

		/**
		 * Replaces the selection with an empty one at the given position.
		 * @throws a WRONG_DOCUMENT_ERR exception if the given node is in a different document.
		 * @param parentNode Node of new selection
		 * @param offest offest of new Selection in parentNode
		 * @void
		 */
		collapse: function (parentNode, offset) {
			this._nativeSelection.collapse(parentNode, offset);
		},

		/**
		 * Replaces the selection with an empty one at the position of the start of the current selection.
		 * @throws an INVALID_STATE_ERR exception if there is no selection.
		 * @void
		 */
		collapseToStart: function () {
			throw "NOT_IMPLEMENTED";
		},

		/**
		 * @void
		 */
		extend: function (parentNode, offset) {

		},

		/**
		 * @param alter DOMString
		 * @param direction DOMString
		 * @param granularity DOMString
		 * @void
		 */
		modify: function (alter, direction, granularity) {

		},

		/**
		 * Replaces the selection with an empty one at the position of the end of the current selection.
		 * @throws an INVALID_STATE_ERR exception if there is no selection.
		 * @void
		 */
		collapseToEnd: function () {
			this._nativeSelection.collapseToEnd();
		},

		/**
		 * Replaces the selection with one that contains all the contents of the given element.
		 * @throws a WRONG_DOCUMENT_ERR exception if the given node is in a different document.
		 * @param parentNode Node the Node fully select
		 * @void
		 */
		selectAllChildren: function (parentNode) {
			throw "NOT_IMPLEMENTED";
		},

		/**
		 * Deletes the contents of the selection
		 */
		deleteFromDocument: function () {
			throw "NOT_IMPLEMENTED";
		},

		/**
		 * NB!
		 * We have serious problem in IE.
		 * The range that we get in IE is not the same as the range we had set,
		 * so even if we normalize it during getRangeAt, in IE, we will be
		 * correcting the range to the "correct" place, but still not the place
		 * where it was originally set.
		 *
		 * Returns the given range.
		 * The getRangeAt(index) method returns the indexth range in the list.
		 * NOTE: Aloha Editor only support 1 range! index can only be 0
		 * @throws INDEX_SIZE_ERR DOM exception if index is less than zero or
		 * greater or equal to the value returned by the rangeCount.
		 * @param index int
		 * @return Range return the selected range from index
		 */
		getRangeAt: function (index) {
			return correctRange(this._nativeSelection.getRangeAt(index));
			//if ( index < 0 || this.rangeCount ) {
			//	throw "INDEX_SIZE_ERR DOM";
			//}
			//return this._ranges[index];
		},

		/**
		 * Adds the given range to the selection.
		 * The addRange(range) method adds the given range Range object to the list of
		 * selections, at the end (so the newly added range is the new last range).
		 * NOTE: Aloha Editor only support 1 range! The added range will replace the
		 * range at index 0
		 * see http://html5.org/specs/dom-range.html#selection note about addRange
		 * @throws an INVALID_NODE_TYPE_ERR exception if the given Range has a boundary point
		 * node that's not a Text or Element node, and an INVALID_MODIFICATION_ERR exception
		 * if it has a boundary point node that doesn't descend from a Document.
		 * @param range Range adds the range to the selection
		 * @void
		 */
		addRange: function (range) {
			// set readonly attributes
			this._nativeSelection.addRange(range);
			// We will correct the range after rangy has processed the native
			// selection range, so that our correction will be the final fix on
			// the range according to the guarentee's that Aloha wants to make
			this._nativeSelection._ranges[0] = correctRange(range);

			// make sure, the old Aloha selection will be updated (until all implementations use the new AlohaSelection)
			Aloha.Selection.updateSelection();
		},

		/**
		 * Removes the given range from the selection, if the range was one of the ones in the selection.
		 * NOTE: Aloha Editor only support 1 range! The added range will replace the
		 * range at with index 0
		 * @param range Range removes the range from the selection
		 * @void
		 */
		removeRange: function (range) {
			this._nativeSelection.removeRange();
		},

		/**
		 * Removes all the ranges in the selection.
		 * @viod
		 */
		removeAllRanges: function () {
			this._nativeSelection.removeAllRanges();
		},

		/**
		 * INFO: Method is used for integration with Gentics
		 * Aloha, has no use otherwise Updates the rangeObject
		 * according to the current user selection Method is
		 * always called on selection change
		 *
		 * @param event
		 *            jQuery browser event object
		 * @return true when rangeObject was modified, false
		 *         otherwise
		 * @hide
		 */
		refresh: function (event) {

		},

		/**
		 * String representation
		 *
		 * @return "Aloha.Selection"
		 * @hide
		 */
		toString: function () {
			return 'Aloha.Selection';
		},

		getRangeCount: function () {
			return this._nativeSelection.rangeCount;
		}

	});

	/**
	 * A wrapper for the function of the same name in the rangy core-depdency.
	 * This function should be preferred as it hides the global rangy object.
	 * For more information look at the following sites:
	 * http://html5.org/specs/dom-range.html
	 * @param window optional - specifices the window to get the selection of
	 */
	Aloha.getSelection = function (target) {
		target = (target !== document || target !== window) ? window : target;
		// Aloha.Selection.refresh()
		// implement Aloha Selection
		// TODO cache
		return new AlohaSelection(window.rangy.getSelection(target));
	};

	/**
	 * A wrapper for the function of the same name in the rangy core-depdency.
	 * This function should be preferred as it hides the global rangy object.
	 * Please note: when the range object is not needed anymore,
	 *   invoke the detach method on it. It is currently unknown to me why
	 *   this is required, but that's what it says in the rangy specification.
	 * For more information look at the following sites:
	 * http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
	 * @param document optional - specifies which document to create the range for
	 */
	Aloha.createRange = function (givenWindow) {
		return window.rangy.createRange(givenWindow);
	};

	/**
	 * Method to scroll the window to move the current collapsed selection into
	 * the viewport. If the selection is not collapsed or some browser functionality
	 * for detecting the position of the current selection is not available, nothing
	 * will be done. This applies e.g. to older IE versions.
	 */
	Aloha.scrollToSelection = function () {
		var sel = Aloha.getSelection();
		// no selection, do nothing
		if (sel.getRangeCount() === 0) {
			return;
		}
		var range = sel.getRangeAt(0);
		if (!range.collapsed) {
			return;
		}

		// check for necessary browser functionality
		if (!range.nativeRange) {
			return;
		}
		if (!range.nativeRange.getClientRects) {
			return;
		}

		// determine the position of the current selection as close as possible
		var rect, top, bottom, prev, next;
		if (range.nativeRange.getClientRects().length === 0) {
			if (range.startContainer.nodeType === 3) {
				rect = range.startContainer.parentNode.getBoundingClientRect();
				top = rect.top;
				bottom = rect.bottom;
			} else {
				// the start container is not a text node, so we get the bounding rectangle of the start container itself
				rect = range.startContainer.getBoundingClientRect();
				top = rect.top;
				bottom = rect.bottom;

				// we refine the top and bottom positions by getting the bounding rectangles of the previous and next elements
				if (range.startOffset > 0) {
					prev = range.startContainer.childNodes[range.startOffset - 1];
					next = prev.nextSibling;
				} else {
					next = range.startContainer.firstChild;
				}
				if (prev && prev.nodeType === 1) {
					top = prev.getBoundingClientRect().bottom;
				}
				if (next && next.nodeType === 1) {
					bottom = next.getBoundingClientRect().top;
				}
			}
		} else {
			rect = range.nativeRange.getClientRects()[0];
			top = rect.top;
			bottom = rect.bottom;
		}

		// scroll the window if necessary
		var $win = jQuery(window);
		if (top < 0) {
			$win.scrollTop($win.scrollTop() + top);
		} else if (bottom > $win.height()) {
			$win.scrollTop($win.scrollTop() + (bottom - $win.height()));
		}

		var $scrollable = jQuery(range.startContainer).closest(':hasScroll(y)');
		if ($scrollable.length > 0 && !$scrollable.is($win) && !$scrollable.is(jQuery('html'))) {
			var scrollRect = $scrollable[0].getBoundingClientRect();
			if (top < scrollRect.top) {
				// scroll up
				$scrollable.scrollTop($scrollable.scrollTop() - (scrollRect.top - top));
			} else if (bottom > scrollRect.bottom) {
				// scroll down
				$scrollable.scrollTop($scrollable.scrollTop() + (bottom - scrollRect.bottom));
			}
		}
	};

	var selection = new Selection();
	Aloha.Selection = selection;

	return selection;
});

/* block-jump.js is part of the Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Implements some logic related to moving the cursor keys across blocks.
 * 
 * In the following example
 *
 * "some text<span class="aloha-block ..." contenteditable="false" ...>...</span>[]some text"
 *
 * when one moves the cursor indicated by "[]" to the left, the entire
 * non-contenteditable block is skipped. The same for moving the cursor
 * right across the block.
 *
 * TODO: actually, the block shouldn't be skipped, it should be
 *       selected/highlighted first.
 * TODO: this file currently doesn't contain all the code to implement
 *       block jumping. Some of it is currently implemented in markup.js.
 */
define('aloha/block-jump',[
	'aloha/core',
	'jquery',
	'aloha/console'
], function (
	Aloha,
	$,
	console
) {
	

	var zeroWidthNode = null;

	/**
	 * Replaces the text in given text with the given text.
	 *
	 * @param node
	 *        A text node attached to the DOM.
	 * @param text
	 *        A string that is to replace the text of the given text node.
	 */
	function replaceMergeTextNode(node, text) {
		node.deleteData(0, node.length);
		if ('' !== text) {
			if (node.nextSibling && 3 === node.nextSibling.nodeType) {
				node.nextSibling.insertData(0, text);
			} else if (node.previousSibling && 3 === node.previousSibling.nodeType) {
				node.previousSibling.insertData(node.previousSibling.length, text);
			} else {
				node.insertData(0, text);
			}
		}
		// We don't remove the node immediately to avoid intefering with a
		// caller's range object that may have a start or end containers
		// equal to this node. Removing it in a timeout may still interfere
		// with the selection, but that was not a problem during testing.
		setTimeout(function () {
			if (0 === node.length) {
				$(node).remove();
			}
		}, 0);
	}

	/**
	 * Removes a previously inserted zero width text node.
	 * See insertZeroWidthTextNodeFix().
	 */
	function removeZeroWidthTextNodeFix() {
		if (!zeroWidthNode) {
			return;
		}
		// We want to only replace a single zero-width character to avoid
		// interfering with the other zero-width whitespace hack that makes
		// empty lines visible in IE7.
		var text = zeroWidthNode.nodeValue.replace(/\u200b/, '');
		if (text === zeroWidthNode.nodeValue) {
			console.warn('Expected to remove the zero width text node fix, but couldn\'t find it');
		}
		replaceMergeTextNode(zeroWidthNode, text);
		zeroWidthNode = null;
	}

	/**
	 * Inserts a zero width text node before or after a block.
	 *
	 * There is a problem where some browsers can't select the boundary
	 * between some contenteditable content and non-contenteditable
	 * content. For example, if in the example at the top of the file
	 * the selection were one step to the right "...</span>s[]ome..."
	 * and the left cursor key were pressed, then the selection would
	 * just disappear or be stuck between the span and the text node.
	 *
	 * To work around this problem a zero width text node is inserted
	 * before or after a block.
	 *
	 * The inserted zero width text node will be removed automatically
	 * when it isn't necessary any more (on selection change or on
	 * editable.getContents()).
	 *
	 * TODO: In retrospect, a better alternative may be to simply wrap
	 *       every inlin-block with an editable span.
	 * @param block
	 *        The DOM element for a block before or after which the zero
	 *        width text node will be inserted.
	 * @param isGoingLeft
	 *        True if the zero width text node is to be inserted after
	 *        the block element, or false if the zero width text node is
	 *        to be inserted before the block element.
	 * @return
	 *        The text node that was inserted.
	 */
	function insertZeroWidthTextNodeFix(block, isGoingLeft) {
		removeZeroWidthTextNodeFix();
		zeroWidthNode = document.createTextNode("\u200b");
		if (isGoingLeft) {
			$(block).after(zeroWidthNode);
		} else {
			$(block).before(zeroWidthNode);
		}
		Aloha.bind('aloha-selection-changed', function (event) {
			removeZeroWidthTextNodeFix();
			Aloha.unbind(event);
		});
		return zeroWidthNode;
	}

	return {
		removeZeroWidthTextNodeFix: removeZeroWidthTextNodeFix,
		insertZeroWidthTextNodeFix: insertZeroWidthTextNodeFix
	};
});

/* markup.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/markup',[
	'aloha/core',
	'util/class',
	'util/html',
	'jquery',
	'aloha/ecma5shims',
	'aloha/console',
	'aloha/block-jump',
	'aloha/content-rules'
], function (
	Aloha,
	Class,
	Html,
	jQuery,
	shims,
	console,
	BlockJump,
	ContentRules
) {
	

	var GENTICS = window.GENTICS;

	var isOldIE = !!(Aloha.browser.msie && 9 > parseInt(Aloha.browser.version, 10));

	function isBR(node) {
		return 'BR' === node.nodeName;
	}

	function isBlock(node) {
		return 'false' === jQuery(node).attr('contenteditable');
	}

	function isTextNode(node) {
		return node && 3 === node.nodeType; // Node.TEXT_NODE
	}

	function nodeLength(node) {
		return !node ? 0 : (isTextNode(node) ? node.length : node.childNodes.length);
	}

	/**
	 * Determines whether the given text node is visible to the the user,
	 * based on our understanding that browsers will not display
	 * superfluous white spaces.
	 *
	 * @param {HTMLEmenent} node The text node to be checked.
	 */
	function isVisibleTextNode(node) {
		return 0 < node.data.replace(/\s+/g, '').length;
	}

	function nextVisibleNode(node) {
		if (!node) {
			return null;
		}

		if (node.nextSibling) {
			// Skip over nodes that the user cannot see ...
			if (isTextNode(node.nextSibling) && !isVisibleTextNode(node.nextSibling)) {
				return nextVisibleNode(node.nextSibling);
			}

			// Skip over propping <br>s ...
			if (isBR(node.nextSibling) && node.nextSibling === node.parentNode.lastChild) {
				return nextVisibleNode(node.nextSibling);
			}

			// Skip over empty editable elements ...
			if ('' === node.nextSibling.innerHTML && !isBlock(node.nextSibling)) {
				return nextVisibleNode(node.nextSibling);
			}

			return node.nextSibling;
		}

		if (node.parentNode) {
			return nextVisibleNode(node.parentNode);
		}

		return null;
	}

	function prevVisibleNode(node) {
		if (!node) {
			return null;
		}

		if (node.previousSibling) {
			// Skip over nodes that the user cannot see...
			if (isTextNode(node.previousSibling) && !isVisibleTextNode(node.previousSibling)) {
				return prevVisibleNode(node.previousSibling);
			}

			// Skip over empty editable elements ...
			if ('' === node.previousSibling.innerHTML && !isBlock(node.previousSibling)) {
				return prevVisibleNode(node.previouSibling);
			}

			return node.previousSibling;
		}

		if (node.parentNode) {
			return prevVisibleNode(node.parentNode);
		}

		return null;
	}

	/**
	 * Checks if the caret (the passed offset) is at the start
	 * of the passed node. This also trims whitespace before checking.
	 *
	 * @param {Object} node    A DOM node
	 * @param {number} offset  Offset into the node, this is 0 or 1 for elements
	 * @return {boolean}       True or false
	 */
	function isFrontPosition(node, offset) {
		if (isTextNode(node)
				&& offset <= node.data.length - node.data.replace(/^\s+/, '').length) {
			return true;
		}

		return offset === 0;
	}

	function isBlockInsideEditable($block) {
		return $block.parent().hasClass('aloha-editable');
	}

	function isEndPosition(node, offset) {
		var length = nodeLength(node);

		if (length === offset) {
			return true;
		}

		var isText = isTextNode(node);

		// If within a text node, then ignore superfluous white-spaces,
		// since they are invisible to the user.
		if (isText && node.data.replace(/\s+$/, '').length === offset) {
			return true;
		}

		if (1 === length && !isText) {
			return isBR(node.childNodes[0]);
		}

		return false;
	}

	function blink(node) {
		jQuery(node).stop(true).css({
			opacity: 0
		}).fadeIn(0).delay(100).fadeIn(function () {
			jQuery(node).css({
				opacity: 1
			});
		});

		return node;
	}

	function nodeContains(node1, node2) {
		return isOldIE ? (shims.compareDocumentPosition(node1, node2) & 16) : 0 < jQuery(node1).find(node2).length;
	}

	function isInsidePlaceholder(range) {
		var start = range.startContainer;
		var end = range.endContainer;
		var $placeholder = window.$_alohaPlaceholder;

		return $placeholder.is(start) || $placeholder.is(end) || nodeContains($placeholder[0], start) || nodeContains($placeholder[0], end);
	}

	function cleanupPlaceholders(range) {
		if (window.$_alohaPlaceholder && !isInsidePlaceholder(range)) {
			if (0 === window.$_alohaPlaceholder.html().replace(/^(&nbsp;)*$/, '').length) {
				window.$_alohaPlaceholder.remove();
			}

			window.$_alohaPlaceholder = null;
		}
	}

	/**
	 * @TODO(petro): We need to be more intelligent about whether we insert a
	 *               block-level placeholder or a phrasing level element.
	 * @TODO(petro): test with <pre>
	 * @TODO: move to block-jump.js
	 */
	function jumpBlock(block, isGoingLeft, currentRange) {
		var range = new GENTICS.Utils.RangeObject();
		var sibling = isGoingLeft ? prevVisibleNode(block) : nextVisibleNode(block);

		if (!sibling || isBlock(sibling)) {
			var $landing = jQuery('<div class="aloha-placeholder aloha-editing-div">&nbsp;</div>');

			if (isGoingLeft) {
				jQuery(block).before($landing);
			} else {
				jQuery(block).after($landing);
			}

			range.startContainer = range.endContainer = $landing[0];
			range.startOffset = range.endOffset = 0;

			// Clear out any old placeholder first ...
			cleanupPlaceholders(range);

			window.$_alohaPlaceholder = $landing;
		} else {

			// Don't jump the block yet if the cursor is moving to the
			// beginning or end of a text node, or if it is about to leave
			// an element node. Both these cases require a hack in some
			// browsers.
			var moveToBoundaryPositionInIE = ( // To the beginning or end of a text node?
				(currentRange.startContainer.nodeType === 3
				 && currentRange.startContainer === currentRange.endContainer
				 && currentRange.startContainer.nodeValue !== ""
				 && (isGoingLeft ? currentRange.startOffset === 1 : currentRange.endOffset + 1 === currentRange.endContainer.length))
				// Leaving an element node?
					|| (currentRange.startContainer.nodeType === 1
						&& (!currentRange.startOffset
							|| (currentRange.startContainer.childNodes[currentRange.startOffset] && currentRange.startContainer.childNodes[currentRange.startOffset].nodeType === 1)))
			);

			if (moveToBoundaryPositionInIE) {
				// The cursor is moving to the beginning or end of a text
				// node, or is leaving an element node, which requires a
				// hack in some browsers.
				var zeroWidthNode = BlockJump.insertZeroWidthTextNodeFix(block, isGoingLeft);
				range.startContainer = range.endContainer = zeroWidthNode;
				range.startOffset = range.endOffset = isGoingLeft ? 1 : 0;
			} else {
				// The selection is already at the boundary position - jump
				// the block.
				range.startContainer = range.endContainer = sibling;
				range.startOffset = range.endOffset = isGoingLeft ? nodeLength(sibling) : 0;
				if (!isGoingLeft) {
					// Just as above, jumping to the first position right of
					// a block requires a hack in some browsers. Jumping
					// left seems to be fine.
					BlockJump.insertZeroWidthTextNodeFix(block, true);
				}
			}
			cleanupPlaceholders(range);
		}

		range.select();

		Aloha.trigger('aloha-block-selected', block);
		Aloha.Selection.preventSelectionChanged();
	}

	/**
	 * recursively search through parent nodes to find if
	 * node is child of a specific node.
	 *
	 * @param {DOMNode} starting node
	 * @param {Array[String]} Array of UPPERCASE (!) node names to search for, eg. ["TD"] or ["TD", "TH"].
	 * @return true if node is child of a node of nodeName, false otherwise
	 */
	function isChildOf(node, nodeNames) {
		var i;
		if (node.parentNode) {
			for (i = 0; i < nodeNames.length; i++) {
				if (nodeNames[i] === node.parentNode.nodeName) {
					return true;
				}
			}
			return isChildOf(node.parentNode, nodeNames);
		} else {
			return false;
		}
	}

	/**
	 * Will recursively check if the current node is the first node in
	 * it's hierarchy up it's ancestor tree until the stopNode is reached.
	 * Useful to find eg. if you're in the first td within a table.
	 * Will stop if stopNodeName is encountered or the root node is reached.
	 *
	 * @param {DOMnode} node to start from
	 * @param {String} UPPERCASE node name to stop search at
	 * @return true if node is the first node, false otherwise
	 */
	function isFirstNode(node, stopNodeName) {
		if (!node.parentNode) {
			return true;
		}

		// firstChild will also find textNodes while children[0] will only return non-text nodes
		var isTextNode = ((node.nodeType === 3 && node.parentNode.firstChild === node) || (node.parentNode.children[0] === node));

		// unfortunately we need to take care of the aloha-table-selectrow and aloha-table-selectcolumn
		var isTableSelectRow = node.nodeName === 'TR' && node.parentNode.children[0].className.indexOf('aloha-table-selectcolumn') !== -1;
		var isTableSelectColumn = node.nodeName === 'TD' && node.parentNode.children[0].className.indexOf('aloha-table-selectrow') !== -1;
		var isFirstNodeOfTable = ((isTableSelectColumn || isTableSelectRow) && node.parentNode.children[1] === node);

		if (isTextNode || isFirstNodeOfTable) {
			if (node.parentNode.nodeName === stopNodeName) {
				return true;
			} else {
				return isFirstNode(node.parentNode, stopNodeName);
			}
		} else {
			return false;
		}
	}

	/**
	 * Will recurseively check if the current node is the last node in
	 * it's hierarchy up it's ancestor tree until the stopNode is reached.
	 * Useful to find eg. if you're in the last td within a table.
	 * Will stop if stopNodeName is encountered or the root node is reached.
	 * Will ignore whitespace text nodes and caption nodes
	 *
	 * @param {DOMnode} node to start from
	 * @param {String} UPPERCASE node name to stop search at
	 * @return true if node is the last node, false otherwise
	 */
	// implemented as an IIFE because the local helper getLast() should only be defined once
	var isLastNode = (function () {
		// get the last node that is not empty text or a table caption
		function getLast(node) {
			var last, i;

			for (i = node.childNodes.length - 1; i > -1; i--) {
				last = node.childNodes[i];
				if (last.nodeName !== 'CAPTION' && !(last.nodeType === 3 && /^[\t\n\r ]+$/.test(last.data))) {
					return last;
				}
			}

			return node.lastChild;
		}

		return function (node, stopNodeName) {
			if (!node.parentNode) {
				return true;
			}

			if (getLast(node.parentNode) === node) {
				if (node.parentNode.nodeName === stopNodeName) {
					return true;
				} else {
					return isLastNode(node.parentNode, stopNodeName);
				}
			} else {
				return false;
			}
		};
	}());

	/**
	 * Markup object
	 */
	Aloha.Markup = Class.extend({

		/**
		 * Key handlers for special key codes
		 */
		keyHandlers: {},

		/**
		 * Add a key handler for the given key code
		 * @param keyCode key code
		 * @param handler handler function
		 */
		addKeyHandler: function (keyCode, handler) {
			if (!this.keyHandlers[keyCode]) {
				this.keyHandlers[keyCode] = [];
			}

			this.keyHandlers[keyCode].push(handler);
		},

		/**
		 * Removes a key handler for the given key code
		 * @param keyCode key code
		 */
		removeKeyHandler: function (keyCode) {
			if (this.keyHandlers[keyCode]) {
				this.keyHandlers[keyCode] = null;
			}
		},

		insertBreak: function () {
			var range = Aloha.Selection.rangeObject,
				nonWSIndex,
				nextTextNode,
				newBreak;

			if (!range.isCollapsed()) {
				this.removeSelectedMarkup();
			}

			newBreak = jQuery('<br/>');
			GENTICS.Utils.Dom.insertIntoDOM(newBreak, range, Aloha.activeEditable.obj);

			nextTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(
				newBreak.parent().get(0),
				GENTICS.Utils.Dom.getIndexInParent(newBreak.get(0)) + 1,
				false
			);

			if (nextTextNode) {
				// trim leading whitespace
				nonWSIndex = nextTextNode.data.search(/\S/);
				if (nonWSIndex > 0) {
					nextTextNode.data = nextTextNode.data.substring(nonWSIndex);
				}
			}

			range.startContainer = range.endContainer = newBreak.get(0).parentNode;
			range.startOffset = range.endOffset = GENTICS.Utils.Dom.getIndexInParent(newBreak.get(0)) + 1;
			range.correctRange();
			range.clearCaches();
			range.select();
		},

		/**
		 * first method to handle key strokes
		 * @param event DOM event
		 * @param rangeObject as provided by Aloha.Selection.getRangeObject();
		 * @return "Aloha.Selection"
		 */
		preProcessKeyStrokes: function (event) {
			if (event.target.nodeName === 'INPUT' || event.target.nodeName === 'TEXTAREA') {
				// Just let the browser handle all events on input fields.
				event.stopImmediatePropagation();

				return true;
			}

			if (event.type !== 'keydown') {
				return false;
			}

			var rangeObject,
			    handlers,
			    i;

			if (this.keyHandlers[event.keyCode]) {
				handlers = this.keyHandlers[event.keyCode];
				for (i = 0; i < handlers.length; ++i) {
					if (!handlers[i](event)) {
						return false;
					}
				}
			}

			// LEFT (37), RIGHT (39) keys for block detection
			if (event.keyCode === 37 || event.keyCode === 39) {
				if (Aloha.getSelection().getRangeCount()) {
					rangeObject = Aloha.getSelection().getRangeAt(0);

					if (this.processCursor(rangeObject, event.keyCode)) {
						cleanupPlaceholders(Aloha.Selection.rangeObject);
						return true;
					}
				}

				return false;
			}

			// UP (38), DOWN (40) keys for table navigation
			if (event.keyCode === 38 || event.keyCode === 40) {
				if (Aloha.getSelection().getRangeCount()) {
					rangeObject = Aloha.getSelection().getRangeAt(0);
					if (this.processCursorUpDown(rangeObject, event.keyCode)) {
						return false;
					}
				}
				return true;
			}

			// Internet Explorer hack: IE would allow to move the cursor into non-editable areas.
			// we cannot prevent this, without breaking the selection & copy/paste functionality.
			// so we simply prevent any keypress events that would insert text into non-editable areas
			// keypresses with the ctrl-key are allowed, because they trigger functionality like copying
			if (Aloha.browser.msie && !Aloha.Selection.isSelectionEditable() && !event.ctrlKey) {
				return false;
			}

			// BACKSPACE
			if (event.keyCode === 8) {
				event.preventDefault(); // prevent history.back() even on exception
				Aloha.execCommand('delete', false);
				return false;
			}

			// DELETE
			if (event.keyCode === 46) {
				Aloha.execCommand('forwarddelete', false);
				return false;
			}

			// ENTER
			if (event.keyCode === 13) {
				if (!event.shiftKey && Html.allowNestedParagraph(Aloha.activeEditable) && ContentRules.isAllowed(Aloha.activeEditable.obj, 'p')) {
					Aloha.execCommand('insertparagraph', false);
					return false;
				// if the shift key is pressed, or if the active editable is not allowed
				// to contain paragraphs, a linebreak is inserted instead
				} else if (ContentRules.isAllowed(Aloha.activeEditable.obj, 'br')) {
					Aloha.execCommand('insertlinebreak', false);
					return false;
				} else if (Html.allowNestedParagraph(Aloha.activeEditable) && ContentRules.isAllowed(Aloha.activeEditable.obj, 'p')) {
					Aloha.execCommand('insertparagraph', false);
					return false;
				} else {
					return false;
				}
			}
			return true;
		},

		/**
		 * processing up and down cursor keys inside tables
		 * will only try to figure out if cursor is at first
		 * or last position in table and exit to the next
		 * editable node from there
		 *
		 * won't do anything if range is not collapsed
		 * 
		 * @param {RangyRange} range A range object for the current selection.
		 * @param {number} keyCode Code of the currently pressed key.
		 * @return {boolean} true if something was done, false if browser should 
		 * continue handling the event
		 */
		processCursorUpDown: function (range, keyCode) {
			if (!range.collapsed) {
				return false;
			}

			var node = range.startContainer,
				tableWrapper,
				cursorNode;

			// UP
			if (keyCode === 38 &&
					isFrontPosition(node, range.startOffset) &&
					isChildOf(node, ['TD', 'TH']) &&
					isFirstNode(node, 'TABLE')) {

				// we want to position the cursor now in the first 
				// element before the table, so we need to find the
				// table wrapper first ...
				tableWrapper = jQuery(node).parents('div.aloha-table-wrapper').get(0);
				if (!tableWrapper) {
					return false;
				}

				// ... and then find it's previousSibling
				// which we will descend down to its deepest
				// nested child node, where we will put the
				// cursor
				// prefer previousElemntSibling because Firefox will land you in a
				// whitespace text node between a preceding <p> and the table otherwise
				if (tableWrapper.previousElementSibling) {
					cursorNode = tableWrapper.previousElementSibling;
				} else {
					cursorNode = tableWrapper.previousSibling;
				}
				while (cursorNode.nodeType !== 3) {
					cursorNode = cursorNode.lastChild;
					if (cursorNode === null) {
						// stop if there is no element to be entered before the table
						return false;
					}
				}

				Aloha.Selection.rangeObject.startContainer = cursorNode;
				Aloha.Selection.rangeObject.endContainer = cursorNode;
				Aloha.Selection.rangeObject.startOffset = cursorNode.length;
				Aloha.Selection.rangeObject.endOffset = cursorNode.length;
				Aloha.Selection.rangeObject.select();

				// Mozilla needs this fix or else the selection will not work
				if (Aloha.activeEditable && jQuery.browser.mozilla && document.activeElement !== Aloha.activeEditable.obj[0]) {
					Aloha.activeEditable.obj.focus();
				}

				return true;

			// DOWN
			} else if (keyCode === 40 &&
					isEndPosition(node, range.startOffset) &&
					isChildOf(node, ['TD', 'TH']) &&
					isLastNode(node, 'TABLE')) {

				// we want to put the cursor in the first element right 
				// after the table so we need to find the table wrapper first
				tableWrapper = jQuery(node).parents('div.aloha-table-wrapper').get(0);
				if (!tableWrapper) {
					return false;
				}

				// and now find its following sibling where we will put
				// the cursor in the first position
				// the next elementSibling is preffered over the nextSibling
				// because Mozilla will sometimes have an empty text node
				// right next to the table - but we most likely want to put 
				// the cursor into the next paragraph
				if (tableWrapper.nextElementSibling) {
					cursorNode = tableWrapper.nextElementSibling;
				} else {
					cursorNode = tableWrapper.nextSibling;
				}

				while (cursorNode.nodeType !== 3) {
					cursorNode = cursorNode.firstChild;
					if (cursorNode === null) {
						return false;
					}
				}

				Aloha.Selection.rangeObject.startContainer = cursorNode;
				Aloha.Selection.rangeObject.endContainer = cursorNode;
				Aloha.Selection.rangeObject.startOffset = 0;
				Aloha.Selection.rangeObject.endOffset = 0;
				Aloha.Selection.rangeObject.select();

				// Mozilla needs this fix or else the selection will not work
				if (Aloha.activeEditable && jQuery.browser.mozilla && document.activeElement !== Aloha.activeEditable.obj[0]) {
					Aloha.activeEditable.obj.focus();
				}

				return true;

			} else {
				return false;
			}
		},

		/**
		 * Processing of cursor keys.
		 * Detect blocks (elements with contenteditable=false) and will select them
		 * (normally the cursor would simply jump right past them).
		 *
		 * For each block that is selected, an 'aloha-block-selected' event will be
		 * triggered.
		 *
		 * TODO: the above is what should happen. Currently we just skip past blocks.
		 *
		 * @param {RangyRange} range A range object for the current selection.
		 * @param {number} keyCode Code of the currently pressed key.
		 * @return {boolean} False if a block was found, to prevent further events,
		 *                   true otherwise.
		 * @TODO move to block-jump.js
		 */
		processCursor: function (range, keyCode) {
			if (!range.collapsed) {
				return true;
			}

			BlockJump.removeZeroWidthTextNodeFix();

			var node = range.startContainer,
				selection = Aloha.getSelection();

			if (!node) {
				return true;
			}

			var sibling, offset;

			// special handling for moving Cursor around zero-width whitespace in IE7
			if (Aloha.browser.msie && parseInt(Aloha.browser.version, 10) <= 7 && isTextNode(node)) {
				if (keyCode == 37) {
					// moving left -> skip zwsp to the left
					offset = range.startOffset;
					while (offset > 0 && node.data.charAt(offset - 1) === '\u200b') {
						offset--;
					}
					if (offset != range.startOffset) {
						range.setStart(range.startContainer, offset);
						range.setEnd(range.startContainer, offset);
						selection = Aloha.getSelection();
						selection.removeAllRanges();
						selection.addRange(range);
					}
				} else if (keyCode == 39) {
					// moving right -> skip zwsp to the right
					offset = range.startOffset;
					while (offset < node.data.length && node.data.charAt(offset) === '\u200b') {
						offset++;
					}
					if (offset != range.startOffset) {
						range.setStart(range.startContainer, offset);
						range.setEnd(range.startContainer, offset);
						selection.removeAllRanges();
						selection.addRange(range);
					}
				}
			}

			// Versions of Internet Explorer that are older that 9, will
			// erroneously allow you to enter and edit inside elements which have
			// their contenteditable attribute set to false...
			if (isOldIE && !jQuery(node).contentEditable()) {
				var $parentBlock = jQuery(node).parents('[contenteditable=false]');
				var isInsideBlock = $parentBlock.length > 0;

				if (isInsideBlock) {
					if (isBlockInsideEditable($parentBlock)) {
						sibling = $parentBlock[0];
					} else {
						return true;
					}
				}
			}

			var isLeft;
			if (!sibling) {
				// True if keyCode denotes LEFT or UP arrow key, otherwise they
				// keyCode is for RIGHT or DOWN in which this value will be false.
				isLeft = (37 === keyCode || 38 === keyCode);
				offset = range.startOffset;

				if (isTextNode(node)) {
					if (isLeft) {
						var isApproachingFrontPosition = (1 === offset);
						if (!isApproachingFrontPosition && !isFrontPosition(node, offset)) {
							return true;
						}
					} else if (!isEndPosition(node, offset)) {
						return true;
					}

				} else if (node.childNodes.length > 0) {
					node = node.childNodes[offset === nodeLength(node) ? offset - 1 : offset];
				}

				sibling = isLeft ? prevVisibleNode(node) : nextVisibleNode(node);
			}

			if (isBlock(sibling)) {
				jumpBlock(sibling, isLeft, range);
				return false;
			}

			return true;
		},

		/**
		 * method handling shiftEnter
		 * @param Aloha.Selection.SelectionRange of the current selection
		 * @return void
		 */
		processShiftEnter: function (rangeObject) {
			this.insertHTMLBreak(rangeObject.getSelectionTree(), rangeObject);
		},

		/**
		 * method handling Enter
		 * @param Aloha.Selection.SelectionRange of the current selection
		 * @return void
		 */
		processEnter: function (rangeObject) {
			if (rangeObject.splitObject) {
				// now comes a very evil hack for ie, when the enter is pressed in a text node in an li element, we just append an empty text node
				// if ( Aloha.browser.msie
				//      && GENTICS.Utils.Dom
				//           .isListElement( rangeObject.splitObject ) ) {
				//  jQuery( rangeObject.splitObject ).append(
				//          jQuery( document.createTextNode( '' ) ) );
				//  }
				this.splitRangeObject(rangeObject);
			} else { // if there is no split object, the Editable is the paragraph type itself (e.g. a p or h2)
				this.insertHTMLBreak(rangeObject.getSelectionTree(), rangeObject);
			}
		},

		/**
		 * Insert the given html markup at the current selection
		 * @param html html markup to be inserted
		 */
		insertHTMLCode: function (html) {
			var rangeObject = Aloha.Selection.rangeObject;
			this.insertHTMLBreak(rangeObject.getSelectionTree(), rangeObject, jQuery(html));
		},

		/**
		 * insert an HTML Break <br /> into current selection
		 * @param Aloha.Selection.SelectionRange of the current selection
		 * @return void
		 */
		insertHTMLBreak: function (selectionTree, rangeObject, inBetweenMarkup) {
			var i,
			    treeLength,
			    el,
			    jqEl,
			    jqElBefore,
			    jqElAfter,
			    tmpObject,
			    offset,
			    checkObj;

			inBetweenMarkup = inBetweenMarkup || jQuery('<br/>');

			for (i = 0, treeLength = selectionTree.length; i < treeLength; ++i) {
				el = selectionTree[i];
				jqEl = el.domobj ? jQuery(el.domobj) : undefined;

				if (el.selection !== 'none') { // before cursor, leave this part inside the splitObject
					if (el.selection == 'collapsed') {
						// collapsed selection found (between nodes)
						if (i > 0) {
							// not at the start, so get the element to the left
							jqElBefore = jQuery(selectionTree[i - 1].domobj);

							// and insert the break after it
							jqElBefore.after(inBetweenMarkup);

						} else {
							// at the start, so get the element to the right
							jqElAfter = jQuery(selectionTree[1].domobj);

							// and insert the break before it
							jqElAfter.before(inBetweenMarkup);
						}

						// now set the range
						rangeObject.startContainer = rangeObject.endContainer = inBetweenMarkup[0].parentNode;
						rangeObject.startOffset = rangeObject.endOffset = GENTICS.Utils.Dom.getIndexInParent(inBetweenMarkup[0]) + 1;
						rangeObject.correctRange();

					} else if (el.domobj && el.domobj.nodeType === 3) { // textNode
						// when the textnode is immediately followed by a blocklevel element (like p, h1, ...) we need to add an additional br in between
						if (el.domobj.nextSibling && el.domobj.nextSibling.nodeType == 1 && Aloha.Selection.replacingElements[el.domobj.nextSibling.nodeName.toLowerCase()]) {
							// TODO check whether this depends on the browser
							jqEl.after('<br/>');
						}

						if (this.needEndingBreak()) {
							// when the textnode is the last inside a blocklevel element
							// (like p, h1, ...) we need to add an additional br as very
							// last object in the blocklevel element
							checkObj = el.domobj;

							while (checkObj) {
								if (checkObj.nextSibling) {
									checkObj = false;
								} else {
									// go to the parent
									checkObj = checkObj.parentNode;

									// found a blocklevel or list element, we are done
									if (GENTICS.Utils.Dom.isBlockLevelElement(checkObj) || GENTICS.Utils.Dom.isListElement(checkObj)) {
										break;
									}

									// reached the limit object, we are done
									if (checkObj === rangeObject.limitObject) {
										checkObj = false;
									}
								}
							}

							// when we found a blocklevel element, insert a break at the
							// end. Mark the break so that it is cleaned when the
							// content is fetched.
							if (checkObj) {
								jQuery(checkObj).append('<br class="aloha-cleanme" />');
							}
						}

						// insert the break
						jqEl.between(inBetweenMarkup, el.startOffset);

						// correct the range
						// count the number of previous siblings
						offset = 0;
						tmpObject = inBetweenMarkup[0];
						while (tmpObject) {
							tmpObject = tmpObject.previousSibling;
							++offset;
						}

						rangeObject.startContainer = inBetweenMarkup[0].parentNode;
						rangeObject.endContainer = inBetweenMarkup[0].parentNode;
						rangeObject.startOffset = offset;
						rangeObject.endOffset = offset;
						rangeObject.correctRange();

					} else if (el.domobj && el.domobj.nodeType === 1) { // other node, normally a break
						if (jqEl.parent().find('br.aloha-ephemera').length === 0) {
							// but before putting it, remove all:
							jQuery(rangeObject.limitObject).find('br.aloha-ephemera').remove();

							//  now put it:
							jQuery(rangeObject.commonAncestorContainer).append(this.getFillUpElement(rangeObject.splitObject));
						}

						jqEl.after(inBetweenMarkup);

						// now set the selection. Since we just added one break do the currect el
						// the new position must be el's position + 1. el's position is the index
						// of the el in the selection tree, which is i. then we must add
						// another +1 because we want to be AFTER the object, not before. therefor +2
						rangeObject.startContainer = rangeObject.commonAncestorContainer;
						rangeObject.endContainer = rangeObject.startContainer;
						rangeObject.startOffset = i + 2;
						rangeObject.endOffset = i + 2;
						rangeObject.update();
					}
				}
			}
			rangeObject.select();
		},

		/**
		 * Check whether blocklevel elements need breaks at the end to visibly render a newline
		 * @return true if an ending break is necessary, false if not
		 */
		needEndingBreak: function () {
			// currently, all browser except IE need ending breaks
			return !Aloha.browser.msie;
		},

		/**
		 * Get the currently selected text or false if nothing is selected (or the selection is collapsed)
		 * @return selected text
		 */
		getSelectedText: function () {
			var rangeObject = Aloha.Selection.rangeObject;

			if (rangeObject.isCollapsed()) {
				return false;
			}

			return this.getFromSelectionTree(rangeObject.getSelectionTree(), true);
		},

		/**
		 * Recursive function to get the selected text from the selection tree starting at the given level
		 * @param selectionTree array of selectiontree elements
		 * @param astext true when the contents shall be fetched as text, false for getting as html markup
		 * @return selected text from that level (incluiding all sublevels)
		 */
		getFromSelectionTree: function (selectionTree, astext) {
			var text = '', i, treeLength, el, clone;
			for (i = 0, treeLength = selectionTree.length; i < treeLength; i++) {
				el = selectionTree[i];
				if (el.selection == 'partial') {
					if (el.domobj.nodeType === 3) {
						// partial text node selected, get the selected part
						text += el.domobj.data.substring(el.startOffset, el.endOffset);
					} else if (el.domobj.nodeType === 1 && el.children) {
						// partial element node selected, do the recursion into the children
						if (astext) {
							text += this.getFromSelectionTree(el.children, astext);
						} else {
							// when the html shall be fetched, we create a clone of
							// the element and remove all the children
							clone = jQuery(el.domobj.outerHTML).empty();
							// then we do the recursion and add the selection into the clone
							clone.html(this.getFromSelectionTree(el.children, astext));
							// finally we get the html of the clone
							text += clone.outerHTML();
						}
					}
				} else if (el.selection == 'full') {
					if (el.domobj.nodeType === 3) {
						// full text node selected, get the text
						text += jQuery(el.domobj).text();
					} else if (el.domobj.nodeType === 1 && el.children) {
						// full element node selected, get the html of the node and all children
						text += astext ? jQuery(el.domobj).text() : jQuery(el.domobj).outerHTML();
					}
				}
			}

			return text;
		},

		/**
		 * Get the currently selected markup or false if nothing is selected (or the selection is collapsed)
		 * @return {?String}
		 */
		getSelectedMarkup: function () {
			var rangeObject = Aloha.Selection.rangeObject;
			return rangeObject.isCollapsed() ? null : this.getFromSelectionTree(rangeObject.getSelectionTree(), false);
		},

		/**
		 * Remove the currently selected markup
		 */
		removeSelectedMarkup: function () {
			var rangeObject = Aloha.Selection.rangeObject,
				newRange;

			if (rangeObject.isCollapsed()) {
				return;
			}

			newRange = new Aloha.Selection.SelectionRange();
			// remove the selection
			this.removeFromSelectionTree(rangeObject.getSelectionTree(), newRange);

			// do a cleanup now (starting with the commonancestorcontainer)
			newRange.update();
			GENTICS.Utils.Dom.doCleanup({
				'merge': true,
				'removeempty': true
			}, Aloha.Selection.rangeObject);
			Aloha.Selection.rangeObject = newRange;

			// need to set the collapsed selection now
			newRange.correctRange();
			newRange.update();
			newRange.select();
			Aloha.Selection.updateSelection();
		},

		/**
		 * Recursively remove the selected items, starting with the given level in the selectiontree
		 * @param selectionTree current level of the selectiontree
		 * @param newRange new collapsed range to be set after the removal
		 */
		removeFromSelectionTree: function (selectionTree, newRange) {
			// remember the first found partially selected element node (in case we need
			// to merge it with the last found partially selected element node)
			var firstPartialElement, newdata, i, el, adjacentTextNode, treeLength;

			// iterate through the selection tree
			for (i = 0, treeLength = selectionTree.length; i < treeLength; i++) {
				el = selectionTree[i];

				// check the type of selection
				if (el.selection == 'partial') {
					if (el.domobj.nodeType === 3) {
						// partial text node selected, so remove the selected portion
						newdata = '';
						if (el.startOffset > 0) {
							newdata += el.domobj.data.substring(0, el.startOffset);
						}
						if (el.endOffset < el.domobj.data.length) {
							newdata += el.domobj.data.substring(el.endOffset, el.domobj.data.length);
						}
						el.domobj.data = newdata;

						// eventually set the new range (if not done before)
						if (!newRange.startContainer) {
							newRange.startContainer = newRange.endContainer = el.domobj;
							newRange.startOffset = newRange.endOffset = el.startOffset;
						}
					} else if (el.domobj.nodeType === 1 && el.children) {
						// partial element node selected, so do the recursion into the children
						this.removeFromSelectionTree(el.children, newRange);

						if (firstPartialElement) {
							// when the first parially selected element is the same type
							// of element, we need to merge them
							if (firstPartialElement.nodeName == el.domobj.nodeName) {
								// merge the nodes
								jQuery(firstPartialElement).append(jQuery(el.domobj).contents());

								// and remove the latter one
								jQuery(el.domobj).remove();
							}

						} else {
							// remember this element as first partially selected element
							firstPartialElement = el.domobj;
						}
					}

				} else if (el.selection == 'full') {
					// eventually set the new range (if not done before)
					if (!newRange.startContainer) {
						adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(
							el.domobj.parentNode,
							GENTICS.Utils.Dom.getIndexInParent(el.domobj) + 1,
							false,
							{
								'blocklevel': false
							}
						);

						if (adjacentTextNode) {
							newRange.startContainer = newRange.endContainer = adjacentTextNode;
							newRange.startOffset = newRange.endOffset = 0;
						} else {
							newRange.startContainer = newRange.endContainer = el.domobj.parentNode;
							newRange.startOffset = newRange.endOffset = GENTICS.Utils.Dom.getIndexInParent(el.domobj) + 1;
						}
					}

					// full node selected, so just remove it (will also remove all children)
					jQuery(el.domobj).remove();
				}
			}
		},

		/**
		 * split passed rangeObject without or with optional markup
		 * @param Aloha.Selection.SelectionRange of the current selection
		 * @param markup object (jQuery) to insert in between the split elements
		 * @return void
		 */
		splitRangeObject: function (rangeObject, markup) {
			// UAAAA: first check where the markup can be inserted... *grrrrr*, then decide where to split
			// object which is split up
			var splitObject = jQuery(rangeObject.splitObject),
				selectionTree,
			    insertAfterObject,
			    followUpContainer;

			// update the commonAncestor with the splitObject (so that the selectionTree is correct)
			rangeObject.update(rangeObject.splitObject); // set the splitObject as new commonAncestorContainer and update the selectionTree

			// calculate the selection tree. NOTE: it is necessary to do this before
			// getting the followupcontainer, since getting the selection tree might
			// possibly merge text nodes, which would lead to differences in the followupcontainer
			selectionTree = rangeObject.getSelectionTree();

			// object to be inserted after the splitObject
			followUpContainer = this.getSplitFollowUpContainer(rangeObject);

			// now split up the splitObject into itself AND the followUpContainer
			this.splitRangeObjectHelper(selectionTree, rangeObject, followUpContainer); // split the current object into itself and the followUpContainer

			// check whether the followupcontainer is still marked for removal
			if (followUpContainer.hasClass('preparedForRemoval')) {
				// TODO shall we just remove the class or shall we not use the followupcontainer?
				followUpContainer.removeClass('preparedForRemoval');
			}

			// now let's find the place, where the followUp is inserted afterwards. normally that's the splitObject itself, but in
			// some cases it might be their parent (e.g. inside a list, a <p> followUp must be inserted outside the list)
			insertAfterObject = this.getInsertAfterObject(rangeObject, followUpContainer);

			// now insert the followUpContainer
			jQuery(followUpContainer).insertAfter(insertAfterObject); // attach the followUpContainer right after the insertAfterObject

			// in some cases, we want to remove the "empty" splitObject (e.g. LIs, if enter was hit twice)
			if (rangeObject.splitObject.nodeName.toLowerCase() === 'li' && !Aloha.Selection.standardTextLevelSemanticsComparator(rangeObject.splitObject, followUpContainer)) {
				jQuery(rangeObject.splitObject).remove();
			}

			rangeObject.startContainer = null;
			// first check whether the followUpContainer starts with a <br/>
			// if so, place the cursor right before the <br/>
			var followContents = followUpContainer.contents();
			if (followContents.length > 0 && followContents.get(0).nodeType == 1 && followContents.get(0).nodeName.toLowerCase() === 'br') {
				rangeObject.startContainer = followUpContainer.get(0);
			}

			if (!rangeObject.startContainer) {
				// find a possible text node in the followUpContainer and set the selection to it
				// if no textnode is available, set the selection to the followup container itself
				rangeObject.startContainer = followUpContainer.textNodes(true, true).first().get(0);
			}
			if (!rangeObject.startContainer) { // if no text node was found, select the parent object of <br class="aloha-ephemera" />
				rangeObject.startContainer = followUpContainer.textNodes(false).first().parent().get(0);
			}
			if (rangeObject.startContainer) {
				// the cursor is always at the beginning of the followUp
				rangeObject.endContainer = rangeObject.startContainer;
				rangeObject.startOffset = 0;
				rangeObject.endOffset = 0;
			} else {
				rangeObject.startContainer = rangeObject.endContainer = followUpContainer.parent().get(0);
				rangeObject.startOffset = rangeObject.endOffset = GENTICS.Utils.Dom.getIndexInParent(followUpContainer.get(0));
			}

			// finally update the range object again
			rangeObject.update();

			// now set the selection
			rangeObject.select();
		},

		/**
		 * method to get the object after which the followUpContainer can be inserted during splitup
		 * this is a helper method, not needed anywhere else
		 * @param rangeObject Aloha.Selection.SelectionRange of the current selection
		 * @param followUpContainer optional jQuery object; if provided the rangeObject will be split and the second part will be insert inside of this object
		 * @return object after which the followUpContainer can be inserted
		 */
		getInsertAfterObject: function (rangeObject, followUpContainer) {
			var passedSplitObject, i, el;

			for (i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				el = rangeObject.markupEffectiveAtStart[i];

				// check if we have already passed the splitObject (some other markup might come before)
				if (el === rangeObject.splitObject) {
					passedSplitObject = true;
				}

				// if not passed splitObject, skip this markup
				if (!passedSplitObject) {
					continue;
				}

				// once we are passed, check if the followUpContainer is allowed to be inserted into the currents el's parent
				if (Aloha.Selection.canTag1WrapTag2(jQuery(el).parent()[0].nodeName, followUpContainer[0].nodeName)) {
					return el;
				}
			}

			return false;
		},

		/**
		 * @fixme: Someone who knows what this function does, please refactor it.
		 *			1. splitObject arg is not used at all
		 *			2. Would be better to use ternary operation would be better than if else statement
		 *
		 * method to get the html code for a fillUpElement. this is needed for empty paragraphs etc., so that they take up their expected height
		 * @param splitObject split object (dom object)
		 * @return fillUpElement HTML Code
		 */
		getFillUpElement: function (splitObject) {
			if (Aloha.browser.msie) {
				return false;
			}
			return jQuery('<br class="aloha-cleanme"/>');
		},

		/**
		 * removes textNodes from passed array, which only contain contentWhiteSpace (e.g. a \n between two tags)
		 * @param domArray array of domObjects
		 * @return void
		 */
		removeElementContentWhitespaceObj: function (domArray) {
			var correction = 0,
				removeLater = [],
				i,
				el,
			    removeIndex;

			for (i = 0; i < domArray.length; ++i) {
				el = domArray[i];
				if (el.isElementContentWhitespace) {
					removeLater[removeLater.length] = i;
				}
			}

			for (i = 0; i < removeLater.length; ++i) {
				removeIndex = removeLater[i];
				domArray.splice(removeIndex - correction, 1);
				++correction;
			}
		},

		/**
		 * recursive method to parallelly walk through two dom subtrees, leave elements before startContainer in first subtree and move rest to other
		 * @param selectionTree tree to iterate over as contained in rangeObject. must be passed separately to allow recursion in the selection tree, but not in the rangeObject
		 * @param rangeObject Aloha.Selection.SelectionRange of the current selection
		 * @param followUpContainer optional jQuery object; if provided the rangeObject will be split and the second part will be insert inside of this object
		 * @param inBetweenMarkup jQuery object to be inserted between the two split parts. will be either a <br> (if no followUpContainer is passed) OR e.g. a table, which must be inserted between the splitobject AND the follow up
		 * @return void
		 */
		splitRangeObjectHelper: function (selectionTree, rangeObject, followUpContainer, inBetweenMarkup) {
			if (!followUpContainer) {
				Aloha.Log.warn(this, 'no followUpContainer, no inBetweenMarkup, nothing to do...');
			}

			var fillUpElement = this.getFillUpElement(rangeObject.splitObject),
				splitObject = jQuery(rangeObject.splitObject),
				startMoving = false,
				el,
				i,
				completeText,
				jqObj,
				mirrorLevel,
				parent,
				treeLength;

			if (selectionTree.length > 0) {
				mirrorLevel = followUpContainer.contents();

				// if length of mirrorLevel and selectionTree are not equal, the mirrorLevel must be corrected. this happens, when the mirrorLevel contains whitespace textNodes
				if (mirrorLevel.length !== selectionTree.length) {
					this.removeElementContentWhitespaceObj(mirrorLevel);
				}

				for (i = 0, treeLength = selectionTree.length; i < treeLength; ++i) {
					el = selectionTree[i];

					// remove all objects in the mirrorLevel, which are BEFORE the cursor
					// OR if the cursor is at the last position of the last Textnode (causing an empty followUpContainer to be appended)
					if ((el.selection === 'none' && startMoving === false) || (el.domobj && el.domobj.nodeType === 3 && el === selectionTree[(selectionTree.length - 1)] && el.startOffset === el.domobj.data.length)) {
						// iteration is before cursor, leave this part inside the splitObject, remove from followUpContainer
						// however if the object to remove is the last existing textNode within the followUpContainer, insert a BR instead
						// otherwise the followUpContainer is invalid and takes up no vertical space

						if (followUpContainer.textNodes().length > 1 || (el.domobj.nodeType === 1 && el.children.length === 0)) {
							// note: the second part of the if (el.domobj.nodeType === 1 && el.children.length === 0) covers a very special condition,
							// where an empty tag is located right before the cursor when pressing enter. In this case the empty tag would not be
							// removed correctly otherwise
							mirrorLevel.eq(i).remove();

						} else if (GENTICS.Utils.Dom.isSplitObject(followUpContainer[0])) {
							if (fillUpElement) {
								followUpContainer.html(fillUpElement); // for your zoological german knowhow: ephemera = Eintagsfliege
							} else {
								followUpContainer.empty();
							}

						} else {
							followUpContainer.empty();
							followUpContainer.addClass('preparedForRemoval');
						}

						continue;

					} else {
						// split objects, which are AT the cursor Position or directly above
						if (el.selection !== 'none') { // before cursor, leave this part inside the splitObject
							// TODO better check for selection == 'partial' here?
							if (el.domobj && el.domobj.nodeType === 3 && el.startOffset !== undefined) {
								completeText = el.domobj.data;
								if (el.startOffset > 0) { // first check, if there will be some text left in the splitObject
									el.domobj.data = completeText.substr(0, el.startOffset);
								} else if (selectionTree.length > 1) { // if not, check if the splitObject contains more than one node, because then it can be removed. this happens, when ENTER is pressed inside of a textnode, but not at the borders
									jQuery(el.domobj).remove();
								} else { // if the "empty" textnode is the last node left in the splitObject, replace it with a ephemera break
									// if the parent is a blocklevel element, we insert the fillup element
									parent = jQuery(el.domobj).parent();
									if (GENTICS.Utils.Dom.isSplitObject(parent[0])) {
										if (fillUpElement) {
											parent.html(fillUpElement);
										} else {
											parent.empty();
										}

									} else {
										// if the parent is no blocklevel element and would be empty now, we completely remove it
										parent.remove();
									}
								}
								if (completeText.length - el.startOffset > 0) {
									// first check if there is text left to put in the followUpContainer's textnode. this happens, when ENTER is pressed inside of a textnode, but not at the borders
									mirrorLevel[i].data = completeText.substr(el.startOffset, completeText.length);
								} else if (mirrorLevel.length > 1) {
									// if not, check if the followUpContainer contains more than one node, because if yes, the "empty" textnode can be removed
									mirrorLevel.eq((i)).remove();
								} else if (GENTICS.Utils.Dom.isBlockLevelElement(followUpContainer[0])) {
									// if the "empty" textnode is the last node left in the followUpContainer (which is a blocklevel element), replace it with a ephemera break
									if (fillUpElement) {
										followUpContainer.html(fillUpElement);
									} else {
										followUpContainer.empty();
									}

								} else {
									// if the "empty" textnode is the last node left in a non-blocklevel element, mark it for removal
									followUpContainer.empty();
									followUpContainer.addClass('preparedForRemoval');
								}
							}

							startMoving = true;

							if (el.children.length > 0) {
								this.splitRangeObjectHelper(el.children, rangeObject, mirrorLevel.eq(i), inBetweenMarkup);
							}

						} else {
							// remove all objects in the origin, which are AFTER the cursor
							if (el.selection === 'none' && startMoving === true) {
								// iteration is after cursor, remove from splitObject and leave this part inside the followUpContainer
								jqObj = jQuery(el.domobj).remove();
							}
						}
					}
				}
			} else {
				Aloha.Log.error(this, 'can not split splitObject due to an empty selection tree');
			}

			// and finally cleanup: remove all fillUps > 1
			splitObject.find('br.aloha-ephemera:gt(0)').remove(); // remove all elements greater than (gt) 0, that also means: leave one
			followUpContainer.find('br.aloha-ephemera:gt(0)').remove(); // remove all elements greater than (gt) 0, that also means: leave one

			// remove objects prepared for removal
			splitObject.find('.preparedForRemoval').remove();
			followUpContainer.find('.preparedForRemoval').remove();

			// if splitObject / followUp are empty, place a fillUp inside
			if (splitObject.contents().length === 0 && GENTICS.Utils.Dom.isSplitObject(splitObject[0]) && fillUpElement) {
				splitObject.html(fillUpElement);
			}

			if (followUpContainer.contents().length === 0 && GENTICS.Utils.Dom.isSplitObject(followUpContainer[0]) && fillUpElement) {
				followUpContainer.html(fillUpElement);
			}
		},

		/**
		 * returns a jQuery object fitting the passed splitObject as follow up object
		 * examples,
		 * - when passed a p it will return an empty p (clone of the passed p)
		 * - when passed an h1, it will return either an h1 (clone of the passed one) or a new p (if the collapsed selection was at the end)
		 * @param rangeObject Aloha.RangeObject
		 * @return void
		 */
		getSplitFollowUpContainer: function (rangeObject) {
			var tagName = rangeObject.splitObject.nodeName.toLowerCase(),
				returnObj,
				inside,
				lastObj;

			switch (tagName) {
			case 'h1':
			case 'h2':
			case 'h3':
			case 'h4':
			case 'h5':
			case 'h6':
				// get the last textnode in the splitobject, but don't consider aloha-cleanme elements
				lastObj = jQuery(rangeObject.splitObject).textNodes(':not(.aloha-cleanme)').last()[0];
				// special case: when enter is hit at the end of a heading, the followUp should be a <p>
				if (lastObj && rangeObject.startContainer === lastObj && rangeObject.startOffset === lastObj.length) {
					returnObj = jQuery('<p></p>');
					inside = jQuery(rangeObject.splitObject.outerHTML).contents();
					returnObj.append(inside);
					return returnObj;
				}
				break;

			case 'li':
				// TODO check whether the li is the last one
				// special case: if enter is hit twice inside a list, the next item should be a <p> (and inserted outside the list)
				if (rangeObject.startContainer.nodeName.toLowerCase() === 'br' && jQuery(rangeObject.startContainer).hasClass('aloha-ephemera')) {
					returnObj = jQuery('<p></p>');
					inside = jQuery(rangeObject.splitObject.outerHTML).contents();
					returnObj.append(inside);
					return returnObj;
				}
				// when the li is the last one and empty, we also just return a <p>
				if (!rangeObject.splitObject.nextSibling && jQuery.trim(jQuery(rangeObject.splitObject).text()).length === 0) {
					returnObj = jQuery('<p></p>');
					return returnObj;
				}
				break;
			}

			return jQuery(rangeObject.splitObject.outerHTML);
		},

		/**
		 * Transform the given domobj into an object with the given new nodeName.
		 * Preserves the content and all attributes. If a range object is given, also the range will be preserved
		 * @param domobj dom object to transform
		 * @param nodeName new node name
		 * @param range range object
		 * @api
		 * @return new object as jQuery object
		 */
		transformDomObject: function (domobj, nodeName, range) {
			// first create the new element
			var jqOldObj = jQuery(domobj),
				jqNewObj = jQuery('<' + nodeName + '>'),
				i,
				attributes = jqOldObj[0].cloneNode(false).attributes;

			// TODO what about events?
			// copy attributes
			if (attributes) {
				for (i = 0; i < attributes.length; ++i) {
					if (typeof attributes[i].specified === 'undefined' || attributes[i].specified) {
						jqNewObj.attr(attributes[i].nodeName, attributes[i].nodeValue);
					}
				}
			}

			// copy inline CSS
			if (jqOldObj[0].style && jqOldObj[0].style.cssText) {
				jqNewObj[0].style.cssText = jqOldObj[0].style.cssText;
			}

			// now move the contents of the old dom object into the new dom object
			jqOldObj.contents().appendTo(jqNewObj);

			// finally replace the old object with the new one
			jqOldObj.replaceWith(jqNewObj);

			// preserve the range
			if (range) {
				if (range.startContainer == domobj) {
					range.startContainer = jqNewObj.get(0);
				}

				if (range.endContainer == domobj) {
					range.endContainer = jqNewObj.get(0);
				}
			}

			return jqNewObj;
		},

		/**
		 * String representation
		 * @return {String}
		 */
		toString: function () {
			return 'Aloha.Markup';
		}

	});

	Aloha.Markup = new Aloha.Markup();
	return Aloha.Markup;
});

/* observable.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/observable',[
	'jquery'
], function (
	jQuery
) {
	

	var $ = jQuery;

	return {
		_eventHandlers: null,

		/**
		 * Attach a handler to an event
		 *
		 * @param {String} eventType A string containing the event name to bind to
		 * @param {Function} handler A function to execute each time the event is triggered
		 * @param {Object} scope Optional. Set the scope in which handler is executed
		 */
		bind: function (eventType, handler, scope) {
			this._eventHandlers = this._eventHandlers || {};
			if (!this._eventHandlers[eventType]) {
				this._eventHandlers[eventType] = [];
			}
			this._eventHandlers[eventType].push({
				handler: handler,
				scope: (scope || window)
			});
		},

		/**
		 * Remove a previously-attached event handler
		 *
		 * @param {String} eventType A string containing the event name to unbind
		 * @param {Function} handler The function that is to be no longer executed. Optional. If not given, unregisters all functions for the given event.
		 */
		unbind: function (eventType, handler) {
			this._eventHandlers = this._eventHandlers || {};
			if (!this._eventHandlers[eventType]) {
				return;
			}
			if (!handler) {
				// No handler function given, unbind all event handlers for the eventType
				this._eventHandlers[eventType] = [];
			} else {
				this._eventHandlers[eventType] = $.grep(this._eventHandlers[eventType], function (element) {
					if (element.handler === handler) {
						return false;
					}
					return true;
				});
			}
		},

		/**
		 * Execute all handlers attached to the given event type.
		 * All arguments except the eventType are directly passed to the callback function.
		 *
		 * @param (String} eventType A string containing the event name for which the event handlers should be invoked.
		 */
		trigger: function (eventType) {
			this._eventHandlers = this._eventHandlers || {};
			if (!this._eventHandlers[eventType]) {
				return;
			}

			// preparedArguments contains all arguments except the first one.
			var preparedArguments = [];
			$.each(arguments, function (i, argument) {
				if (i > 0) {
					preparedArguments.push(argument);
				}
			});

			$.each(this._eventHandlers[eventType], function (index, element) {
				element.handler.apply(element.scope, preparedArguments);
			});
		},

		/**
		 * Clears all event handlers. Call this method when cleaning up.
		 */
		unbindAll: function () {
			this._eventHandlers = null;
		}
	};
});

/* registry.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/*global define:true */
/**
 * Registry base class.
 * TODO: document that it also contains Observable.
 *
 */
define('aloha/registry',[
	'jquery',
	'aloha/observable',
	'util/class'
], function (
	jQuery,
	Observable,
	Class
) {
	

	return Class.extend(Observable, {

		/**
		 * Object containing the registered entries by key.
		 */
		_entries: null,

		/**
		 * Array containing the registered ids in order
		 * of registry
		 */
		_ids: null,

		_constructor: function () {
			this._entries = {};
			this._ids = [];
		},

		/**
		 * Register an entry with an id
		 * 
		 * @event register
		 * @param id id of the registered entry
		 * @param entry registered entry
		 */
		register: function (id, entry) {
			// TODO check whether an entry with the id is already registered
			this._entries[id] = entry;
			this._ids.push(id);
			this.trigger('register', entry, id);
		},

		/**
		 * Unregister the entry with given id
		 * 
		 * @event unregister
		 * @param id id of the registered entry
		 */
		unregister: function (id) {
			// TODO check whether an entry was registered
			var i, oldEntry = this._entries[id];
			delete this._entries[id];
			for (i in this._ids) {
				if (this._ids.hasOwnProperty(i) && this._ids[i] === id) {
					this._ids.splice(i, 1);
					break;
				}
			}
			this.trigger('unregister', oldEntry, id);
		},

		/**
		 * Get the entry registered with the given id
		 * 
		 * @param id id of the registered entry
		 * @return registered entry
		 */
		get: function (id) {
			return this._entries[id];
		},

		/**
		 * Check whether an entry was registered with given id
		 * 
		 * @param id id to check
		 * @return true if an entry was registered, false if not
		 */
		has: function (id) {
			return (this._entries[id] ? true : false);
		},

		/**
		 * Get an object mapping the ids (properties) to the registered entries
		 * Note, that iterating over the properties of the returned object
		 * will return the entries in an unspecified order
		 * 
		 * @return object containing the registered entries
		 */
		getEntries: function () {
			// clone the entries so the user does not accidentally modify our _entries object.
			return jQuery.extend({}, this._entries);
		},

		/**
		 * Get the ids of the registered objects as array.
		 * The array will contain the ids in order of registry
		 * 
		 * @return array if registered ids
		 */
		getIds: function () {
			return jQuery.extend([], this._ids);
		}
	});
});

/* contenthandlermanager.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/contenthandlermanager',[
	'aloha/core',
	'aloha/registry',
	'util/class'
], function (
	Aloha,
	Registry,
	Class
) {
	

	var ContentHandlerManager = Registry.extend({

		/**
		 * Create a contentHandler from the given definition.  Acts as a factory
		 * method for contentHandler.
		 *
		 * @param {object} definition
		 */
		createHandler: function (definition) {
			if (typeof definition.handleContent !== 'function') {
				throw 'ContentHandler has no function handleContent().';
			}
			var AbstractContentHandler = Class.extend({
				handleContent: function (content) {
					// Implement in subclass!
				}
			}, definition);
			return new AbstractContentHandler();
		},

		/**
		 * Manipulates the given contents of an editable by invoking content
		 * handlers over it.
		 *
		 * @param {string} content The content of an editable which will be
		 *                         handled.
		 * @param {object} options Used to filter limit which content handlers
		 *                         should be used.
		 * @param {Aloha.Editable} The editable whose content is being handled.
		 * @return {string} The handled content.
		 */
		handleContent: function (content, options, editable) {
			var manager = this;

			// Because if no options are specified, to indicate which content
			// handler to use, then all that are available are used.
			var handlers = options ? options.contenthandler : manager.getIds();

			if (!handlers) {
				return content;
			}

			var i;
			var handler;
			for (i = 0; i < handlers.length; i++) {
				handler = manager.get(handlers[i]);
				if (handler) {
					content = handler.handleContent(
						content,
						options,
						editable || Aloha.activeEditable
					);
				}

				// FIXME: Is it ever valid for content to be null?  This would
				//        break the handleContent(string):string contract.
				if (null === content) {
					break;
				}
			}

			return content;
		}
	});

	return new ContentHandlerManager();
});

/* trees.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Tree walking functions.
 *
 * prewalk(form, fn, inplace)
 *
 *     Descend into the given form, which is a tree of arrays andmaps
 *     (javascript Object), and build a new tree with the result of
 *     applying the given fn to each branch and leaf. Only arrays and
 *     maps are descended into, everything else is considered a leaf.
 *
 *     The given fn is applied as the tree is descended into - the
 *     function application (pre)cedes descending into the tree.
 * 
 *     By default, an entirely new structure is returned. If the
 *     optional inplace argument is true, the algorithm will not
 *     allocate any new structures, but modify the given form in-place.
 *     The benefit of this is more performance due to less allocation,
 *     and reduced memory overhead, but see the "Note" below.
 *
 * postwalk(form, fn, inplace)
 *
 *     the same as prewalk, except the given fn is applied as the tree
 *     is ascended.
 *
 * preprune(form, pred, inplace)
 *
 *     the same as prewalk, except pred is a predicate function and any
 *     branch or leaf that is encountered and for which pred returns
 *     true is removed from the tree.
 *
 * postprune(form, pred, inplace)
 *
 *     the same as preprune, except the predicate function is applied as
 *     the tree is ascended.
 *
 *     Postpruning is potentially slower than prepruning since it always
 *     descendes into the whole tree, even into pruned nodes, while
 *     prepruning skips any pruned nodes.
 *
 * leaves(form, leaf, inplace)
 *
 *     Like postwalk, except the leaf function is applied only to
 *     leaves, and not to the arrays or maps that make up the tree
 *     structure of form.
 *
 *     Useful when one is only interested in tranforming leaves.
 *
 * flatten(form)
 *
 *     Makes an array of all of the given form's leaves.
 *
 * clone(form)
 *
 *     Constructs a deep clone of the given form.
 *
 * prepruneDom(form, pred, inplace)
 *
 *     Like preprune() except:
 *
 *     - the given form may be either an element or other DOM node, and
 *       only elements are descended into, all other node types are
 *       considered leaves.
 *
 *     - the given form will be cloned before it is being traversed, unless
 *       inplace is true.
 *
 *       This is different from prewalk, where the subforms that are
 *       passed to fn are not clones. Making a deep clone first
 *       simplifies some things, basically because an array or map can
 *       be the child of multiple arrays and maps at the same time,
 *       while a node can only be the child of a single parent node at
 *       any one time.
 *   
 * postpruneDom(form, pred, inplace)
 *
 *     Like prepruneDom(), except the given function is applied as the tree
 *     is ascended.
 *
 * walk(form, recurse, inplace)
 *
 *     If form is an array or map, calls recurse on each of its items.
 *     If inplace is true, modifies the form and sets each item to the
 *     result of the call to recurse. If inplace is false, creates a new
 *     array/map containing the results of calling recurse. Returns
 *     either form if inplace is true, or the newly created array/map.
 *
 *     If form is not an array or map, it is simply returned.
 *
 *     An example using walk() in a custom recursive traversal function:
 * 
 *     function doSomething(root) {
 *         function step(form) {
 *             form = Trees.walk(form, step);
 *             // do something with form
 *             return form ? [form] : [];
 *         }
 *         return step(root)[0] || null;
 *     }
 *
 * walk(form, recurse)
 *
 *     Short for walk(form, recurse, true)
 *
 * walkDomInplace(form, recurse)
 *
 *     Similar to walk() but operates on DOM nodes.
 *
 *     Elements are considered non-leaf, and everything else is
 *     considerd a leaf.
 *
 * Note: All functions work on array+map trees, unless they are suffixed
 *       with Dom, in which case they only work on DOM nodes.
 *
 * Note: When walking arrays and maps, if the fn and leaf functions
 *       modify the parent or any ancestor of the passed form, the
 *       resulting behaviour is undefined. Only modification of the
 *       passed form and descendants of the passed form is valid.
 *
 *       During DOM traversal, it is allowed to insert-into/remove-from
 *       the children of the parent of the given form, as long the given
 *       form itself is not removed.
 *
 * Note: the algorithms are recursive and the maximum nesting level of
 *       the input set is therefore bound to the maximum stack depth.
 *       IE7 and IE8 for example have a maximum stack depth of greater
 *       than 1000, so the maximum input nesting level should not exceed
 *       about 300 (3 stack frames are needed per nesting level).
 */
define('util/trees',['jquery'], function ($) {
	

	function walk(form, step, inplace) {
		var type = $.type(form),
			subResult,
			result,
			resultOff,
			len,
			i,
			key;
		if ('array' === type) {
			result = (inplace ? form : []);
			resultOff = 0;
			for (i = 0, len = form.length; i < len; i++) {
				subResult = step(form[i]);
				if (subResult.length) {
					result[resultOff++] = subResult[0];
				}
			}
			if (resultOff !== result.length) {
				// TODO is result.length = resultOff better?
				result = result.slice(0, resultOff);
			}
		} else if ('object' === type) {
			result = (inplace ? form : {});
			for (key in form) {
				if (form.hasOwnProperty(key)) {
					subResult = step(form[key]);
					if (subResult.length) {
						result[key] = subResult[0];
					} else {
						delete result[key];
					}
				}
			}
		} else {
			result = form;
		}
		return result;
	}

	function walkInplace(form, step) {
		return walk(form, step, true);
	}

	function walkDomInplace(form, step) {
		var subResult,
		    child,
		    nextChild;
		if (1 === form.nodeType) {
			child = form.firstChild;
			while (child) {
				subResult = step(child);
				// Advance to the next child _after stepping into child_
				// to pick up modifications of the DOM.
				nextChild = child.nextSibling;
				if (subResult.length) {
					if (subResult[0] !== child) {
						form.replaceChild(subResult[0], child);
					}
				} else {
					form.removeChild(child);
				}
				child = nextChild;
			}
		}
		return form;
	}

	function prewalkStep(step, fn, walk, form) {
		return [walk(fn(form), step)];
	}

	function postwalkStep(step, fn, walk, form) {
		return [fn(walk(form, step))];
	}

	function prepruneStep(step, fn, walk, form) {
		return fn(form) ? [] : [walk(form, step)];
	}

	function postpruneStep(step, fn, walk, form) {
		var subForm = walk(form, step);
		return fn(subForm) ? [] : [subForm];
	}

	function prepost(step, fnOrPred, walk, form) {
		function prepostStep(form) {
			return step(prepostStep, fnOrPred, walk, form);
		}
		return prepostStep(form)[0];
	}

	function prewalk(form, fn, inplace) {
		return prepost(prewalkStep, fn, inplace ? walkInplace : walk, form);
	}

	function postwalk(form, fn, inplace) {
		return prepost(postwalkStep, fn, inplace ? walkInplace : walk, form);
	}

	function preprune(form, pred, inplace) {
		return prepost(prepruneStep, pred, inplace ? walkInplace : walk, form);
	}

	function postprune(form, pred, inplace) {
		return prepost(postpruneStep, pred, inplace ? walkInplace : walk, form);
	}

	function prewalkDom(form, fn, inplace) {
		return prepost(prewalkStep, fn, walkDomInplace, inplace ? form : form.cloneNode(true));
	}

	function postwalkDom(form, fn, inplace) {
		return prepost(postwalkStep, fn, walkDomInplace, inplace ? form : form.cloneNode(true));
	}

	function prepruneDom(form, pred, inplace) {
		return prepost(prepruneStep, pred, walkDomInplace, inplace ? form : form.cloneNode(true));
	}

	function postpruneDom(form, pred, inplace) {
		return prepost(postpruneStep, pred, walkDomInplace, inplace ? form : form.cloneNode(true));
	}

	function isLeaf(form) {
		var type = $.type(form);
		return type !== 'object' && type !== 'array';
	}

	function identityStep(step, walk, form) {
		return [walk(form, step)];
	}

	function leaves(form, leaf, inplace) {
		var leafWalk = inplace ? walkInplace : walk;

		function leafStep(form) {
			if (isLeaf(form)) {
				return [leaf(form)];
			}
			return identityStep(leafStep, leafWalk, form);
		}
		return leafStep(form)[0];
	}

	function clone(form) {
		function cloneStep(form) {
			return identityStep(cloneStep, walk, form);
		}
		return cloneStep(form)[0];
	}

	function flatten(form) {
		var inplace = true;
		var result = [];
		leaves(form, function (leaf) {
			result.push(leaf);
			return leaf;
		}, inplace);
		return result;
	}

	return {
		prewalk: prewalk,
		postwalk: postwalk,
		preprune: preprune,
		postprune: postprune,
		prewalkDom: prewalkDom,
		postwalkDom: postwalkDom,
		prepruneDom: prepruneDom,
		postpruneDom: postpruneDom,
		isLeaf: isLeaf,
		leaves: leaves,
		clone: clone,
		flatten: flatten,
		walk: walk,
		walkInplace: walkInplace,
		walkDomInplace: walkDomInplace
	};
});

/* misc.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Contains miscellaneous utility functions that don't fit anywhere else.
 */
define('util/misc',[
	'jquery',
	'aloha/engine',
	'util/dom'
], function (
	jQuery,
	Engine,
	Dom
) {
	

	/**
	 * Returns true if any regex in the given rxs array tests true
	 * against str.
	 */
	function anyRx(rxs, str) {
		var i,
		    len;
		for (i = 0, len = rxs.length; i < len; i++) {
			if (rxs[i].test(str)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Add editing helpers, if necessary
	 *
	 * @param {jQuery} $field object where to add the editing helpers
	 */
	function addEditingHelpers($field) {
		// check whether the editable contains a non-editable block
		// element. as first or last element (not counting whitespace
		// nodes) if so, we need to add br's at the end and/or start so
		// that the user could enter text. Note that inline blocks
		// will be padded by the block plugin, so we ignore them here.
		var firstChild = Dom.getFirstVisibleChild($field[0], false, ["TABLE"]);
		var $firstChild = jQuery(firstChild);

		if (Dom.isBlockNode(firstChild)
				&& (!$firstChild.contentEditable() || firstChild.nodeName === 'TABLE')) {
			var startP = jQuery('<p class="aloha-editing-p"></p>');

			$field.prepend(startP);

			if (0 === Dom.getOffsetHeight(startP[0])) {
				startP.append(jQuery('<br class="aloha-end-br"/>'));
			}
		}

		var lastChild = Dom.getLastVisibleChild($field[0], false, ["TABLE"]);
		var $lastChild = jQuery(lastChild);

		if (Dom.isBlockNode(lastChild)
				&& (!jQuery(lastChild).contentEditable() || lastChild.nodeName === 'TABLE')) {
			var endP = jQuery('<p class="aloha-editing-p"></p>');

			$field.append(endP);
			if (0 === Dom.getOffsetHeight(endP[0])) {
				endP.append(jQuery('<br class="aloha-end-br"/>'));
			}
		}
	}

	/**
	 * Removes helper-breaks in paragraphs inserted to be able to target editables
	 * with uneditable block-elements, and either removes the paragraphs (if empty)
	 * or their editing attribute (otherwise)
	 *
	 * @param {jQuery} $field specifies the area in which to look for editing helpers
	 */
	function removeEditingHelpers($field) {
		// remove the editing br's
		$field.find('p.aloha-editing-p > br.aloha-end-br').remove();

		$field.find('p.aloha-editing-p').each(function (index, elem) {
			if (Dom.isEmpty(elem) &&
				// blocks may remain empty until the user fills them with content
					!jQuery(elem).find('.aloha-block').length) {
				jQuery(elem).remove();
			} else {
				// if the p shall remain, we remove the class aloha-editing-p
				jQuery(elem).removeClass('aloha-editing-p');
				// use the Engine to add an end br, if necessary
				Engine.ensureContainerEditable(elem);
			}
		});
	}


	return {
		anyRx: anyRx,
		addEditingHelpers: addEditingHelpers,
		removeEditingHelpers: removeEditingHelpers
	};
});

/* ephemera.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Provides functions to mark the contents of editables as ephemeral. An
 * editable's ephemeral content will be pruned before it is being
 * returned by editable.getContents().
 * 
 * It is planned to replace most instances of makeClean() with this
 * implementation for improved performance and more importantly, in
 * order to have a centralized place that has the control over all
 * ephemeral content, which can be leveraged by plugins to provide more
 * advanced functionality.
 *
 * Some examples that would be possible:
 * * a HTML source code text box, an interactive tree structure, or
 *   other kind of DOM visualization, next to the editable, that
 *   contains just the content of the editable (without ephemeral data)
 *   and which is updated efficiently in real time after each keystroke.
 *
 * * change detection algorithms that are able to intelligently ignore
 *   ephemeral data and which would not trigger unless non-ephemeral
 *   data is added to the editable.
 *
 * * When a plugin provides very general functionality over all nodes of
 *   the DOM, somtimes the plugin may not know what is and what isn't
 *   supposed to be real content. The functionality provided here makes
 *   it possible for the plugin to exaclty distinguish real content from
 *   ephemeral content.
 *
 * TODO: currently only simple transformations are suppored, like
 *       marking classes, attributes and elements as ephemeral and removing
 *       them during the pruning process.
 *       In the future, support for the block-plugin and custom pruning
 *       functions should be added. This may be done by letting implementations
 *       completely control the pruning of a DOM element through a
 *       function that takes the content+ephemeral-data and returns only
 *       content - similar to make clean, but for single elements to reduce
 *       overhead.
 */
define('aloha/ephemera',[
	'jquery',
	'aloha/core',
	'aloha/console',
	'util/strings',
	'util/trees',
	'util/arrays',
	'util/maps',
	'util/dom2',
	'util/functions',
	'util/html',
	'util/misc',
	'util/browser',
	'PubSub'
], function (
	$,
	Aloha,
	console,
	Strings,
	Trees,
	Arrays,
	Maps,
	Dom,
	Functions,
	Html,
	Misc,
	Browser,
	PubSub
) {
	

	var ephemeraMap = {
		classMap: {
			'aloha-ephemera-wrapper': true,
			'aloha-ephemera-filler': true,
			'aloha-ephemera-attr': true,
			'aloha-ephemera': true,
			// aloha-cleanme is the same as aloha-ephemera.
			// TODO: should be replaced with aloha-ephemera throughout
			//       the codebase and removed here.
			'aloha-cleanme': true
		},
		attrMap: {
			'hidefocus': true,
			'hideFocus': true,
			'tabindex': true,
			'tabIndex': true,
			'contenteditable': ['TABLE'],
			'contentEditable': ['TABLE']
		},
		attrRxs: [/^(?:nodeIndex|sizcache|sizset|jquery)[\w\d]*$/i],
		pruneFns: []
	};

	var commonClsSubstr = 'aloha-';

	/**
	 * Checks whether the given classes contain the substring common to
	 * all ephemeral classes. If the check fails, an warning will be
	 * logged and the substring will be set to the empty string which
	 * voids the performance improvement the common substring would
	 * otherwise have gained.
	 */
	function checkCommonSubstr(clss) {
		var i, len;
		for (i = 0, len = clss.length; i < len; i++) {
			if (-1 === clss[i].indexOf(commonClsSubstr)) {
				console.warn('Class "' + clss[i] + '" was set to be ephemeral,' + 'which hurts peformance.' + ' Add the common substring "' + commonClsSubstr + '" to the class to fix this problem.');
				commonClsSubstr = '';
			}
		}
	}

	/**
	 * Registers ephemeral classes.
	 *
	 * An ephemeral class is a non-content class that will be pruned
	 * from the from the result of editable.getContents().
	 *
	 * The given classes should contain the string 'aloha-' to get the
	 * benefit of a performance optimization.
	 *
	 * Returns a map that contains all classes that were ever registered
	 * with this function.
	 *
	 * Multiple classes may be specified. If none are specified, just
	 * returns the current ephemeral classes map without modifying it.
	 *
	 * Also see ephemera().
	 */
	function classes() {
		var clss = Array.prototype.slice.call(arguments);
		Maps.fillKeys(ephemeraMap.classMap, clss, true);
		checkCommonSubstr(clss);
		PubSub.pub('aloha.ephemera.classes', {
			ephemera: ephemeraMap,
			newClasses: clss
		});
	}

	/**
	 * Registers ephemeral attributes by attribute name.
	 *
	 * Similar to classes() except applies to entire attributes instead
	 * of individual classes in the class attribute.
	 */
	function attributes() {
		var attrs = Array.prototype.slice.call(arguments);
		Maps.fillKeys(ephemeraMap.attrMap, attrs, true);
		PubSub.pub('aloha.ephemera.attributes', {
			ephemera: ephemeraMap,
			newAttributes: attrs
		});
	}

	/**
	 * Provides access to the global ephemera registry.
	 *
	 * If the given argument is not null, sets the global ephemera
	 * registry to the given value and returns it. Otherwise, just
	 * returns the global registry.
	 *
	 * The given/returned value has the following properties:
	 *
	 * The given map may have the following entries
	 *
	 * classMap - a map from class name to the value true.
	 *            all classes must have a "aloha-" prefix.
	 *            Use Ehpemera.attributes() to set classes without "aloha-" prefix.
	 *
	 * attrMap  - a map from attribute name to the value true or to an array
	 *            of element names. If an array of elements is specified, the
	 *            attribute will only be considered ephemeral if it is
	 *            found on an element in the array.
	 *
	 * attrRxs  - an array of regexes (in object - not string - form: /[a-z].../)
	 *
	 * pruneFns - an array of functions that will be called at each pruning step.
	 *
	 * When a DOM tree is pruned with prune(elem) without an emap
	 * argument, the global registry maintained with classes()
	 * attributes() and ephemera() is used as a default map. If an emap
	 * argument is specified, the global registry will be ignored and
	 * the emap argument will be used instead.
	 *
	 * When a DOM tree is pruned with prune()
	 * - classes specified by classMap will be removed
	 * - attributes specified by attrMap or attrRxs will be removed
	 * - functions specified by pruneFns will be called as the DOM tree
	 *   is descended into (pre-order), with each node (element, text,
	 *   etc.) as a single argument. The function is free to modify the
	 *   element and return it, or return a new element which will
	 *   replace the given element in the pruned tree. If null or
	 *   undefined is returned, the element will be removed from the
	 *   tree. As per contract of Maps.walkDomInplace, it is allowed to
	 *   insert/remove children in the parent node as long as the given
	 *   node is not removed.
	 *
	 * Also see classes() and attributes().
	 *
	 * Note that removal of attributes doesn't always work on IE7 (in
	 * rare special cases). The dom-to-xhtml plugin can reliably remove
	 * ephemeral attributes during the serialization step.
	 */
	function ephemera(emap) {
		if (emap) {
			ephemeraMap = emap;
		}
		PubSub.pub('aloha.ephemera', {
			ephemera: ephemeraMap
		});
		return ephemeraMap;
	}

	/**
	 * Marks an element as ephemeral.
	 *
	 * The element will be completely removed when the prune function is
	 * called on it.
	 *
	 * Adds the class 'aloha-ephemera' to the given element.
	 *
	 * The class 'aloha-ephemera' can also be added directly without
	 * recurse to this function, if that is more convenient.
	 */
	function markElement(elem) {
		$(elem).addClass('aloha-ephemera');
	}

	/**
	 * Marks the attribute of an element as ephemeral.
	 *
	 * The attribute will be removed from the element when the prune
	 * function is called on it.
	 *
	 * Multiple attributes can be passed at the same time be separating
	 * them with a space.
	 *
	 * Adds the class 'aloha-ephemera-attr' to the given element. Also
	 * adds or modifies the 'data-aloha-ephemera-attr' attribute,
	 * and adds to it the name of the given attribute.
	 *
	 * These modifications can be made directly without recurse to this
	 * function, if that is more convenient.
	 */
	function markAttr(elem, attr) {
		elem = $(elem);
		var data = elem.attr('data-aloha-ephemera-attr');
		if (null == data || '' === data) {
			data = attr;
		} else if (-1 === Arrays.indexOf(Strings.words(data), attr)) {
			data += ' ' + attr;
		}
		elem.attr('data-aloha-ephemera-attr', data);
		elem.addClass('aloha-ephemera-attr');
	}

	/**
	 * Marks an element as a ephemeral, excluding subnodes.
	 *
	 * The element will be removed when the prune function is called on
	 * it, but any children of the wrapper element will remain in its
	 * place.
	 *
	 * A wrapper is an element that wraps a single non-ephemeral
	 * element. A filler is an element that is wrapped by a single
	 * non-ephemeral element. This distinction is not important for the
	 * prune function, which behave the same for both wrappers and
	 * fillers, but it makes it easier to build more advanced content
	 * inspection algorithms (also see note at the header of ephemeral.js).
	 * 
	 * Adds the class 'aloha-ephemera-wrapper' to the given element.
	 *
	 * The class 'aloha-ephemera-wrapper' may also be added directly,
	 * without recurse to this function, if that is more convenient.
	 *
	 * NB: a wrapper element must not wrap a filler element. Wrappers
	 *     and fillers are ephermeral. A wrapper must always wrap a
	 *     single _non-ephemeral_ element, and a filler must always fill
	 *     a single _non-ephemeral_ element.
	 */
	function markWrapper(elem) {
		$(elem).addClass('aloha-ephemera-wrapper');
	}

	/**
	 * Marks an element as a ephemeral. If all subnodes are White Spaces,
	 * the elements would be removed completed. Otherwise only the wrapper
	 * will be removed, without deleting the subnodes. 
	 */
	function markWhiteSpaceWrapper(elem) {
		$(elem).addClass('aloha-ephemera-empty-wrapper');
	}

	/**
	 * Marks an element as ephemeral, excluding subnodes.
	 *
	 * Adds the class 'aloha-ephemera-filler' to the given element.
	 *
	 * The class 'aloha-ephemera-filler' may also be added directly,
	 * without recurse to this function, if that is more convenient.
	 *
	 * See wrapper()
	 */
	function markFiller(elem) {
		$(elem).addClass('aloha-ephemera-filler');
	}

	/**
	 * Prunes attributes marked as ephemeral with Ephemera.attributes()
	 * from the given element.
	 */
	function pruneMarkedAttrs(elem) {
		var $elem = $(elem);
		var data = $elem.attr('data-aloha-ephemera-attr');
		var i;
		var attrs;
		// Because IE7 crashes if we remove this attribute. If the
		// dom-to-xhtml plugin is turned on, it will handle the removal
		// of this attribute during serialization.
		if (!Browser.ie7) {
			$elem.removeAttr('data-aloha-ephemera-attr');
		}
		if (typeof data === 'string') {
			attrs = Strings.words(data);
			for (i = 0; i < attrs.length; i++) {
				$elem.removeAttr(attrs[i]);
			}
		}
	}

	/**
	 * Determines whether the given attribute of the given element is
	 * ephemeral according to the given emap.
	 * See Ephemera.ephemera() for an explanation of attrMap and attrRxs.
	 */
	function isAttrEphemeral(elem, attrName, attrMap, attrRxs) {
		var mapped = attrMap[attrName];
		if (mapped) {
			// The attrMap may either contain boolean true or an array of element names.
			if (true === mapped) {
				return true;
			}
			if (-1 !== Arrays.indexOf(mapped, elem.nodeName)) {
				return true;
			}
		}
		return Misc.anyRx(attrRxs, attrName);
	}

	/**
	 * Prunes attributes specified with either emap.attrMap or emap.attrRxs.
	 * See ephemera().
	 */
	function pruneEmapAttrs(elem, emap) {
		var $elem = null,
			attrs = Dom.attrNames(elem),
		    name,
		    i,
		    len;
		for (i = 0, len = attrs.length; i < len; i++) {
			name = attrs[i];
			if (isAttrEphemeral(elem, name, emap.attrMap, emap.attrRxs)) {
				$elem = $elem || $(elem);
				$elem.removeAttr(name);
			}
		}
	}

	/**
	 * Prunes an element of attributes and classes or removes the
	 * element by returning false.
	 *
	 * Elements attributes and classes can either be marked as
	 * ephemeral, in which case the element itself will contain the
	 * prune-info, or they can be specified as ephemeral with the given
	 * emap.
	 *
	 * See ephemera() for an explanation of the emap argument.
	 */
	function pruneElem(elem, emap) {
		var className = elem.className;
		// Because SVG elements will (sometimes) hold a SVGAnimatedString object
		// (http://mdn.beonex.com/en/DOM/SVGStylable.html#Properties) instead of
		// a string for the className property
		if ('string' === typeof className && -1 !== className.indexOf(commonClsSubstr)) {
			var classes = Strings.words(className);

			// Ephemera.markElement()
			if (-1 !== Arrays.indexOf(classes, 'aloha-cleanme') || -1 !== Arrays.indexOf(classes, 'aloha-ephemera')) {
				$.removeData(elem); // avoids memory leak
				return false; // removes the element
			}

			// Ephemera.markWrapper() and Ephemera.markFiller()
			if (-1 !== Arrays.indexOf(classes, 'aloha-ephemera-wrapper') || -1 !== Arrays.indexOf(classes, 'aloha-ephemera-filler')) {
				Dom.moveNextAll(elem.parentNode, elem.firstChild, elem.nextSibling);
				$.removeData(elem);
				return false;
			}

			// Ephemera.markWhiteSpaceWrapper() and Ephemera.markFiller()
			if (-1 !== Arrays.indexOf(classes, 'aloha-ephemera-empty-wrapper')) {
				if (!Html.hasOnlyWhiteSpaceChildren(elem)) {
					Dom.moveNextAll(elem.parentNode, elem.firstChild, elem.nextSibling);
				}
				$.removeData(elem);
				return false;
			}

			// Ephemera.markAttr()
			if (-1 !== Arrays.indexOf(classes, 'aloha-ephemera-attr')) {
				pruneMarkedAttrs(elem);
			}

			// Ephemera.classes() and Ehpemera.ephemera({ classMap: {} })
			var persistentClasses = Arrays.filter(classes, function (cls) {
				return !emap.classMap[cls];
			});
			if (persistentClasses.length !== classes.length) {
				if (0 === persistentClasses.length) {
					// Removing the attributes is dangerous. Aloha has a
					// jquery patch in place to fix some issue.
					$(elem).removeAttr('class');
				} else {
					elem.className = persistentClasses.join(' ');
				}
			}
		}

		// Ephemera.attributes() and Ephemera.ephemera({ attrMap: {}, attrRxs: {} })
		pruneEmapAttrs(elem, emap);

		return true;
	}

	/**
	 * Called for each node during the pruning of a DOM tree.
	 */
	function pruneStep(emap, step, node) {
		if (1 === node.nodeType) {
			if (!pruneElem(node, emap)) {
				return [];
			}
			node = Trees.walkDomInplace(node, step);
		}

		// Ephemera.ephemera({ pruneFns: [] })
		node = Arrays.reduce(emap.pruneFns, node, Arrays.applyNotNull);
		if (!node) {
			return [];
		}

		return [node];
	}

	/**
	 * Prunes the given element of all ephemeral data.
	 *
	 * Elements marked with Ephemera.markElement() will be removed.
	 * Attributes marked with Ephemera.markAttr() will be removed.
	 * Elements marked with Ephemera.markWrapper() or
	 * Ephemera.markFiller() will be replaced with their children.
	 *
	 * See ephemera() for an explanation of the emap argument.
	 *
	 * All properties of emap, if specified, are required, but may be
	 * empty.
	 *
	 * The element is modified in-place and returned.
	 */
	function prune(elem, emap) {
		emap = emap || ephemeraMap;

		function pruneStepClosure(node) {
			return pruneStep(emap, pruneStepClosure, node);
		}
		return pruneStepClosure(elem)[0];
	}

	return {
		ephemera: ephemera,
		classes: classes,
		attributes: attributes,
		markElement: markElement,
		markAttr: markAttr,
		markWrapper: markWrapper,
		markWhiteSpaceWrapper: markWhiteSpaceWrapper,
		markFiller: markFiller,
		prune: prune,
		isAttrEphemeral: isAttrEphemeral
	};
});

/* copypaste.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * @overview:
 * Various utility functions that are useful when working with selections, and
 * ranges for copy/paste functionality.
 */
define('aloha/copypaste', [
	'jquery',
	'aloha/core'
], function (
	$,
	Aloha
) {
	

	/**
	 * Retrieve the editable host in which the given range is contained.
	 *
	 * @param {WrappedRange} range
	 * @return {jQuery.<HTMLElement>|null} The editable host element, null if
	 *                                     non can be determinded from the given
	 *                                     range.
	 */
	function getEditableAt(range) {
		if (!range || !range.commonAncestorContainer) {
			return null;
		}
		var $container = $(range.commonAncestorContainer);
		return $container.length ? Aloha.getEditableHost($container) : null;
	}

	/**
	 * Retrieves the current range.
	 *
	 * @return {WrappedRange|null} Range at current selection or null of non
	 *                             exists.
	 */
	function getRange() {
		var selection = Aloha.getSelection();
		return selection.getRangeCount() ? selection.getRangeAt(0) : null;
	}

	/**
	 * Set the selection to the given range
	 *
	 * @param {object} range An object that must container the following
	 *                       essential range properties: ~ startContainer
	 *                                                   ~ endContainer
	 *                                                   ~ startOffset
	 *                                                   ~ endOffset
	 */
	function setSelectionAt(range) {
		var newRange = Aloha.createRange();
		var selection = Aloha.getSelection();
		newRange.setStart(range.startContainer, range.startOffset);
		newRange.setEnd(range.endContainer, range.endOffset);
		selection.removeAllRanges();
		selection.addRange(newRange);
	}

	/**
	 * Creates a selection that encompasses the contents of the given element.
	 *
	 * @param {HTMLElement} element Editable DOM element.
	 */
	function selectAllOf(element) {
		setSelectionAt({
			startContainer: element,
			endContainer: element,
			startOffset: 0,
			endOffset: element.childNodes ? element.childNodes.length
		                                  : element.length
		});
		$(element).focus();
	}

	return {
		getEditableAt: getEditableAt,
		getRange: getRange,
		selectAllOf: selectAllOf,
		setSelectionAt: setSelectionAt
	};
});

/* command.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/command',[
	'aloha/core',
	'aloha/registry',
	'aloha/engine',
	'util/dom',
	'aloha/contenthandlermanager'
], function (
	Aloha,
	Registry,
	Engine,
	Dom,
	ContentHandlerManager
) {
	

	//			Action: What the command does when executed via execCommand(). Every command defined
	//			in CommandManager specification has an action defined for it in the relevant section. For example,
	//			the bold command's action generally makes the current selection bold, or removes bold if
	//			the selection is already bold. An editing toolbar might provide buttons that execute the
	//			action for a command if clicked, or a script might run an action without user interaction
	//			to achieve some particular effect.
	//
	//			Indeterminate: A boolean value returned by queryCommandIndeterm(), depending on the
	//			current state of the document. Generally, a command that has a state defined will be
	//			indeterminate if the state is true for part but not all of the current selection, and a
	//			command that has a value defined will be indeterminate if different parts of the
	//			selection have different values. An editing toolbar might display a button or control
	//			in a special way if the command is indeterminate, like showing a "bold" button as
	//			partially depressed, or leaving a font size selector blank instead of showing the font
	//			size of the current selection. As a rule, a command can only be indeterminate if its
	//			state is false, supposing it has a state.
	//
	//			State: A boolean value returned by queryCommandState(), depending on the current state
	//			of the document. The state of a command is true if it is already in effect, in some
	//			sense specific to the command. Most commands that have a state defined will take opposite
	//			actions depending on whether the state is true or false, such as making the selection
	//			bold if the state is false and removing bold if the state is true. Others will just
	//			have no effect if the state is true, like the justifyCenter command. Still others will
	//			have the same effect regardless, like the styleWithCss command. An editing toolbar might
	//			display a button or control differently depending on the state and indeterminacy of the
	//			command.
	//
	//			Value: A string returned by queryCommandValue(), depending on the current state of the
	//			document. A command usually has a value instead of a state if the property it modifies
	//			can take more than two different values, like the foreColor command. If the command is
	//			indeterminate, its value is generally based on the start of the selection. Otherwise,
	//			in most cases the value holds true for the entire selection, but see the justifyCenter
	//			command and its three companions for an exception. An editing toolbar might display the
	//			value of a command as selected in a drop-down or filled in in a text box, if the command
	//			isn't indeterminate.
	//
	//			Relevant CSS property: CommandManager is defined for certain inline formatting commands, and
	//			is used in algorithms specific to those commands. It is an implementation detail, and
	//			is not exposed to authors. If a command does not have a relevant CSS property
	//			specified, it defaults to null.

	var CommandManager = {

		execCommand: function (commandId, showUi, value, range) {
			var eventData = {
				commandId: commandId,
				preventDefault: false
			};

			Aloha.trigger('aloha-command-will-execute', eventData);

			if (eventData.preventDefault === true) {
				return;
			}

			var selection = Aloha.getSelection();
			if (!range) {
				if (!selection.getRangeCount()) {
					return;
				}
				range = selection.getRangeAt(0);
			}

			if (commandId.toLowerCase() === 'inserthtml') {
				value = ContentHandlerManager.handleContent(value, {
					contenthandler: Aloha.settings.contentHandler.insertHtml,
					command: 'insertHtml'
				});
			}

			Engine.execCommand(commandId, showUi, value, range);

			// Because there is never a situation where it will be necessary to
			// do any further cleanup (merging of similar adjacent nodes)
			if ('insertparagraph' !== commandId.toLowerCase()
					&& selection.getRangeCount()) {

				range = selection.getRangeAt(0);

				// FIX: doCleanup should work with W3C range
				var start = range.commonAncestorContainer;
				if (start.parentNode) {
					start = start.parentNode;
				}

				var rangeObject = new window.GENTICS.Utils.RangeObject();
				rangeObject.startContainer = range.startContainer;
				rangeObject.startOffset = range.startOffset;
				rangeObject.endContainer = range.endContainer;
				rangeObject.endOffset = range.endOffset;

				Dom.doCleanup({
					merge: true,
					removeempty: false
				}, rangeObject, start);

				rangeObject.select();
			}

			Aloha.scrollToSelection();

			Aloha.trigger('aloha-command-executed', commandId);
		},

		// If command is available and not disabled or the active range is not null
		// the command is enabled
		queryCommandEnabled: function (commandId, range) {

			// Take current selection if not passed
			if (!range) {
				if (!Aloha.getSelection().getRangeCount()) {
					return;
				}
				range = Aloha.getSelection().getRangeAt(0);
			}
			return Engine.queryCommandEnabled(commandId, range);
		},

		// "Return true if command is indeterminate, otherwise false."
		queryCommandIndeterm: function (commandId, range) {

			// Take current selection if not passed
			if (!range) {
				if (!Aloha.getSelection().getRangeCount()) {
					return;
				}
				range = Aloha.getSelection().getRangeAt(0);
			}
			return Engine.queryCommandIndeterm(commandId, range);

		},

		queryCommandState: function (commandId, range) {

			// Take current selection if not passed
			if (!range) {
				if (!Aloha.getSelection().getRangeCount()) {
					return;
				}
				range = Aloha.getSelection().getRangeAt(0);
			}
			return Engine.queryCommandState(commandId, range);

		},

		// "When the queryCommandSupported(command) method on the HTMLDocument
		// interface is invoked, the user agent must return true if command is
		// supported, and false otherwise."
		queryCommandSupported: function (commandId) {

			return Engine.queryCommandSupported(commandId);
		},

		queryCommandValue: function (commandId, range) {

			// Take current selection if not passed
			if (!range) {
				if (!Aloha.getSelection().getRangeCount()) {
					return;
				}
				range = Aloha.getSelection().getRangeAt(0);
			}

			// "Return command's value."
			return Engine.queryCommandValue(commandId, range);
		},
		querySupportedCommands: function () {

			var commands = [],
				command;

			for (command in Engine.commands) {
				if (Engine.commands.hasOwnProperty(command)) {
					commands.push(command);
				}
			}
			return commands;
		},
		getStateOverride: Engine.getStateOverride,
		setStateOverride: Engine.setStateOverride,
		resetOverrides: Engine.resetOverrides,
		unsetStateOverride: Engine.unsetStateOverride
	};

	// create an instance
	CommandManager = new (Registry.extend(CommandManager))();

	/**
	 * Executes a registered command.
	 * http://aryeh.name/spec/editing/editing.html#methods-of-the-htmldocument-interface
	 * @method
	 * @param command name of the command
	 * @param showUI has no effect for Aloha Editor and is only here because in spec...
	 * @param value depends on the used command and it impementation
	 * @range optional a range on which the command will be executed if not specified
	 *        the current selection will be used as range
	 */
	Aloha.execCommand = CommandManager.execCommand;

	/**
	 * Check wheater the command in enabled.
	 * If command is not supported, raise a NOT_SUPPORTED_ERR exception.
	 * @param command name of the command
	 * @return true if command is enabled, false otherwise.
	 */
	Aloha.queryCommandEnabled = CommandManager.queryCommandEnabled;

	/**
	 * Check if the command has an indetermed state.
	 * If command is not supported, a NOT_SUPPORTED_ERR exception is thrown
	 * If command has no indeterminacy, INVALID_ACCESS_ERR exception is thrown
	 * If command is not enabled, return false.
	 * @param command name of the command
	 * @range optional a range on which the command will be executed if not specified
	 *        the current selection will be used as range
	 * @return true if command is indeterminate, otherwise false.
	 */
	Aloha.queryCommandIndeterm = CommandManager.queryCommandIndeterm;

	/**
	 * Returns the state of a given command
	 * If command is not supported, a NOT_SUPPORTED_ERR exception is thrown
	 * If command has no state, an INVALID_ACCESS_ERR exception is thrown
	 * If command is not enabled, return false
	 * If the state override for command is set, it returns the state
	 * @param command name of the command
	 * @return state override or true if command's state is true, otherwise false.
	 */
	Aloha.queryCommandState = CommandManager.queryCommandState;

	/**
	 * Check if a given command is supported
	 * @return true if command is supported, and false otherwise.
	 */
	Aloha.queryCommandSupported = CommandManager.queryCommandSupported;

	/**
	 * Returns the Value of a given Command
	 * If command is not supported, a NOT_SUPPORTED_ERR exception is thrown
	 * If command is not enabled, returns an empty string
	 * If command is "fontSize" and its value override is set, an integer
	 * number of pixels is returned as font size for the result.
	 * If the value override for command is set, it returns that.
	 * @return command's value.
	 */
	Aloha.queryCommandValue = CommandManager.queryCommandValue;

	Aloha.querySupportedCommands = CommandManager.querySupportedCommands;

	return CommandManager;
});

/* state-override.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/state-override',[
	'aloha/core',
	'jquery',
	'aloha/command',
	'util/dom2',
	'util/maps',
	'util/range',
	'PubSub'
], function (
	Aloha,
	jQuery,
	Command,
	Dom,
	Maps,
	RangeObject,
	PubSub
) {
	

	// Because we want to provide an easy way to disable the state-override feature.
	var enabled = Aloha.settings.stateOverride !== false;
	var overrides = null;
	var overrideRange = null;

	function rangeObjectFromRange(range) {
		return new RangeObject(range);
	}

	function clear() {
		overrideRange = null;
		overrides = null;
	}

	function keyPressHandler(event) {
		if (!overrides) {
			return;
		}
		if (event.altKey || event.ctrlKey || !event.which) {
			return;
		}
		var selection = Aloha.getSelection();
		if (!selection.getRangeCount()) {
			return;
		}
		var text = String.fromCharCode(event.which);
		var range = selection.getRangeAt(0);
		Dom.insertSelectText(text, range);
		Maps.forEach(overrides, function (formatFn, command) {
			formatFn(command, range);
		});
		Dom.collapseToEnd(range);
		selection.removeAllRanges();
		selection.addRange(range);
		// Because we handled the character insert ourselves via
		// insertText we must not let the browser's default action
		// insert the character a second time.
		event.preventDefault();
	}

	function set(command, range, formatFn) {
		if (!enabled) {
			return;
		}
		overrideRange = range;
		overrides = overrides || {};
		overrides[command] = formatFn;
	}

	function setWithRangeObject(command, rangeObject, formatFn) {
		if (!enabled) {
			return;
		}
		set(command, Dom.rangeFromRangeObject(rangeObject), function (command, range) {
			var rangeObject = rangeObjectFromRange(range);
			formatFn(command, rangeObject);
			Dom.setRangeFromRef(range, rangeObject);
		});
		// Because without doing rangeObject.select(), the
		// next insertText command (see editable.js) will
		// not be reached and instead the browsers default
		// insert behaviour will be applied (which doesn't
		// know anything about state overrides). I don't
		// know the exact reasons why; probably some
		// stopPropagation somewhere by some plugin.
		rangeObject.select();
	}

	function enabledAccessor(trueFalse) {
		if (null != trueFalse) {
			enabled = trueFalse;
		}
		return enabled;
	}

	// https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#state-override
	// "Whenever the number of ranges in the selection changes to
	// something different, and whenever a boundary point of the range
	// at a given index in the selection changes to something different,
	// the state override and value override must be unset for every
	// command."
	Aloha.bind('aloha-selection-changed', function (event, range) {
		if (overrideRange && !Dom.areRangesEq(overrideRange, range)) {
			clear();
			// Because the UI may reflect the any potentially state
			// overrides that are now no longer in effect, we must
			// redraw the UI according to the current selection.
			PubSub.pub('aloha.selection.context-change', {
				range: range,
				event: event
			});
		}
	});

	return {
		enabled: enabledAccessor,
		keyPressHandler: keyPressHandler,
		setWithRangeObject: setWithRangeObject,
		set: set,
		clear: clear
	};
});

/* editable.js is part of the Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2014 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 * License http://aloha-editor.org/license.php
 */
define('aloha/editable',[
	'aloha/core',
	'util/class',
	'jquery',
	'aloha/content-rules',
	'aloha/pluginmanager',
	'aloha/selection',
	'aloha/markup',
	'aloha/contenthandlermanager',
	'aloha/console',
	'aloha/block-jump',
	'aloha/ephemera',
	'util/dom2',
	'util/browser',
	'PubSub',
	'aloha/copypaste',
	'aloha/command',
	'aloha/state-override'
], function (
	Aloha,
	Class,
	$,
	ContentRules,
	PluginManager,
	Selection,
	Markup,
	ContentHandlerManager,
	console,
	BlockJump,
	Ephemera,
	Dom,
	Browser,
	PubSub,
	CopyPaste,
	Command,
	StateOverride
) {
	

	var jQuery = $;
	var unescape = window.unescape,
		GENTICS = window.GENTICS,

		// True, if the next editable activate event should not be handled
		ignoreNextActivateEvent = false;

	/**
	 * A cache to hold information derived, and used in getContents().
	 * @type {object<string,(string|jQuery.<HTMLElement>)>}
	 * @private
	 */
	var editableContentCache = {};

	// default supported and custom content handler settings
	// @TODO move to new config when implemented in Aloha
	Aloha.defaults.contentHandler = {};
	Aloha.defaults.contentHandler.initEditable = ['blockelement', 'sanitize'];
	Aloha.defaults.contentHandler.getContents = ['blockelement', 'sanitize', 'basic'];

	// The insertHtml contenthandler ( paste ) will, by default, use all
	// registered content handlers.
	//Aloha.defaults.contentHandler.insertHtml = void 0;

	if (typeof Aloha.settings.contentHandler === 'undefined') {
		Aloha.settings.contentHandler = {};
	}

	var defaultContentSerializer = function (editableElement) {
		return jQuery(editableElement).html();
	};

	var contentSerializer = defaultContentSerializer;

	/**
	 * Triggers smartContentChange handlers.
	 *
	 * @param {Aloha.Editable}
	 * @return {string} Content that has been processed by getContent handlers
	 *                  and smartContentChange handlers.
	 */
	function handleSmartContentChange(editable) {
		return ContentHandlerManager.handleContent(editable.getContents(), {
			contenthandler: Aloha.settings.contentHandler.smartContentChange
		}, editable);
	}

	/**
	 * List of observed key, mapped against their keycodes.
	 *
	 * @type {object<number, string>}
	 * @const
	 */
	var KEYCODES = {
		65: 'a'
	};

	/**
	 * Handlers for various key combos.
	 * Each handler ought to return false if they do not want the event to
	 * continue propagating.
	 */
	var keyBindings = {
		'ctrl+a': function () {
			var editable = CopyPaste.getEditableAt(CopyPaste.getRange());
			if (editable) {
				CopyPaste.selectAllOf(editable.obj[0]);
				return false;
			}
		}
	};

	/**
	 * Gets the name of the modifier key if is in effect for the given event.
	 *
	 * eg: <Ctrl>+c
	 *
	 * @param {jQuery.Event} $event
	 * @return {string|null} Modifier string or null if no modifier is in
	 *                       effect.
	 *                      
	 */
	function keyModifier($event) {
		return $event.altKey ? 'alt' :
					$event.ctrlKey ? 'ctrl' :
						$event.shiftKey ? 'shift' : null;
	}

	/**
	 * Handles keydown events that are fired on the page's document.
	 *
	 * @param {jQuery.Event) $event
	 * @return {boolean} Returns false to stop propagation; undefined otherwise.
	 */
	function onKeydown($event) {
		if (!Aloha.activeEditable) {
			return;
		}
		var key = KEYCODES[$event.which];
		if (key) {
			var modifier = keyModifier($event);
			var combo = (modifier ? modifier + '+' : '') + key;
			if (keyBindings[combo]) {
				return keyBindings[combo]($event);
			}
		}
	}

	/**
	 * Registers events on the given editable's corresponding DOM element.
	 *
	 * @param {Editable} editable
	 */
	function registerEvents(editable) {
		var $editable = editable.obj;

		$editable.mousedown(function () {
			Aloha.eventHandled = true;
		}).mouseup(function () {
			Aloha.eventHandled = false;
		});

		$editable.keydown(function (event) {
			var letEventPass = Markup.preProcessKeyStrokes(event);
			editable.keyCode = event.which;
			if (!letEventPass) {
				// the event will not proceed to key press, therefore trigger
				// smartContentChange
				editable.smartContentChange(event);
			}
			return letEventPass;
		});

		$editable.keypress(StateOverride.keyPressHandler);
		$editable.keypress(function (event) {
			// triggers a smartContentChange to get the right charcode
			// To test try http://www.w3.org/2002/09/tests/keys.html
			Aloha.activeEditable.smartContentChange(event);
		});

		// native drag and drop adds unwanted style elements so we need to
		// disable it
		$editable.on('dragstart', function (event) {
			return false;
		});

		$editable.keyup(function (event) {
			if (event.keyCode === 27) {
				Aloha.deactivateEditable();
				return false;
			}
		});

		$editable.contentEditableSelectionChange(function (event) {
			Selection.onChange($editable, event, 0, Aloha.mouseEventChangedEditable);
			if (Aloha.mouseEventChangedEditable) {
				Aloha.mouseEventChangedEditable = false;
			}
			return $editable;
		});
	}

	/**
	 * Helper function for safely removing a placeholder that is a div
	 * @private
	 * @param {Element} placeholder DOM Element
	 */
	function removeDivPlaceholder(placeholder) {
		var $placeholder = $(placeholder);
		var child = placeholder.firstChild;

		if (!child) {
			$placeholder.remove();
			return;
		}

		if (!Dom.isTextNode(child) || child.data.indexOf('\u00A0') === -1) {
			child = placeholder.lastChild;
		}

		if (Dom.isTextNode(child)) {
			child.data = child.data.replace(/(^(\u00A0)+)|((\u00A0)+$)/g, '');
		}

		if (!$placeholder.is(':empty')) {
			Dom.removeShallow(placeholder);
		} else {
			$placeholder.remove();
		}
	}

	/**
	 * Checks if the element given is an unmodified aloha placeholder
	 *
	 * @private
	 * @param {HTMLElement} node
	 * @return {Boolean} True if the given element is an aloha-editing-paragraph.
	 */
	function isUnmodifiedAlohaEditingP(node) {
		return Browser.ie
		       ? (node.className === 'aloha-editing-p aloha-placeholder'
		         && node.children.length === 0
		         && (!node.firstChild || node.firstChild.data === '\u2060'))
		       : (node.className === 'aloha-editing-p aloha-placeholder'
		         && node.children.length >= 1
		         && node.children[0].nodeName === 'BR'
		         && node.children[0].className === 'aloha-end-br');
	}

	/**
	 * Helper function for safely removing a placeholder that is a paragraph
	 *
	 * @private
	 * @param {Element} placeholder DOM Element
	 */
	function removePPlaceholder(placeholder) {
		var $placeholder = $(placeholder);
		if (isUnmodifiedAlohaEditingP(placeholder)) {
			$placeholder.remove();
		} else {
			$placeholder.removeClass('aloha-editing-p');
			$placeholder.removeClass('aloha-placeholder');
			if (Browser.ie) {
				//remove trailing or leading word joiner
				var child = $placeholder[0].firstChild;
				if (child && Dom.isTextNode(child) && child.data.indexOf('\u2060') >= -1) {
					child.data = child.data.replace(/(^(\u2060)+)|((\u2060)+$)/g, '');
				}
			}
		}
	}


	/**
	 * Safely removes a placeholder and leaves its content at its place.
	 * This function is a helper to be passed to a jQuery each() invocation.
	 *
	 * @private
	 * @param {index}   index (Unused) index of placeholder element in jQuery collection.
	 * @param {Element} placeholder DOM Element
	 */
	function removePlaceholder(index, placeholder) {
		var $placeholder = $(placeholder);
		if ($placeholder.hasClass('aloha-editing-div')) {
			removeDivPlaceholder(placeholder);
			return;
		}
		if ($placeholder.hasClass('aloha-editing-p')) {
			removePPlaceholder(placeholder);
			return;
		}
		$placeholder.remove();
	}

	$(document).keydown(onKeydown);

	/**
	 * Editable object
	 * @namespace Aloha
	 * @class Editable
	 * @method
	 * @constructor
	 * @param {Object} obj jQuery object reference to the object
	 */
	Aloha.Editable = Class.extend({

		_constructor: function (obj) {
			// check wheter the object has an ID otherwise generate and set
			// globally unique ID
			if (!obj.attr('id')) {
				obj.attr('id', GENTICS.Utils.guid());
			}

			// store object reference
			this.obj = obj;
			this.originalObj = obj;
			this.ready = false;

			// delimiters, timer and idle for smartContentChange
			// smartContentChange triggers -- tab: '\u0009' - space: '\u0020' - enter: 'Enter'
			// backspace: U+0008 - delete: U+007F
			this.sccDelimiters = [':', ';', '.', '!', '?', ',',
								  unescape('%u0009'), unescape('%u0020'), unescape('%u0008'), unescape('%u007F'), 'Enter'];
			this.sccIdle = 5000;
			this.sccDelay = 500;
			this.sccTimerIdle = false;
			this.sccTimerDelay = false;

			// see keyset http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html
			this.keyCodeMap = {
				93: 'Apps', // The Application key
				18: 'Alt', // The Alt ( Menu ) key.
				20: 'CapsLock', // The Caps Lock ( Capital ) key.
				17: 'Control', // The Control ( Ctrl ) key.
				40: 'Down', // The Down Arrow key.
				35: 'End', // The End key.
				13: 'Enter', // The Enter key.
				112: 'F1', // The F1 key.
				113: 'F2', // The F2 key.
				114: 'F3', // The F3 key.
				115: 'F4', // The F4 key.
				116: 'F5', // The F5 key.
				117: 'F6', // The F6 key.
				118: 'F7', // The F7 key.
				119: 'F8', // The F8 key.
				120: 'F9', // The F9 key.
				121: 'F10', // The F10 key.
				122: 'F11', // The F11 key.
				123: 'F12', // The F12 key.

				// Anybody knows the keycode for F13-F24?
				36: 'Home', // The Home key.
				45: 'Insert', // The Insert ( Ins ) key.
				37: 'Left', // The Left Arrow key.
				224: 'Meta', // The Meta key.
				34: 'PageDown', // The Page Down ( Next ) key.
				33: 'PageUp', // The Page Up key.
				19: 'Pause', // The Pause key.
				44: 'PrintScreen', // The Print Screen ( PrintScrn, SnapShot ) key.
				39: 'Right', // The Right Arrow key.
				145: 'Scroll', // The scroll lock key
				16: 'Shift', // The Shift key.
				38: 'Up', // The Up Arrow key.
				91: 'Win', // The left Windows Logo key.
				92: 'Win' // The right Windows Logo key.
			};

			this.placeholderClass = 'aloha-placeholder';

			Aloha.registerEditable(this);
		},

		/**
		 * Initialize the editable
		 * @return void
		 * @hide
		 */
		init: function () {
			var me = this;

			// TODO make editables their own settings.
			this.settings = Aloha.settings;

			// smartContentChange settings
			// @TODO move to new config when implemented in Aloha
			if (Aloha.settings && Aloha.settings.smartContentChange) {
				if (Aloha.settings.smartContentChange.delimiters) {
					this.sccDelimiters = Aloha.settings.smartContentChange.delimiters;
				}

				if (Aloha.settings.smartContentChange.idle) {
					this.sccIdle = Aloha.settings.smartContentChange.idle;
				}

				if (Aloha.settings.smartContentChange.delay) {
					this.sccDelay = Aloha.settings.smartContentChange.delay;
				}
			}

			// check if Aloha can handle the obj as Editable
			if (!this.check(this.obj)) {
				//Aloha.log( 'warn', this, 'Aloha cannot handle {' + this.obj[0].nodeName + '}' );
				this.destroy();
				return;
			}

			// apply content handler to clean up content
			if (typeof Aloha.settings.contentHandler.getContents === 'undefined') {
				Aloha.settings.contentHandler.getContents = Aloha.defaults.contentHandler.getContents;
			}

			// apply content handler to clean up content
			if (typeof Aloha.settings.contentHandler.initEditable === 'undefined') {
				Aloha.settings.contentHandler.initEditable = Aloha.defaults.contentHandler.initEditable;
			}

			Ephemera.markAttr(me.obj, 'style');

			var content = me.obj.html();
			content = ContentHandlerManager.handleContent(content, {
				contenthandler: Aloha.settings.contentHandler.initEditable,
				command: 'initEditable'
			}, me);
			me.obj.html(ContentRules.applyRules(content, me.obj[0]));

			// Because editables can only properly be initialized when Aloha
			// plugins are loaded.
			Aloha.bind('aloha-plugins-loaded', function () {
				me.obj.addClass('aloha-editable').contentEditable(true);

				registerEvents(me);

				// mark the editable as unmodified
				me.setUnmodified();

				// we don't do the sanitizing on aloha ready, since some plugins add elements into the content and bind
				// events to it. If we sanitize by replacing the html, all events would get lost. TODO: think about a
				// better solution for the sanitizing, without destroying the events  apply content handler to clean up content
				//				var content = me.obj.html();
				//				if ( typeof Aloha.settings.contentHandler.initEditable === 'undefined' ) {
				//					Aloha.settings.contentHandler.initEditable = Aloha.defaults.contentHandler.initEditable;
				//				}
				//				content = ContentHandlerManager.handleContent( content, {
				//					contenthandler: Aloha.settings.contentHandler.initEditable
				//				} );
				//				me.obj.html( content );

				me.snapshotContent = me.getContents();


				me.initPlaceholder();

				me.ready = true;

				// disable object resizing and inline table editing.
				// we do this in here and with a slight delay, because
				// starting with FF 15, this would cause a JS error
				// if done before the first DOM object is made contentEditable.
				window.setTimeout(function () {
					Aloha.disableObjectResizing();
					Aloha.disableInlineTableEditing();
				}, 20);

				// throw a new event when the editable has been created
				/**
				 * @event editableCreated fires after a new editable has been created, eg. via $( '#editme' ).aloha()
				 * The event is triggered in Aloha's global scope Aloha
				 * @param {Event} e the event object
				 * @param {Array} a an array which contains a reference to the currently created editable on its first position
				 */
				Aloha.trigger('aloha-editable-created', [me]);
				PubSub.pub('aloha.editable.created', {
					editable: me,
					data: me // deprecated
				});
			});
		},

		/**
		 * True, if this editable is active for editing
		 * @property
		 * @type boolean
		 */
		isActive: false,

		/**
		 * stores the original content to determine if it has been modified
		 * @hide
		 */
		originalContent: null,

		/**
		 * every time a selection is made in the current editable the selection has to
		 * be saved for further use
		 * @hide
		 */
		range: undefined,

		/**
		 * Check if object can be edited by Aloha Editor
		 * @return {boolean } editable true if Aloha Editor can handle else false
		 * @hide
		 */
		check: function () {
			/* TODO check those elements
			'map', 'meter', 'object', 'output', 'progress', 'samp',
			'time', 'area', 'datalist', 'figure', 'kbd', 'keygen',
			'mark', 'math', 'wbr', 'area',
			*/

			// Extract El
			var me = this,
				obj = this.obj,
				el = obj.get(0),
				nodeName = el.nodeName.toLowerCase(),

				// supported elements
				textElements = ['a', 'abbr', 'address', 'article', 'aside', 'b', 'bdo', 'blockquote', 'cite', 'code', 'command', 'del', 'details', 'dfn', 'div', 'dl', 'em', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'i', 'ins', 'menu', 'nav', 'p', 'pre', 'q', 'ruby', 'section', 'small', 'span', 'strong', 'sub', 'sup', 'var'],
				i,
			    div;

			for (i = 0; i < textElements.length; ++i) {
				if (nodeName === textElements[i]) {
					return true;
				}
			}

			// special handled elements
			switch (nodeName) {
			case 'label':
			case 'button':
				// TODO need some special handling.
				break;
			case 'textarea':
			case 'input':
				// Create a div alongside the textarea
				div = jQuery('<div id="' + this.getId() + '-aloha" class="aloha-' + nodeName + '" />').insertAfter(obj);

				// Resize the div to the textarea and
				// Populate the div with the value of the textarea
				// Then, hide the textarea
				div.height(obj.height()).width(obj.width()).html(obj.val());

				obj.hide();

				// Attach a onsubmit to the form to place the HTML of the
				// div back into the textarea
				obj.parents('form:first').submit(function () {
					obj.val(me.getContents());
				});

				// Swap textarea reference with the new div
				this.obj = div;

				// Supported
				return true;
			default:
				break;
			}

			// the following elements are not supported
			/*
			'canvas', 'audio', 'br', 'embed', 'fieldset', 'hgroup', 'hr',
			'iframe', 'img', 'input', 'map', 'script', 'select', 'style',
			'svg', 'table', 'ul', 'video', 'ol', 'form', 'noscript',
			 */
			return false;
		},

		/**
		 * Init Placeholder
		 *
		 * @return void
		 */
		initPlaceholder: function () {
			if (Aloha.settings.placeholder && this.isEmpty()) {
				this.addPlaceholder();
			}
		},

		/**
		 * Check if the conteneditable is empty.
		 *
		 * @return {Boolean}
		 */
		isEmpty: function () {
			var editableTrimedContent = $('<div />').html(this.getContents()).text().trim(),
				onlyBrTag = (editableTrimedContent === '<br>') ? true : false;
			return (editableTrimedContent.length === 0 || onlyBrTag);
		},

		/**
		 * Add placeholder in editable
		 *
		 * @return void
		 */
		addPlaceholder: function () {
			var div = jQuery('<div>'),
				span = jQuery('<span>'),
				el,
				obj = this.obj;
			if (GENTICS.Utils.Dom.allowsNesting(obj[0], div[0])) {
				el = div;
			} else {
				el = span;
			}
			if (jQuery("." + this.placeholderClass, obj).length !== 0) {
				return;
			}
			jQuery.each(Aloha.settings.placeholder, function (selector, selectorConfig) {
				if (obj.is(selector)) {
					el.html(selectorConfig);
				}
			});
			if (!el.is(':empty')) {
				el.addClass(this.placeholderClass).addClass('aloha-ephemera');
				jQuery(obj).append(el);
			}
			jQuery('br', obj).remove();
		},

		/**
		 * remove placeholder from contenteditable. If setCursor is true,
		 * will also set the cursor to the start of the selection. However,
		 * this will be ASYNCHRONOUS, so if you rely on the fact that
		 * the placeholder is removed after calling this method, setCursor
		 * should be false ( or not set )
		 *
		 * @return void
		 */
		removePlaceholder: function (obj, setCursor) {
			var placeholderClass = this.placeholderClass,
				range;
			if (jQuery("." + this.placeholderClass, obj).length === 0) {
				return;
			}
			// set the cursor // remove placeholder
			if (setCursor === true) {
				window.setTimeout(function () {
					range = new Selection.SelectionRange();
					range.startContainer = range.endContainer = obj.get(0);
					range.startOffset = range.endOffset = 0;
					obj.find('.' + placeholderClass).each(removePlaceholder);
					range.select();

				}, 100);
			} else {
				obj.find('.' + placeholderClass).each(removePlaceholder);
			}
		},

		/**
		 * destroy the editable
		 * @return void
		 */
		destroy: function () {
			// leave the element just to get sure
			if (this === Aloha.getActiveEditable()) {
				this.blur();
			}

			// special handled elements
			switch (this.originalObj.get(0).nodeName.toLowerCase()) {
			case 'label':
			case 'button':
				// TODO need some special handling.
				break;
			case 'textarea':
			case 'input':
				// restore content to original textarea
				this.originalObj.val(this.getContents());
				this.obj.remove();
				this.originalObj.show();
				break;
			default:
				break;
			}

			// now the editable is not ready any more
			this.ready = false;

			// remove the placeholder if needed.
			this.removePlaceholder(this.obj);

			// initialize the object and disable contentEditable
			// unbind all events
			// TODO should only unbind the specific handlers.
			this.obj.removeClass('aloha-editable').contentEditable(false).unbind('mousedown click dblclick focus keydown keypress keyup');

			/* TODO remove this event, it should implemented as bind and unbind
			// register the onSelectionChange Event with the Editable field
			this.obj.contentEditableSelectionChange( function( event ) {
				Aloha.Selection.onChange( me.obj, event );
				return me.obj;
			} );
			*/

			// throw a new event when the editable has been created
			/**
			 * @event editableCreated fires after a new editable has been destroyes, eg. via $( '#editme' ).mahalo()
			 * The event is triggered in Aloha's global scope Aloha
			 * @param {Event} e the event object
			 * @param {Array} a an array which contains a reference to the currently created editable on its first position
			 */
			Aloha.trigger('aloha-editable-destroyed', [this]);
			PubSub.pub('aloha.editable.destroyed', {
				editable: this,
				data: this // deprecated
			});

			// finally register the editable with Aloha
			Aloha.unregisterEditable(this);
		},

		/**
		 * marks the editables current state as unmodified. Use this method to inform the editable
		 * that it's contents have been saved
		 * @method
		 */
		setUnmodified: function () {
			this.originalContent = this.getContents();
		},

		/**
		 * check if the editable has been modified during the edit process#
		 * @method
		 * @return boolean true if the editable has been modified, false otherwise
		 */
		isModified: function () {
			return this.originalContent !== this.getContents();
		},

		/**
		 * String representation of the object
		 * @method
		 * @return Aloha.Editable
		 */
		toString: function () {
			return 'Aloha.Editable';
		},

		/**
		 * check whether the editable has been disabled
		 */
		isDisabled: function () {
			return !this.obj.contentEditable() || this.obj.contentEditable() === 'false';
		},

		/**
		 * disable this editable
		 * a disabled editable cannot be written on by keyboard
		 */
		disable: function () {
			return this.isDisabled() || this.obj.contentEditable(false);
		},

		/**
		 * enable this editable
		 * reenables a disabled editable to be writteable again
		 */
		enable: function () {
			return this.isDisabled() && this.obj.contentEditable(true);
		},


		/**
		 * activates an Editable for editing
		 * disables all other active items
		 * @method
		 */
		activate: function (e) {
			// get active Editable before setting the new one.
			var oldActive = Aloha.getActiveEditable();

			// We need to ommit this call when this flag is set to true.
			// This flag will only be set to true before the removePlaceholder method
			// is called since that method invokes a focus event which will again trigger
			// this method. We want to avoid double invokation of this method.
			if (ignoreNextActivateEvent) {
				ignoreNextActivateEvent = false;
				return;
			}

			// handle special case in which a nested editable is focused by a click
			// in this case the "focus" event would be triggered on the parent element
			// which actually shifts the focus away to it's parent. this if is here to
			// prevent this situation
			if (e && e.type === 'focus' && oldActive !== null && oldActive.obj.parent().get(0) === e.currentTarget) {
				return;
			}

			// leave immediately if this is already the active editable
			if (this.isActive || this.isDisabled()) {
				// we don't want parent editables to be triggered as well, so return false
				return;
			}

			this.obj.addClass('aloha-editable-active');

			Aloha.activateEditable(this);

			ignoreNextActivateEvent = true;
			this.removePlaceholder(this.obj, true);
			ignoreNextActivateEvent = false;

			this.isActive = true;

			/**
			 * @event editableActivated fires after the editable has been activated by clicking on it.
			 * This event is triggered in Aloha's global scope Aloha
			 * @param {Event} e the event object
			 * @param {Array} a an array which contains a reference to last active editable on its first position, as well
			 * as the currently active editable on it's second position
			 */
			// trigger a 'general' editableActivated event
			Aloha.trigger('aloha-editable-activated', {
				'oldActive': oldActive,
				'editable': this
			});
			PubSub.pub('aloha.editable.activated', {
				old: oldActive,
				editable: this,
				// deprecated
				data: {
					old: oldActive,
					editable: this
				}
			});
		},

		/**
		 * handle the blur event
		 * this must not be attached to the blur event, which will trigger far too often
		 * eg. when a table within an editable is selected
		 * @hide
		 */
		blur: function () {
			this.obj.blur();
			this.isActive = false;
			this.initPlaceholder();
			this.obj.removeClass('aloha-editable-active');

			/**
			 * @event editableDeactivated fires after the editable has been activated by clicking on it.
			 * This event is triggered in Aloha's global scope Aloha
			 * @param {Event} e the event object
			 * @param {Array} a an array which contains a reference to this editable
			 */
			Aloha.trigger('aloha-editable-deactivated', {editable: this});
			PubSub.pub('aloha.editable.deactivated', {
				editable: this,
				// deprecated
				data: {
					editable: this
				}
			});

			/**
			 * @event smartContentChanged
			 */
			Aloha.activeEditable.smartContentChange({
				type: 'blur'
			}, null);

			Selection.resetPrevSelectionContexts();
		},

		/**
		 * check if the string is empty
		 * used for zerowidth check
		 * @return true if empty or string is null, false otherwise
		 * @hide
		 */
		empty: function (str) {
			// br is needed for chrome
			return (null === str) || (jQuery.trim(str) === '' || str === '<br/>');
		},

		/**
		 * Get the contents of this editable as a HTML string or child node DOM
		 * objects.
		 *
		 * @param {boolean} asObject Whether or not to retreive the contents of
		 *                           this editable as child node objects or as
		 *                           HTML string.
		 * @return {string|jQuery.<HTMLElement>} Contents of the editable as
		 *                                       DOM objects or an HTML string.
		 */
		getContents: function (asObject) {
			var raw = this.obj.html();
			var cache = editableContentCache[this.getId()];

			if (!cache || raw !== cache.raw) {

				BlockJump.removeZeroWidthTextNodeFix();

				var $clone = this.obj.clone(false);
				this.removePlaceholder($clone);
				$clone = jQuery(Ephemera.prune($clone[0]));
				PluginManager.makeClean($clone);

				// TODO rewrite ContentHandlerManager to accept DOM trees instead of strings
				$clone = jQuery('<div>' + ContentHandlerManager.handleContent($clone.html(), {
					contenthandler: Aloha.settings.contentHandler.getContents,
					command: 'getContents'
				}, this) + '</div>');

				cache = editableContentCache[this.getId()] = {};
				cache.raw = raw;
				cache.element = $clone;
			}

			if (asObject) {
				return cache.element.clone().contents();
			}

			if (null == cache.serialized) {
				cache.serialized = contentSerializer(cache.element[0]);
			}
			return cache.serialized;
		},

		/**
		 * Set the contents of this editable as a HTML string
		 * @param content as html
		 * @param return as object or html string
		 * @return contents of the editable
		 */
		setContents: function (content, asObject) {
			var reactivate = null;

			if (Aloha.getActiveEditable() === this) {
				Aloha.deactivateEditable();
				reactivate = this;
			}

			this.obj.html(content);

			if (null !== reactivate) {
				reactivate.activate();
			}

			this.smartContentChange({
				type: 'set-contents'
			});

			return asObject ? this.obj.contents() : contentSerializer(this.obj[0]);
		},

		/**
		 * Get the id of this editable
		 * @method
		 * @return id of this editable
		 */
		getId: function () {
			return this.obj.attr('id');
		},

		/**
		 * Generates and signals a smartContentChange event.
		 *
		 * A smart content change occurs when a special editing action, or a
		 * combination of interactions are performed by the user during the
		 * course of editing within an editable.
		 * The smart content change event would therefore signal to any
		 * component that is listening to this event, that content has been
		 * inserted into the editable that may need to be prococessed in a
		 * special way
		 * This is used for smart actions within the content/while editing.
		 * @param {Event} event
		 * @hide
		 */
		smartContentChange: function (event) {
			var me = this,
				uniChar = null;

			// ignore meta keys like crtl+v or crtl+l and so on
			if (event && (event.metaKey || event.crtlKey || event.altKey)) {
				return false;
			}

			if (event) {
				// Use among browsers reliable which http://api.jquery.com/keypress
				uniChar = (this.keyCodeMap[this.keyCode] || String.fromCharCode(event.which) || 'unknown');
			}

			var snapshot = null;

			function getSnapshotContent() {
				if (null == snapshot) {
					snapshot = me.getSnapshotContent();
				}
				return snapshot;
			}

			// handle "Enter" -- it's not "U+1234" -- when returned via "event.originalEvent.keyIdentifier"
			// reference: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html
			if (jQuery.inArray(uniChar, this.sccDelimiters) >= 0) {
				clearTimeout(this.sccTimerIdle);
				clearTimeout(this.sccTimerDelay);

				this.sccTimerDelay = window.setTimeout(function () {
					Aloha.trigger('aloha-smart-content-changed', {
						'editable': me,
						'keyIdentifier': event.originalEvent.keyIdentifier,
						'keyCode': event.keyCode,
						'char': uniChar,
						'triggerType': 'keypress', // keypress, timer, blur, paste
						'getSnapshotContent': getSnapshotContent
					});
					handleSmartContentChange(me);

					console.debug('Aloha.Editable',
							'smartContentChanged: event type keypress triggered');
				}, this.sccDelay);

			} else if (event && event.type === 'paste') {
				Aloha.trigger('aloha-smart-content-changed', {
					'editable': me,
					'keyIdentifier': null,
					'keyCode': null,
					'char': null,
					'triggerType': 'paste',
					'getSnapshotContent': getSnapshotContent
				});
				handleSmartContentChange(me);

			} else if (event && event.type === 'blur') {
				Aloha.trigger('aloha-smart-content-changed', {
					'editable': me,
					'keyIdentifier': null,
					'keyCode': null,
					'char': null,
					'triggerType': 'blur',
					'getSnapshotContent': getSnapshotContent
				});
				handleSmartContentChange(me);

			} else if (event && event.type === 'block-change') {
				clearTimeout(this.sccTimerIdle);
				clearTimeout(this.sccTimerDelay);

				this.sccTimerDelay = window.setTimeout(function () {
					Aloha.trigger('aloha-smart-content-changed', {
						'editable': me,
						'keyIdentifier': null,
						'keyCode': null,
						'char': null,
						'triggerType': 'block-change',
						'getSnapshotContent': getSnapshotContent
					});
					handleSmartContentChange(me);

				}, this.sccDelay);

			} else if (uniChar !== null) {
				var range = Aloha.Selection.getRangeObject();

				//Remove break in otherwise empty children in IE and Mozilla
				//This is done automatically in Chrome and would lead to errors
				if (Browser.ie || Browser.mozilla) {
					if (range.startContainer == range.endContainer) {
						var $children = $(range.startContainer).children();

						if ($children.length == 1 && $children.is('br')) {
							$children.remove();
						}
					}
				}

				// in the rare case idle time is lower then delay time
				clearTimeout(this.sccTimerDelay);
				clearTimeout(this.sccTimerIdle);
				this.sccTimerIdle = window.setTimeout(function () {
					Aloha.trigger('aloha-smart-content-changed', {
						'editable': me,
						'keyIdentifier': null,
						'keyCode': null,
						'char': null,
						'triggerType': 'idle',
						'getSnapshotContent': getSnapshotContent
					});
					handleSmartContentChange(me);
				}, this.sccIdle);
			}
		},

		/**
		 * Get a snapshot of the active editable as a HTML string
		 * @hide
		 * @return snapshot of the editable
		 */
		getSnapshotContent: function () {
			var ret = this.snapshotContent;
			this.snapshotContent = this.getContents();
			return ret;
		}
	});

	/**
	 * Sets the content serializer function.
	 *
	 * The default content serializer will just call the jQuery.html()
	 * function on the editable element (which gets the innerHTML property).
	 *
	 * This method is a static class method and will affect the result
	 * of editable.getContents() for all editables that have been or
	 * will be constructed.
	 *
	 * @param {!Function} serializerFunction
	 *        A function that accepts a DOM element and returns the serialized
	 *        XHTML of the element contents (excluding the start and end tag of
	 *        the passed element).
	 * @api
	 */
	Aloha.Editable.setContentSerializer = function (serializerFunction) {
		contentSerializer = serializerFunction;
	};

	/**
	 * Gets the content serializer function.
	 *
	 * @see Aloha.Editable.setContentSerializer()
	 * @api
	 * @return {!Function}
	 *        The serializer function.
	 */
	Aloha.Editable.getContentSerializer = function () {
		return contentSerializer;
	};

	Aloha.Editable.registerEvents = registerEvents;

});

/* plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/plugin',[
	'aloha/core',
	'jquery',
	'util/class',
	'aloha/pluginmanager',
	'aloha/console'
], function (
	Aloha,
	jQuery,
	Class,
	PluginManager,
	console
) {
	

	/**
	 * Gets the original object from the editable. If the editable is an input or textarea,
	 * aloha creates a editable div, but sometimes we need the original object and not the div
	 * created by aloha.
	 * @param {jQuery} editableElement
	 * @returns {jQuery}
	 */
	function getEditableOriginalObj(editableElement) {
		var editable = Aloha.getEditableById(editableElement.attr('id'));

		return editable ? editable.originalObj : editableElement;
	}

	/**
	 * Abstract Plugin Object
	 * @namespace Aloha
	 * @class Plugin
	 * @constructor
	 * @param {String} pluginPrefix unique plugin prefix
	 */
	var Plugin = Class.extend({

		name: null,

		/**
		 * contains the plugin's default settings object
		 * @cfg {Object} default settings for the plugin
		 */
		defaults: {},

		/**
		 * contains the plugin's settings object
		 * @cfg {Object} settings the plugins settings stored in an object
		 */
		settings: {},

		/**
		 * Names of other plugins which must be loaded in order for this plugin to
		 * function.
		 * @cfg {Array}
		 */
		dependencies: [],

		_constructor: function (name) {
			/**
			 * Settings of the plugin
			 */
			if (typeof name !== "string") {
				console.error('Cannot initialise unnamed plugin, skipping');
			} else {
				this.name = name;
			}
		},

		/**
		 * @return true if dependencies satisfied, false otherwise
		 */
		checkDependencies: function () {
			var plugin = this;
			var satisfied = true;
			jQuery.each(plugin.dependencies, function (i, dependency) {
				if (!Aloha.isPluginLoaded(dependency.toString())) {
					satisfied = false;
					console.error('plugin.' + plugin.name,
							'Required plugin "' + dependency + '" not found.');
					return false;
				}
			});
			return satisfied;
		},

		/**
		 * Init method of the plugin. Called from Aloha Core to initialize this plugin
		 * @return void
		 * @hide
		 */
		init: function () {},

		/**
		 * Get the configuration settings for an editable obj.
		 * Handles both conf arrays or conf objects
		 * <ul>
		 * <li>Array configuration parameters are:
		 * <pre>
		 * "list": {
		 *		config : [ 'b', 'h1' ],
		 *		editables : {
		 *			'#title'	: [ ],
		 *			'div'		: [ 'b', 'i' ],
		 *			'.article'	: [ 'h1' ]
		 *		}
		 *	}
		 * </pre>
		 *
		 * The hash keys of the editables are css selectors. For a
		 *
		 * <pre>
		 *  <div class="article">content</div>
		 * </pre>
		 *
		 *  the selectors 'div' and '.article' match and the returned configuration is
		 *
		 * <pre>
		 *  [ 'b', 'i', 'h1']
		 * </pre>
		 *
		 * The '#title' object would return an empty configuration.
		 *
		 * <pre>
		 *  [ ]
		 * </pre>
		 *
		 *  All other objects would get the 'config' configuration. If config is not set
		 * the plugin default configuration is returned.
		 *
		 * <pre>
		 *  [ 'b', 'h1']
		 * </pre></li>
		 * <li>Object configuration parameters are :
		 * <pre>
		 *	"image": {
		 *		config : { 'img': { 'max_width': '50px',
		 *		'max_height': '50px' }},
		 *		editables : {
		 *			'#title': {},
		 *			'div': {'img': {}},
		 *			'.article': {'img': { 'max_width': '150px',
		 *			'max_height': '150px' }}
		 *		}
		 *	}
		 * </pre>
		 *  The '#title' object would return an empty configuration.<br/>
		 *  The 'div' object would return the default configuration.<br/>
		 *  the '.article' would return :
		 *  <pre>
		 *		{'img': { 'max_width': '150px',
		 *		'max_height': '150px' }}
		 *  </pre>
		 * </li>
		 *
		 * @param {jQuery} obj jQuery object of an Editable Object
		 * @return {Array} config A Array with configuration entries
		 */
		getEditableConfig: function (obj) {
			var configObj = null,
				configSpecified = false,
				that = this;

			if (this.settings.editables) {
				// When editable is an input or textarea we need the original object.
				obj = getEditableOriginalObj(obj);

				// check if the editable's selector matches and if so add its configuration to object configuration
				jQuery.each(this.settings.editables, function (selector, selectorConfig) {
					var k;
					if (obj.is(selector)) {
						configSpecified = true;
						if (selectorConfig instanceof Array) {
							configObj = [];
							configObj = jQuery.merge(configObj, selectorConfig);
						} else if (typeof selectorConfig === "object") {
							configObj = {};
							configObj['aloha-editable-selector'] = selector;
							for (k in selectorConfig) {
								if (selectorConfig.hasOwnProperty(k)) {
									if (selectorConfig[k] instanceof Array) {
										//configObj[k] = [];
										//configObj[k] = jQuery.extend(true, configObj[k], that.config[k], selectorConfig[k]);
										configObj[k] = selectorConfig[k];
									} else if (typeof selectorConfig[k] === "object") {
										configObj[k] = {};
										configObj[k] = jQuery.extend(true, configObj[k], that.config[k], selectorConfig[k]);
									} else {
										configObj[k] = selectorConfig[k];
									}
								}
							}
						} else {
							configObj = selectorConfig;
						}
					}
				});
			}

			// fall back to default configuration
			if (!configSpecified) {
				if (typeof this.settings.config === 'undefined' || !this.settings.config) {
					configObj = this.config;
				} else {
					configObj = this.settings.config;
				}
			}

			return configObj;
		},

		/**
		 * Make the given jQuery object (representing an editable) clean for saving
		 * @param obj jQuery object to make clean
		 * @return void
		 */
		makeClean: function (obj) {},

		/**
		 * Make a system-wide unique id out of a plugin-wide unique id by prefixing it with the plugin prefix
		 * @param id plugin-wide unique id
		 * @return system-wide unique id
		 * @hide
		 * @deprecated
		 */
		getUID: function (id) {
			console.deprecated('plugin', 'getUID() is deprecated. Use plugin.name instead.');
			return this.name;
		},

		/**
		 * Return string representation of the plugin, which is the prefix
		 * @return name
		 * @hide
		 * @deprecated
		 */
		toString: function () {
			return this.name;
		},

		/**
		 * Log a plugin message to the logger
		 * @param level log level
		 * @param message log message
		 * @return void
		 * @hide
		 * @deprecated
		 */
		log: function (level, message) {
			console.deprecated('plugin', 'log() is deprecated. Use Aloha.console instead.');
			console.log(level, this, message);
		}
	});

	/**
	 * Static method used as factory to create plugins.
	 * 
	 * @param {String} pluginName name of the plugin
	 * @param {Object} definition definition of the plugin, should have at least an "init" and "destroy" method.
	 */
	Plugin.create = function (pluginName, definition) {

		var pluginInstance = new (Plugin.extend(definition))(pluginName);
		pluginInstance.settings = jQuery.extendObjects(true, pluginInstance.defaults, Aloha.settings[pluginName]);
		PluginManager.register(pluginInstance);

		return pluginInstance;
	};

	return Plugin;
});

/* jquery.aloha.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * IMPORTANT!
 * Don't add any more custom jquery extensions here.
 * Instead use the define(...) mechanism to define a module and to
 * import it where you need it.
 */
define('aloha/jquery.aloha',[
	'aloha/core',
	'aloha/selection',
	'jquery',
	'aloha/console'
], function (
	Aloha,
	Selection,
	jQuery,
	console
) {
	

	var XMLSerializer = window.XMLSerializer;

	/**
	 * jQuery between Extension
	 *
	 * insert either html code, a dom object OR a jQuery object inside of an existing text node.
	 * if the chained jQuery object is not a text node, nothing will happen.
	 *
	 * @param content HTML Code, DOM object or jQuery object to be inserted
	 * @param offset character offset from the start where the content should be inserted
	 */
	jQuery.fn.between = function (content, offset) {
		var offSize, fullText;

		if (this[0].nodeType !== 3) {
			// we are not in a text node, just insert the element at the corresponding position
			offSize = this.children().size();
			if (offset > offSize) {
				offset = offSize;
			}
			if (offset <= 0) {
				this.prepend(content);
			} else {
				this.children().eq(offset - 1).after(content);
			}
		} else {
			// we are in a text node so we have to split it at the correct position
			if (offset <= 0) {
				this.before(content);
			} else if (offset >= this[0].length) {
				this.after(content);
			} else {
				fullText = this[0].data;
				this[0].data = fullText.substring(0, offset);
				this.after(fullText.substring(offset, fullText.length));
				this.after(content);
			}
		}
	};

	/**
	 * Make the object contenteditable. Care about browser version (name of contenteditable attribute depends on it)
	 */
	jQuery.fn.contentEditable = function (b) {
		// ie does not understand contenteditable but contentEditable
		// contentEditable is not xhtml compatible.
		var $el = jQuery(this);
		var ce = 'contenteditable';

		// Check
		if (Aloha.browser.msie && parseInt(Aloha.browser.version, 10) == 7) {
			ce = 'contentEditable';
		}

		if (typeof b === 'undefined') {

			// For chrome use this specific attribute. The old ce will only
			// return 'inherit' for nested elements of a contenteditable.
			// The isContentEditable is a w3c standard compliant property which works in IE7,8,FF36+, Chrome 12+
			if (typeof $el[0] === 'undefined') {
				console.warn('The jquery object did not contain any valid elements.'); // die silent
				return undefined;
			}
			if (typeof $el[0].isContentEditable === 'undefined') {
				console.warn('Could not determine whether the is editable or not. I assume it is.');
				return true;
			}

			return $el[0].isContentEditable;
		}

		if (b === '') {
			$el.removeAttr(ce);
		} else {
			if (b && b !== 'false') {
				b = 'true';
			} else {
				b = 'false';
			}
			$el.attr(ce, b);
		}

		return $el;
	};

	/**
	 * jQuery Aloha Plugin.
	 *
	 * Makes the elements in a jQuery selection set Aloha editables.
	 *
	 * @return jQuery container of holding DOM elements that have been
	 *         aloha()fied.
	 * @api
	 */
	jQuery.fn.aloha = function () {
		var $elements = this;
		Aloha.bind('aloha-plugins-loaded', function () {
			$elements.each(function (_, elem) {
				if (!Aloha.isEditable(elem)) {
					new Aloha.Editable(jQuery(elem)).init();
				}
			});
		});
		return $elements;
	};

	/**
	 * jQuery destroy elements as editable
	 *
	 * destroy all mached elements editable capabilities
	 * @return	jQuery object for the matched elements
	 * @api
	 */
	jQuery.fn.mahalo = function () {
		return this.each(function () {
			if (Aloha.isEditable(this)) {
				Aloha.getEditableById(jQuery(this).attr('id')).destroy();
			}
		});
	};

	/**
	 * jQuery alohaText gets contents for an Aloha Editor editable
	 *
	 * getContents forall editable
	 * @return	jQuery object for the matched elements
	 * @api
	 */
	jQuery.fn.alohaText = function () {
		return this.each(function () {
			if (Aloha.isEditable(this)) {
				Aloha.getEditableById(jQuery(this).attr('id')).getContents();
			}
		});
	};

	/**
	 * jQuery Extension
	 * new Event which is triggered whenever a selection (length >= 0) is made in
	 * an Aloha Editable element
	 */
	jQuery.fn.contentEditableSelectionChange = function (callback) {
		var that = this;

		// update selection when keys are pressed
		this.keyup(function (event) {
			var rangeObject = Selection.getRangeObject();
			callback(event);
		});

		// update selection on doubleclick (especially important for the first automatic selection, when the Editable is not active yet, but is at the same time activated as the selection occurs
		this.dblclick(function (event) {
			callback(event);
		});

		// update selection when text is selected
		this.mousedown(function (event) {
			// remember that a selection was started
			that.selectionStarted = true;
		});

		jQuery(document).mouseup(function (event) {
			Selection.eventOriginalTarget = that;
			if (that.selectionStarted) {
				callback(event);
			}
			Selection.eventOriginalTarget = false;
			that.selectionStarted = false;
		});

		return this;
	};

	/**
	 * Fetch the outerHTML of an Element
	 * @version 1.0.0
	 * @date February 01, 2011
	 * @package jquery-sparkle {@link http://www.balupton/projects/jquery-sparkle}
	 * @author Benjamin Arthur Lupton {@link http://balupton.com}
	 * @copyright 2011 Benjamin Arthur Lupton {@link http://balupton.com}
	 * @license MIT License {@link http://creativecommons.org/licenses/MIT/}
	 * @return {String} outerHtml
	 */
	jQuery.fn.outerHtml = jQuery.fn.outerHtml || function () {
		var $el = jQuery(this),
			el = $el.get(0);
		if (typeof el.outerHTML != 'undefined') {
			return el.outerHTML;
		}
		try {
			// Gecko-based browsers, Safari, Opera.
			return (new XMLSerializer()).serializeToString(el);
		} catch (e) {
			try {
				// Internet Explorer.
				return el.xml;
			} catch (e2) {}
		}
	};

	jQuery.fn.zap = function () {
		return this.each(function () {
			jQuery(this.childNodes).insertBefore(this);
		}).remove();
	};

	jQuery.fn.textNodes = function (excludeBreaks, includeEmptyTextNodes) {
		var ret = [],
			doSomething = function (el) {
				var i, childLength;
				if ((el.nodeType === 3 && jQuery.trim(el.data) && !includeEmptyTextNodes) || (el.nodeType === 3 && includeEmptyTextNodes) || (el.nodeName == "BR" && !excludeBreaks)) {
					ret.push(el);
				} else {
					for (i = 0, childLength = el.childNodes.length; i < childLength; ++i) {
						doSomething(el.childNodes[i]);
					}
				}
			};

		doSomething(this[0]);

		return jQuery(ret);
	};

	/**
	 * extendObjects is like jQuery.extend, but it does not extend arrays
	 */
	jQuery.extendObjects = jQuery.fn.extendObjects = function (arg1, arg2) {
		var options, name, src, copy, copyIsArray, clone,
		    start = 1,
		    target = arg1 || {},
			length = arguments.length,
		    deep = false,
		    i;


		// Handle a deep copy situation
		if (typeof target === "boolean") {
			deep = target;
			target = arg2 || {};
			// skip the boolean and the target
			start = 2;
		}

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

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

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

						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) || true === (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
							if (jQuery.isArray(copy)) {
								// don't extend arrays
								target[name] = copy;
							} else {
								target[name] = jQuery.extendObjects(deep, clone, copy);
							}

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

		// Return the modified object
		return target;
	};

	/**
	 * Check whether element has scrollbars visible
	 */
	function hasScroll(el, index, match) {
		if (!el.style) {
			return false;
		}
		var $el = jQuery(el), sX = $el.css('overflow-x'), sY = $el
			.css('overflow-y'), hidden = 'hidden', visible = 'visible',
			scroll = 'scroll', axis = match[3];

		if (!axis) {
			if (sX === sY && (sY === hidden || sY === visible)) {
				return false;
			}
			if (sX === scroll || sY === scroll) {
				return true;
			}
		} else if (axis === 'x') {
			if (sX === hidden || sX === visible) {
				return false;
			}
			if (sX === scroll) {
				return true;
			}
		} else if (axis === 'y') {
			if (sY === hidden || sY === visible) {
				return false;
			}
			if (sY === scroll) {
				return true;
			}
		}

		// Compare client and scroll dimensions to see if a scrollbar is
		// needed

		return $el.innerHeight() < el.scrollHeight
				|| $el.innerWidth() < el.scrollWidth;
	}

	jQuery.expr[':'].hasScroll = hasScroll;

	/*
	 * jQuery Hotkeys Plugin
	 * Copyright 2010, John Resig
	 * Dual licensed under the MIT or GPL Version 2 licenses.
	 *
	 * Based upon the plugin by Tzury Bar Yochay:
	 * http://github.com/tzuryby/hotkeys
	 *
	 * Original idea by:
	 * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
	 */

	jQuery.hotkeys = {
		version: "0.8",

		specialKeys: {
			8: "backspace",
			9: "tab",
			13: "return",
			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",
			45: "insert",
			46: "del",
			96: "0",
			97: "1",
			98: "2",
			99: "3",
			100: "4",
			101: "5",
			102: "6",
			103: "7",
			104: "8",
			105: "9",
			106: "*",
			107: "+",
			109: "-",
			110: ".",
			111: "/",
			112: "f1",
			113: "f2",
			114: "f3",
			115: "f4",
			116: "f5",
			117: "f6",
			118: "f7",
			119: "f8",
			120: "f9",
			121: "f10",
			122: "f11",
			123: "f12",
			144: "numlock",
			145: "scroll",
			188: ",",
			190: ".",
			191: "/",
			224: "meta"
		},

		shiftNums: {
			"`": "~",
			"1": "!",
			"2": "@",
			"3": "#",
			"4": "$",
			"5": "%",
			"6": "^",
			"7": "&",
			"8": "*",
			"9": "(",
			"0": ")",
			"-": "_",
			"=": "+",
			";": ": ",
			"'": "\"",
			",": "<",
			".": ">",
			"/": "?",
			"\\": "|"
		}
	};

	function applyKeyHandler(handler, context, args, event) {
		// Don't fire in text-accepting inputs that we didn't directly bind to
		if (context !== event.target && (/textarea|input|select/i.test(event.target.nodeName) || event.target.type === "text")) {
			return;
		}
		return handler.apply(context, args);
	}

	function keyHandler(handleObj) {
		var origHandler, keys, handle, i;

		// Only care when a possible input has been specified
		if (typeof handleObj.data !== "string") {
			return;
		}

		origHandler = handleObj.handler;
		keys = handleObj.data.toLowerCase().split(" ");
		handle = {};

		for (i = 0; i < keys.length; i++) {
			handle[keys[i]] = true;
		}

		handleObj.handler = function (event) {
			// The original comment that was added with this condition says:
			// "Don't fire in contentEditable true elements"
			// But this is incorrect.
			// What this condition does is it skips hotkey events for
			// any target unless it is directly bound.
			// The condition event.target.contentEditable !== true will
			// always be true, because contentEditable is a string
			// attribute that is never strictly equal true.
			//if (this !== event.target && event.target.contentEditable !== true) {
			//return;
			//}
			// Below is what this condition really does. Ideally, I'd
			// like to remove this condition since it was not there in
			// the original implementation by John Resig and it could
			// interfere with other plugins, but when I removed it, I
			// was unable to input any space characters into an
			// editable.
			// TODO figure out a way to safely remove this
			if (this !== event.target) {
				return;
			}

			// Keypress represents characters, not special keys
			var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which],
				modif = "",
				character;

			// check combinations (alt|ctrl|shift+anything)
			if (event.altKey && special !== "alt") {
				modif += "alt+";
			}

			if (event.ctrlKey && special !== "ctrl") {
				modif += "ctrl+";
			}

			// TODO: Need to make sure this works consistently across platforms
			if (event.metaKey && !event.ctrlKey && special !== "meta") {
				modif += "meta+";
			}

			if (event.shiftKey && special !== "shift") {
				modif += "shift+";
			}

			if (special) {
				if (handle[modif + special]) {
					return applyKeyHandler(origHandler, this, arguments, event);
				}
			} else {
				character = String.fromCharCode(event.which).toLowerCase();

				if (handle[modif + character]) {
					return applyKeyHandler(origHandler, this, arguments, event);
				}

				if (handle[modif + jQuery.hotkeys.shiftNums[character]]) {
					return applyKeyHandler(origHandler, this, arguments, event);
				}

				// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
				if (modif === "shift+") {
					if (handle[jQuery.hotkeys.shiftNums[character]]) {
						return applyKeyHandler(origHandler, this, arguments, event);
					}
				}
			}
		};
	}

	jQuery.each(['keydown', 'keyup', 'keypress'], function () {
		jQuery.event.special[this] = {
			add: keyHandler
		};
	});

});

/* sidebar.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * @todo: - Make the sidebars resizable using drag handles.
 *        - Make overlayPage setting settable from external config.
 */

define('aloha/sidebar',[
	'aloha/core',
	'jquery',
	'aloha/selection',
	'PubSub'
], function (
	Aloha,
	$,
	Selection,
	PubSub
) {
	

	var uid = +(new Date());

	// Extend jQuery easing animations.
	if (!$.easing.easeOutExpo) {
		$.extend($.easing, {
			easeOutExpo: function (x, t, b, c, d) {
				return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
			},
			easeOutElastic: function (x, t, b, c, d) {
				var m = Math,
					s = 1.70158,
					p = 0,
					a = c;
				if (!t) {
					return b;
				}
				if ((t /= d) == 1) {
					return b + c;
				}
				if (!p) {
					p = d * 0.3;
				}
				if (a < m.abs(c)) {
					a = c;
					s = p / 4;
				} else {
					s = p / (2 * m.PI) * m.asin(c / a);
				}
				return a * m.pow(2, -10 * t) * m.sin((t * d - s) * (2 * m.PI) / p) + c + b;
			}
		});
	}

	var Panel = function Panel(opts) {
		this.id = null;
		this.folds = {};
		this.button = null;
		this.title = $('<div class="aloha-sidebar-panel-title">' + '<span class="aloha-sidebar-panel-title-arrow"></span>' + '<span class="aloha-sidebar-panel-title-text">Untitled</span>' + '</div>');
		this.content = $('<div class="aloha-sidebar-panel-content">' + '<div class="aloha-sidebar-panel-content-inner">' + '<div class="aloha-sidebar-panel-content-inner-text"></div>' + '</div>' + '</div>');
		this.element = null;
		this.effectiveElement = null;
		this.expanded = false;
		this.isActive = true;
		this.init(opts);
	};

	var Sidebar = function Sidebar(opts) {
		var sidebar = this;
		this.id = 'aloha-sidebar-' + (++uid);
		this.panels = {};
		this.container = $('<div class="aloha-ui aloha-sidebar-bar">' + '<div class="aloha-sidebar-handle">' + '<span class="aloha-sidebar-handle-icon"></span>' + '</div>' + '<div class="aloha-sidebar-inner">' + '<ul class="aloha-sidebar-panels"></ul>' + '</div>' + '</div>');
		this.width = 300;
		this.opened = false;
		this.isOpen = false;
		this.isCompletelyOpen = false;
		this.settings = {
			// We automatically set this to true when we are in IE, where
			// rotating elements using filters causes undesirable rendering
			// ugliness.  Our solution is to fallback to swapping icon images.
			// We set this as a sidebar property so that it can overridden by
			// whoever thinks they are smarter than we are.
			rotateIcons: !Aloha.browser.msie,
			overlayPage: true
		};

		$(function () {
			if (!((typeof Aloha.settings.sidebar !== 'undefined') && Aloha.settings.sidebar.disabled)) {
				sidebar.init(opts);
			}
		});
	};

	/**
	 * The last calculated view port height.
	 * @type {number}
	 */
	var previousViewportHeight = null;
	var previousActivePanelIds = null;

	$.extend(Sidebar.prototype, {

		// We build as much of the sidebar as we can before appending it to DOM
		// to minimize reflow.
		init: function (opts) {
			var that = this;
			var panels;

			if (typeof opts === 'object') {
				panels = opts.panels;
				delete opts.panels;
			}

			$.extend(this, opts);

			if (typeof panels === 'object') {
				$.each(panels, function () {
					that.addPanel(this, true);
				});
			}

			var bar = this.container;

			if (this.position === 'right') {
				bar.addClass('aloha-sidebar-right');
			}

			bar.hide().appendTo($('body')).click(function () {
				that.barClicked.apply(that, arguments);
			}).find('.aloha-sidebar-panels').width(this.width);

			// IE7 needs us to explicitly set the container width, since it is
			// unable to determine it on its own.
			bar.width(this.width);
			this.width = bar.width();

			this.updateHeight();
			this.initToggler();

			this.container.css(this.position === 'right' ? 'marginRight' : 'marginLeft', -this.width);

			if (this.opened) {
				this.open(0);
			}

			this.toggleHandleIcon(this.isOpen);
			this.subscribeToEvents();

			$(window).resize(function () {
				that.updateHeight();
				that.correctHeight();
			});

			this.correctHeight();
		},

		show: function () {
			this.container.css('display', 'block');
			return this;
		},

		hide: function () {
			this.container.css('display', 'none');
			return this;
		},

		/**
		 * Determines the effective elements at the current selection.
		 * Iterates through all panels and checks whether the panel should be
		 * activated for any of the effective elements in the selection.
		 *
		 * @param {Aloha.RangeObject} range The current selection range.
		 */
		checkActivePanels: function (range) {
			var effective = [];

			if (typeof range !== 'undefined' && typeof range.markupEffectiveAtStart !== 'undefined') {
				var l = range.markupEffectiveAtStart.length;
				var i;
				for (i = 0; i < l; ++i) {
					effective.push($(range.markupEffectiveAtStart[i]));
				}
			}

			var that = this;

			$.each(this.panels, function () {
				that.showActivePanel(this, effective);
			});

			this.correctHeight();
		},

		subscribeToEvents: function () {
			var that = this;

			PubSub.sub('aloha.selection.context-change', function (message) {
				if (that.isOpen) {
					that.checkActivePanels(message.range);
				}
				that.lastRange = message.range;
			});

			Aloha.bind('aloha-editable-deactivated', function (event, params) {
				if (that.isOpen) {
					that.checkActivePanels();
				}
				that.lastRange = null;
			});

			this.container.mousedown(function (e) {
				e.originalEvent.stopSelectionUpdate = true;
				Aloha.eventHandled = true;
			});

			this.container.mouseup(function (e) {
				e.originalEvent.stopSelectionUpdate = true;
				Aloha.eventHandled = false;
			});
		},

		/**
		 * Dynamically set appropriate heights for panels.
		 * The height for each panel is determined by the amount of space that
		 * is available in the viewport and the number of panels that need to
		 * share that space.
		 */
		correctHeight: function () {
			if (!this.isOpen) {
				return;
			}
			if (!this.isCompletelyOpen) {
				this.correctHeightWhenCompletelyOpen = true;
				return;
			}

			var viewportHeight = $(window).height();
			var activePanelIds = [];
			var panels = [];
			var panelId;
			for (panelId in this.panels) {
				if (this.panels.hasOwnProperty(panelId)) {
					if (this.panels[panelId].isActive) {
						panels.push(this.panels[panelId]);
						activePanelIds.push(panelId);
					}
				}
			}

			if (0 === panels.length) {
				return;
			}

			activePanelIds = activePanelIds.sort().join(',');

			if (previousActivePanelIds === activePanelIds && previousViewportHeight === viewportHeight) {
				return;
			}

			previousViewportHeight = viewportHeight;
			previousActivePanelIds = activePanelIds;

			var height = this.container.find('.aloha-sidebar-inner').height();
			var remainingHeight = height - ((panels[0].title.outerHeight() + 10) * panels.length);
			var panel;
			var targetHeight;
			var panelInner;
			var panelText;
			var undone;
			var toadd = 0;
			var math = Math;
			var j;

			while (panels.length > 0 && remainingHeight > 0) {
				remainingHeight += toadd;
				toadd = 0;
				undone = [];

				for (j = panels.length - 1; j >= 0; --j) {
					panel = panels[j];
					panelInner = panel.content.find('.aloha-sidebar-panel-content-inner');

					targetHeight = math.min(
						panelInner.height('auto').height(),
						math.floor(remainingHeight / (j + 1))
					);

					panelInner.height(targetHeight);
					remainingHeight -= targetHeight;
					panelText = panelInner.find('.aloha-sidebar-panel-content-inner-text');

					if (panelText.height() > targetHeight) {
						undone.push(panel);
						toadd += targetHeight;
						panelInner.css({
							'overflow-x': 'hidden',
							'overflow-y': 'scroll'
						});
					} else {
						panelInner.css('overflow-y', 'hidden');
					}

					if (panel.expanded) {
						panel.expand();
					}
				}

				panels = undone;
			}
		},

		/**
		 * Checks whether this panel should be activated (ie: made visible) for
		 * any of the elements specified in a given list of elements.
		 *
		 * We have to add a null object to the list of elements to allow us to
		 * check whether the panel should be visible when we have no effective
		 * elements in the current selection
		 *
		 * @param {object} panel The Panel object we will test
		 * @param {Array.<jQuery.<HTMLElement>>} elements The effective
		 *                                                elements, any of
		 *                                                which may activate
		 *                                                the panel.
		 */
		showActivePanel: function (panel, elements) {
			elements.push(null);

			var li = panel.content.parent('li');
			var activeOn = panel.activeOn;
			var effective = $();
			var count = 0;
			var j = elements.length;
			var i;

			for (i = 0; i < j; ++i) {
				if (activeOn(elements[i])) {
					++count;
					if (elements[i]) {
						$.merge(effective, elements[i]);
					}
				}
			}

			if (count) {
				panel.activate(effective);
			} else {
				panel.deactivate();
			}
		},

		/**
		 * Sets up the functionality, event listeners, and animation of the
		 * sidebar handle
		 */
		initToggler: function () {
			var that = this;
			var bar = this.container;
			var icon = bar.find('.aloha-sidebar-handle-icon');
			var toggledClass = 'aloha-sidebar-toggled';
			var bounceTimer;
			var isRight = (this.position === 'right');

			if (this.opened) {
				this.rotateHandleArrow(isRight ? 0 : 180, 0);
			}

			// configure the position of the sidebar handle
			$(function () {
				if (typeof Aloha.settings.sidebar !== 'undefined' && Aloha.settings.sidebar.handle && Aloha.settings.sidebar.handle.top) {
					$(bar.find('.aloha-sidebar-handle'))[0].style.top = Aloha.settings.sidebar.handle.top;
				}
			});

			bar.find('.aloha-sidebar-handle').click(function () {
				if (bounceTimer) {
					clearInterval(bounceTimer);
				}

				icon.stop().css('marginLeft', 4);

				if (that.isOpen) {
					$(this).removeClass(toggledClass);
					that.close();
					that.isOpen = false;
				} else {
					$(this).addClass(toggledClass);
					that.open();
					that.isOpen = true;
				}
			}).hover(function () {
				var flag = that.isOpen ? -1 : 1;

				if (bounceTimer) {
					clearInterval(bounceTimer);
				}

				icon.stop();

				$(this).stop().animate(
					isRight ? {
						marginLeft: '-=' + (flag * 5)
					} : {
						marginRight: '-=' + (flag * 5)
					},
					200
				);

				bounceTimer = setInterval(function () {
					flag *= -1;
					icon.animate(
						isRight ? {
							left: '-=' + (flag * 4)
						} : {
							right: '-=' + (flag * 4)
						},
						300
					);
				}, 300);
			}, function () {
				if (bounceTimer) {
					clearInterval(bounceTimer);
				}

				icon.stop().css(isRight ? 'left' : 'right', 5);

				$(this).stop().animate(
					isRight ? {
						marginLeft: 0
					} : {
						marginRight: 0
					},
					600,
					'easeOutElastic'
				);
			});
		},

		/**
		 * Rounds the top corners of the first visible panel, and the bottom
		 * corners of the last visible panel elements in the panels ul list.
		 * @deprecated
		 * @fixme: css3
		 */
		roundCorners: function () {

			var bar = this.container;
			var lis = bar.find('.aloha-sidebar-panels>li:not(.aloha-sidebar-deactivated)');
			var topClass = 'aloha-sidebar-panel-top';
			var bottomClass = 'aloha-sidebar-panel-bottom';

			bar.find('.aloha-sidebar-panel-top, .aloha-sidebar-panel-bottom').removeClass(topClass).removeClass(bottomClass);

			lis.first().find('.aloha-sidebar-panel-title').addClass(topClass);
			lis.last().find('.aloha-sidebar-panel-content').addClass(bottomClass);
		},

		/**
		 * Updates the height of the inner div of the sidebar. This is done
		 * whenever the viewport is resized.
		 */
		updateHeight: function () {
			var h = $(window).height();
			this.container.height(h).find('.aloha-sidebar-inner').height(h);
		},

		/**
		 * Delegate all sidebar onclick events to the container.
		 * Then use handleBarclick method until we bubble up to the first
		 * significant element that we can interact with.
		 */
		barClicked: function (ev) {
			this.handleBarclick($(ev.target));
		},

		/**
		 * We handle all click events on the sidebar from here--dispatching
		 * calls to which ever methods that should be invoked for the each
		 * interaction.
		 */
		handleBarclick: function (el) {
			if (el.hasClass('aloha-sidebar-panel-title')) {
				this.togglePanel(el);
			} else if (!el.hasClass('aloha-sidebar-panel-content') && !el.hasClass('aloha-sidebar-handle') && !el.hasClass('aloha-sidebar-bar')) {
				this.handleBarclick(el.parent());
			}
		},

		getPanelById: function (id) {
			return this.panels[id];
		},

		getPanelByElement: function (el) {
			var li = (el[0].tagName === 'LI') ? el : el.parent('li');
			return this.getPanelById(li[0].id);
		},

		togglePanel: function (el) {
			this.getPanelByElement(el).toggle();
		},

		/**
		 * Animation to rotate the sidebar arrow
		 *
		 * @param {number} angle The angle two which the arrow should rotate
		 *						 (0 or 180).
		 * @param {number|String} duration (Optional) How long the animation
		 *                                 should play for.
		 */
		rotateHandleIcon: function (angle, duration) {
			var arr = this.container.find('.aloha-sidebar-handle-icon');
			arr.animate({
				angle: angle
			}, {
				duration: (typeof duration === 'number' || typeof duration === 'string') ? duration : 500,
				easing: 'easeOutExpo',
				step: function (val, fx) {
					arr.css({
						'-o-transform': 'rotate(' + val + 'deg)',
						'-webkit-transform': 'rotate(' + val + 'deg)',
						'-moz-transform': 'rotate(' + val + 'deg)',
						'-ms-transform': 'rotate(' + val + 'deg)'
						// We cannot use Microsoft Internet Explorer filters
						// because Microsoft Internet Explore 8 does not support
						// Microsoft Internet Explorer filters correctly. It
						// breaks the layout
						// filter             : 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (angle / 90) + ')'
					});
				}
			});
		},

		/**
		 * Sets the handle icon to the "i am opened, click me to close the
		 * sidebar" state, or vice versa. The direction of the arrow depends
		 * on whether the sidebar is on the left or right, and whether it is
		 * in an opened state or not.
		 *
		 * @param {boolean} isOpen Whether or not the sidebar is in the opened
		 *                         state.
		 */
		toggleHandleIcon: function (isOpen) {
			var isPointingLeft = (this.position === 'right') ^ isOpen;

			if (this.settings.rotateIcons) {
				this.rotateHandleIcon(isPointingLeft ? 180 : 0, 0);
			} else {
				var icon = this.container.find('.aloha-sidebar-handle-icon');

				if (isPointingLeft) {
					icon.addClass('aloha-sidebar-handle-icon-left');
				} else {
					icon.removeClass('aloha-sidebar-handle-icon-left');
				}
			}
		},

		/**
		 * Slides the sidebar into view
		 */
		open: function (duration, callback) {
			if (this.isOpen) {
				return this;
			}

			var isRight = (this.position === 'right');
			var anim = isRight ? {
				marginRight: 0
			} : {
				marginLeft: 0
			};
			var sidebar = this;

			this.toggleHandleIcon(true);
			this.container.animate(anim, (typeof duration === 'number' || typeof duration === 'string') ? duration : 500, 'easeOutExpo');

			if (!this.settings.overlayPage) {
				$('body').animate(
					isRight ? {
						marginRight: '+=' + this.width
					} : {
						marginLeft: '+=' + this.width
					},
					500,
					'easeOutExpo',
					function () {
						sidebar.isCompletelyOpen = true;
						if (sidebar.correctHeightWhenCompletelyOpen) {
							sidebar.correctHeight();
						}
					}
				);
			}

			this.isOpen = true;
			this.correctHeight();
			if (this.lastRange) {
				this.checkActivePanels(this.lastRange);
			}
			$('body').trigger('aloha-sidebar-opened', this);

			return this;
		},

		/**
		 * Slides that sidebar out of view.
		 */
		close: function (duration, callback) {
			if (!this.isOpen) {
				return this;
			}

			var isRight = (this.position === 'right');
			var anim = isRight ? {
				marginRight: -this.width
			} : {
				marginLeft: -this.width
			};

			this.toggleHandleIcon(false);
			this.container.animate(anim, (typeof duration === 'number' || typeof duration === 'string') ? duration : 500, 'easeOutExpo');

			if (!this.settings.overlayPage) {
				$('body').animate(
					isRight ? {
						marginRight: '-=' + this.width
					} : {
						marginLeft: '-=' + this.width
					},
					500,
					'easeOutExpo'
				);
			}

			this.isOpen = false;
			this.isCompletelyOpen = false;

			return this;
		},

		/**
		 * Activates the given panel and passes to it the given element as the
		 * the effective that we want it to think activated it.
		 *
		 * @param {object|String} panel Panel instance or the id of a panel
		 *								object.
		 * @param {jQuery} element Element to pass to the panel as effective
		 *	                       element (the element that activated it).
		 */
		activatePanel: function (panel, element) {
			if (typeof panel === 'string') {
				panel = this.getPanelById(panel);
			}

			if (panel) {
				panel.activate(element);
			}

			this.roundCorners();

			return this;
		},

		/**
		 * Invokes the expand method for the given panel so that it expands its
		 * height to display its contents
		 *
		 * @param {object|String} panel Panel instance or the id of a panel
		 *                              object.
		 * @param {funtion} callback
		 */
		expandPanel: function (panel, callback) {
			if (typeof panel === 'string') {
				panel = this.getPanelById(panel);
			}

			if (panel) {
				panel.expand(callback);
			}

			return this;
		},

		/**
		 * Collapses the panel contents by invoking the given panel's collapse
		 * method.
		 *
		 * @param {object|String} panel Panel instance or the id of a panel
		 *								object.
		 * @param {funtion} callback
		 */
		collapsePanel: function (panel, callback) {
			if (typeof panel === 'string') {
				panel = this.getPanelById(panel);
			}

			if (panel) {
				panel.collapse(callback);
			}

			return this;
		},

		/**
		 * Adds a panel to this sidebar instance.
		 * We try and build as much of the panel DOM as we can before inserting
		 * it into the DOM in order to reduce reflow.
		 *
		 * @param {object} panel - either a panel instance or an associative
		 *			   array containing settings for the construction
		 *			   of a new panel.
		 * @param {boolean} deferRounding - (Optional) If true, the rounding-off
		 *				    of the top most and bottom most panels
		 *				    will not be automatically done. Set
		 *				    this to true when adding a lot of panels
		 *				    at once.
		 * @return {object} The newly created panel.
		 */
		addPanel: function (panel, deferRounding) {
			if (!(panel instanceof Panel)) {
				if (!panel.width) {
					panel.width = this.width;
				}
				panel.sidebar = this;
				panel = new Panel(panel);
			}

			this.panels[panel.id] = panel;
			this.container.find('.aloha-sidebar-panels').append(panel.element);
			this.checkActivePanels(Selection.getRangeObject());
			return panel;
		}

	});

	// ------------------------------------------------------------------------
	// Panel prototype
	// ------------------------------------------------------------------------
	$.extend(Panel.prototype, {

		init: function (opts) {
			this.setTitle(opts.title).setContent(opts.content);

			delete opts.title;
			delete opts.content;

			$.extend(this, opts);

			if (!this.id) {
				this.id = 'aloha-sidebar-' + (++uid);
			}

			var li = this.element = $('<li id="' + this.id + '">').append(this.title, this.content);

			if (this.expanded) {
				this.content.height('auto');
			}

			this.toggleTitleIcon(this.expanded);
			this.coerceActiveOn();

			// Disable text selection on title element.
			this.title.attr('unselectable', 'on').css('-moz-user-select', 'none').each(function () {
				this.onselectstart = function () {
					return false;
				};
			});

			if (typeof this.onInit === 'function') {
				this.onInit.apply(this);
			}
		},

		/**
		 * @param {boolean} isExpanded Whether or not the panel is in an
		 *                             expanded state.
		 */
		toggleTitleIcon: function (isExpanded) {
			if (this.sidebar.settings.rotateIcons) {
				this.rotateTitleIcon(isExpanded ? 90 : 0);
			} else {
				var icon = this.title.find('.aloha-sidebar-panel-title-arrow');

				if (isExpanded) {
					icon.addClass('aloha-sidebar-panel-title-arrow-down');
				} else {
					icon.removeClass('aloha-sidebar-panel-title-arrow-down');
				}
			}
		},

		/**
		 * Normalizes the activeOn property into a predicate function.
		 */
		coerceActiveOn: function () {
			if (typeof this.activeOn !== 'function') {
				var activeOn = this.activeOn;

				this.activeOn = (function () {
					var typeofActiveOn = typeof activeOn,
						fn;

					if (typeofActiveOn === 'boolean') {
						fn = function () {
							return activeOn;
						};
					} else if (typeofActiveOn === 'undefined') {
						fn = function () {
							return true;
						};
					} else if (typeofActiveOn === 'string') {
						fn = function (el) {
							return el ? el.is(activeOn) : false;
						};
					} else {
						fn = function () {
							return false;
						};
					}

					return fn;
				}());
			}
		},

		/**
		 * Activates (displays) this panel.
		 */
		activate: function (effective) {
			this.isActive = true;
			this.content.parent('li').show().removeClass('aloha-sidebar-deactivated');
			this.effectiveElement = effective;
			if (typeof this.onActivate === 'function') {
				this.onActivate.call(this, effective);
			}
		},

		/**
		 * Hides this panel.
		 */
		deactivate: function () {
			if (!this.isActive) {
				return;
			}
			this.isActive = false;
			this.content.parent('li').hide().addClass('aloha-sidebar-deactivated');
			this.effectiveElement = null;
		},

		toggle: function () {
			if (this.expanded) {
				this.collapse();
			} else {
				this.expand();
			}
		},

		/**
		 * Displays the panel's contents.
		 */
		expand: function (callback) {
			var that = this;
			var el = this.content;
			var old_h = el.height();
			var new_h = el.height('auto').height();
			el.height(old_h).stop().animate(
				{height: new_h},
				500,
				'easeOutExpo',
				function () {
					if (typeof callback === 'function') {
						callback.call(that);
					}
				}
			);
			this.element.removeClass('collapsed');
			this.toggleTitleIcon(true);
			this.expanded = true;
			return this;
		},

		/**
		 * Hides the panel's contents--leaving only it's header.
		 */
		collapse: function (duration, callback) {
			var that = this;
			this.element.addClass('collapsed');
			this.content.stop().animate(
				{ height: 3 },
				250,
				'easeOutExpo',
				function () {
					if (typeof callback === 'function') {
						callback.call(that);
					}
				}
			);
			this.toggleTitleIcon(false);
			this.expanded = false;
			return this;
		},

		/**
		 * May also be called by the Sidebar to update title of panel
		 *
		 * @param {string} html Markup string, DOM object, or jQuery object.
		 */
		setTitle: function (html) {
			this.title.find('.aloha-sidebar-panel-title-text').html(html);
			return this;
		},

		/**
		 * May also be called by the Sidebar to update content of panel
		 *
		 * @param {string|jQuery.<HTMLElement>|HTMLElement} html Markup string,
		 *                                                       DOM object, or
		 *                                                       jQuery object.
		 */
		setContent: function (html) {
			// We do this so that empty panels don't appear collapsed
			if (!html || html === '') {
				html = '&nbsp;';
			}

			this.content.find('.aloha-sidebar-panel-content-inner-text').html(html);
			return this;
		},

		rotateTitleIcon: function (angle, duration) {
			var arr = this.title.find('.aloha-sidebar-panel-title-arrow');
			arr.animate({
				angle: angle
			}, {
				duration: (typeof duration === 'number') ? duration : 500,
				easing: 'easeOutExpo',
				step: function (val, fx) {
					arr.css({
						'-o-transform': 'rotate(' + val + 'deg)',
						'-webkit-transform': 'rotate(' + val + 'deg)',
						'-moz-transform': 'rotate(' + val + 'deg)',
						'-ms-transform': 'rotate(' + val + 'deg)'
						// filter              : 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (angle / 90) + ')'
					});
				}
			});
		},

		/**
		 * Walks up the ancestors chain for the given effective element, and
		 * renders subpanels using the specified renderer function.
		 *
		 * @param {jQuery.<HTMLElement>} effective The effective element, whose
		 *                                         lineage we want to render.
		 * @param {function} renderer (Optional) function that will render each
		 *                                       element in the parental
		 *                                       lineage of the effective
		 *                                       element.
		 */
		renderEffectiveParents: function (effective, renderer) {
			var el = effective.first();
			var content = [];
			var path = [];
			var activeOn = this.activeOn;
			var l;
			var pathRev;

			while (el.length > 0 && !el.is('.aloha-editable')) {
				if (activeOn(el)) {
					path.push('<span>' + el[0].tagName.toLowerCase() + '</span>');
					l = path.length;
					pathRev = [];
					while (l--) {
						pathRev.push(path[l]);
					}
					content.push(
						'<div class="aloha-sidebar-panel-parent">'
							+ '<div class="aloha-sidebar-panel-parent-path">'
							+ pathRev.join('')
							+ '</div>'
							+ '<div class="aloha-sidebar-panel-parent-content'
							+ 'aloha-sidebar-opened">'
							+ ((typeof renderer === 'function') ? renderer(el) : '----') + '</div>' + '</div>'
					);
				}
				el = el.parent();
			}

			this.setContent(content.join(''));

			$('.aloha-sidebar-panel-parent-path').click(function () {
				var $content = $(this).parent().find('.aloha-sidebar-panel-parent-content');
				if ($content.hasClass('aloha-sidebar-opened')) {
					$content.hide().removeClass('aloha-sidebar-opened');
				} else {
					$content.show().addClass('aloha-sidebar-opened');
				}
			});

			this.content.height('auto').find('.aloha-sidebar-panel-content-inner').height('auto');
		}

	});

	var left = new Sidebar({
		position: 'left',
		width: 250 // TODO define in config
	});

	var right = new Sidebar({
		position: 'right',
		width: 250 // TODO define in config
	});

	Aloha.Sidebar = {
		left: left,
		right: right
	};

	return Aloha.Sidebar;
});

/* position.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
// Start Closure
// Ensure GENTICS Namespace
window.GENTICS = window.GENTICS || {};
window.GENTICS.Utils = window.GENTICS.Utils || {};
define('util/position',['jquery'], function (jQuery) {
	

	var	$ = jQuery,
		GENTICS = window.GENTICS,
		Class = window.Class,
		console = window.console;

	/**
	 * position utility, which will provide scroll and mouse positions
	 * please note that the positions provided by this class are not
	 * realtime - instead they are calculated with a 0.5 second delay
	 */
	GENTICS.Utils.Position = {};

	/**
	 * jquery reference to the window object
	 */
	GENTICS.Utils.Position.w = jQuery(window);

	/**
	 * contains the current scroll top and left position, and indicates if the user is currently scrolling
	 * @api
	 */
	GENTICS.Utils.Position.Scroll = {
		top: 0,
		left: 0,
		isScrolling: false
	};

	/**
	 * contains the scroll corrections to apply on special cases (ribbon for example)
	 * @api
	 */
	GENTICS.Utils.Position.ScrollCorrection = {
		top: 100,
		left: 50
	};

	/**
	 * contains the current mouse position (x,y) as well as an indicator if the mouse is moving
	 * @api
	 */
	GENTICS.Utils.Position.Mouse = {
		x: 0,
		y: 0,
		oldX: 0,
		oldY: 0,
		isMoving: false,
		triggeredMouseStop: true
	};

	/**
	 * contains all mousestop callbacks
	 */
	GENTICS.Utils.Position.mouseStopCallbacks = [];

	/**
	 * contains all mousemove callbacks
	 */
	GENTICS.Utils.Position.mouseMoveCallbacks = [];

	/**
	 * updates scroll position and the scrolling status
	 */
	GENTICS.Utils.Position.update = function () {
		// update scroll position
		var st = this.w.scrollTop(),
			sl = this.w.scrollLeft(),
			i;

		if (this.Scroll.isScrolling) {
			if (this.Scroll.top == st && this.Scroll.left == sl) {
				// stopped scrolling
				this.Scroll.isScrolling = false;
			}
		} else {
			if (this.Scroll.top != st || this.Scroll.left != sl) {
				// started scrolling
				this.Scroll.isScrolling = true;
			}
		}

		// update scroll positions
		this.Scroll.top = st;
		this.Scroll.left = sl;

		// check wether the user has stopped moving the mouse
		if (this.Mouse.x == this.Mouse.oldX && this.Mouse.y == this.Mouse.oldY) {
			this.Mouse.isMoving = false;
			// now check if we've triggered the mousestop event
			if (!this.Mouse.triggeredMouseStop) {
				this.Mouse.triggeredMouseStop = true;
				// iterate callbacks
				for (i = 0; i < this.mouseStopCallbacks.length; i++) {
					this.mouseStopCallbacks[i].call();
				}
			}
		} else {
			this.Mouse.isMoving = true;
			this.Mouse.triggeredMouseStop = false;
			// iterate callbacks
			for (i = 0; i < this.mouseMoveCallbacks.length; i++) {
				this.mouseMoveCallbacks[i].call();
			}
		}

		// update mouse positions
		this.Mouse.oldX = this.Mouse.x;
		this.Mouse.oldY = this.Mouse.y;
	};

	/**
	 * adds a callback method which is invoked when the mouse has stopped moving
	 * @param	callback	the callback method to be invoked
	 * @return	index of the callback
	 */
	GENTICS.Utils.Position.addMouseStopCallback = function (callback) {
		this.mouseStopCallbacks.push(callback);
		return (this.mouseStopCallbacks.length - 1);
	};

	/**
	 * adds a callback method which is invoked when the mouse is moving
	 * @param	callback	the callback method to be invoked
	 * @return	index of the callback
	 */
	GENTICS.Utils.Position.addMouseMoveCallback = function (callback) {
		this.mouseMoveCallbacks.push(callback);
		return (this.mouseMoveCallbacks.length - 1);
	};


	// Mousemove Hooks
	jQuery(function () {
		window.setInterval(function () {
			GENTICS.Utils.Position.update();
		}, 500);
	});

	jQuery('html').mousemove(function (e) {
		GENTICS.Utils.Position.Mouse.x = e.pageX;
		GENTICS.Utils.Position.Mouse.y = e.pageY;
	});

});

/* repositoryobjects.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/repositoryobjects',[
	'aloha/core',
	'util/class'
], function (
	Aloha,
	Class
) {
	

	var	GENTICS = window.GENTICS;

	Aloha.RepositoryObject = function () {};

	/**
	 * @namespace Aloha.Repository
	 * @class Document
	 * @constructor
	 *
	 * Abstract Document suitable for most Objects.<br /><br />
	 *
	 * Example:
	 *
	<pre><code>
	 var item = new Aloha.Repository.Document({
		id: 1,
		repositoryId: 'myrepository',
		name: 'Aloha Editor - The HTML5 Editor',
		type: 'website',
		url:'http://aloha-editor.com',
	 });
	</code></pre>
	 *
	 * @param {Object} properties An object with the data.
	 * <div class="mdetail-params"><ul>
	 * <li><code>id</code> : String <div class="sub-desc">Unique identifier</div></li>
	 * <li><code>repositoryId</code> : String <div class="sub-desc">Unique repository identifier</div></li>
	 * <li><code>name</code> : String <div class="sub-desc">Name of the object. This name is used to display</div></li>
	 * <li><code>type</code> : String <div class="sub-desc">The specific object type</div></li>
	 * <li><code>partentId</code> : String (optional) <div class="sub-desc"></div></li>
	 * <li><code>mimetype</code> : String (optional) <div class="sub-desc">MIME type of the Content Stream</div></li>
	 * <li><code>filename</code> : String (optional) <div class="sub-desc">File name of the Content Stream</div></li>
	 * <li><code>length</code> : String (optional) <div class="sub-desc">Length of the content stream (in bytes)</div></li>
	 * <li><code>url</code> : String (optional) <div class="sub-desc">URL of the content stream</div></li>
	 * <li><code>renditions</code> : Array (optional) <div class="sub-desc">Array of different renditions of this object</div></li>
	 * <li><code>localName</code> : String (optional) <div class="sub-desc">Name of the object. This name is used internally</div></li>
	 * <li><code>createdBy</code> : String (optional) <div class="sub-desc">User who created the object</div></li>
	 * <li><code>creationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was created</div></li>
	 * <li><code>lastModifiedBy</code> : String (optional) <div class="sub-desc">User who last modified the object</div></li>
	 * <li><code>lastModificationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was last modified</div></li>
	 * </ul></div>
	 *
	 */
	Aloha.RepositoryDocument = Class.extend({
		_constructor: function (properties) {

			var p = properties;

			this.type = 'document';

			// Basic error checking for MUST attributes
			if (!p.id || !p.name || !p.repositoryId) {
				//				Aloha.Log.error(this, "No valid Aloha Object. Missing MUST property");
				return;
			}

			GENTICS.Utils.applyProperties(this, properties);

			this.baseType = 'document';
		}
		//		/**
		//		 * Not implemented method to generate this JS API doc correctly.
		//		 */
		//		,empty = function() }

	});



	/**
	 * @namespace Aloha.Repository
	 * @class Folder
	 * @constructor
	 * Abstract Folder suitable for most strucural Objects.<br /><br />
	 *
	 * Example:
	 *
	<pre><code>
	 var item = new Aloha.Repository.Folder({
		id: 2,
		repositoryId: 'myrepository',
		name: 'images',
		type: 'directory',
		parentId:'/www'
	 });
	</code></pre>
	 * @param {Object} properties An object with the data.
	 * <div class="mdetail-params"><ul>
	 * <li><code>id</code> : String <div class="sub-desc">Unique identifier</div></li>
	 * <li><code>repositoryId</code> : String <div class="sub-desc">Unique repository identifier</div></li>
	 * <li><code>name</code> : String <div class="sub-desc">Name of the object. This name is used to display</div></li>
	 * <li><code>type</code> : String <div class="sub-desc">The specific object type</div></li>
	 * <li><code>partentId</code> : String (optional) <div class="sub-desc"></div></li>
	 * <li><code>localName</code> : String (optional) <div class="sub-desc">Name of the object. This name is used internally</div></li>
	 * <li><code>createdBy</code> : String (optional) <div class="sub-desc">User who created the object</div></li>
	 * <li><code>creationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was created</div></li>
	 * <li><code>lastModifiedBy</code> : String (optional) <div class="sub-desc">User who last modified the object</div></li>
	 * <li><code>lastModificationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was last modified</div></li>
	 * </ul></div>
	 *
	 */
	Aloha.RepositoryFolder = Class.extend({

		_constructor: function (properties) {

			var p = properties;

			this.type = 'folder';

			// Basic error checking for MUST attributes
			if (!p.id || !p.name || !p.repositoryId) {
				//		Aloha.Log.error(this, "No valid Aloha Object. Missing MUST property");
				return;
			}

			GENTICS.Utils.applyProperties(this, properties);

			this.baseType = 'folder';

		}
		//	/**
		//	* Not implemented method to generate this JS API doc correctly.
		//	*/
		//	,empty = function() {};

	});
});

/* repositorymanager.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/repositorymanager',[
	'jquery',
	'util/class',
	'aloha/core',
	'aloha/console',
	'aloha/repositoryobjects' // Provides Aloha.RepositoryFolder
], function (
	$,
	Class,
	Aloha,
	Console,
	__unused__
) {
	

	/**
	 * Given an input set, returns a new set which is a range of the input set
	 * that maps to the given predicate.
	 *
	 * Prefers native Array.prototype.filter() where available (after JavaScript
	 * 1.6).
	 *
	 * @param {function:boolean} predicate
	 * @return {Array} Sub set of domain
	 */
	var filter = (function (predicate) {
		if (predicate) {
			return function (domain, predicate) {
				return domain.filter(predicate);
			};
		}

		return function (domain, predicate) {
			var codomain = [],
				i,
				len = domain.length;
			for (i = 0; i < len; i++) {
				if (predicate(domain[i])) {
					codomain.push(domain[i]);
				}
			}
			return codomain;
		};
	}(Array.prototype.hasOwnProperty('filter')));

	/**
	 * Bundles results, and meta information in preparation for the JSON Reader.
	 *
	 * Used with query().
	 *
	 * @param {Array.<Document|Folder>} items Results, collected from all
	 *                                        repositories.
	 * @param {object<string, number>} meta Optional object containing metainfo.
	 * @return {object} Result object.
	 */
	function bundle(items, meta) {
		var result = {
			items: items,
			results: items.length
		};
		if (meta) {
			result.numItems = meta.numItems;
			result.hasMoreItems = meta.hasMoreItems;
			result.timeout = meta.timeout;
		}
		return result;
	}

	/**
	 * Passes all the results we have collected to the client through the
	 * callback it specified.
	 *
	 * TODO: Implement sorting based on repository specification sort
	 *       items by weight.
	 * items.sort(function (a, b) {
	 *	return (b.weight || 0) - (a.weight || 0);
	 * });
	 *
	 * @param {function} callback Callback specified by client when invoking
	 *                            the query method.
	 * @param {Array.<Document|Folder>|object<string, number>} results
	 */
	function report(callback, results) {
		callback(results);
	}

	/**
	 * Predicates; used to filter lists of repositories based on whether they
	 * implement a method or not.
	 *
	 * @type {object<string, function(Repository):boolean}
	 */
	var repositoryFilters = {
		query: function (repository) {
			return typeof repository.query === 'function';
		},
		getChildren: function (repository) {
			return typeof repository.getChildren === 'function';
		},
		getSelectedFolder: function (repository) {
			return typeof repository.getSelectedFolder === 'function';
		}
	};

	/**
	 * Repository Manager.
	 *
	 * @namespace Aloha
	 * @class RepositoryManager
	 * @singleton
	 */
	var RepositoryManager = Class.extend({

		repositories: [],

		settings: (Aloha.settings && Aloha.settings.repositories) || {},

		initialized: false,

		/**
		 * Initializes all registered repositories.
		 *
		 *                            ???
		 *                             |
		 *                             v
		 *
		 * Warning: testing has shown that repositories are maybe not loaded yet
		 * (found that case in IE7), so don't rely on that in this init
		 * function.
		 *
		 *                             ^
		 *                             |
		 *                            !!!
		 */
		init: function () {
			var manager = this;
			if (typeof manager.settings.timeout === 'undefined') {
				manager.settings.timeout = 5000;
			}
			var i;
			for (i = 0; i < manager.repositories.length; i++) {
				manager.initRepository(manager.repositories[i]);
			}
			manager.initialized = true;
		},

		/**
		 * Registers a Repository.
		 *
		 * If the repositorie is registered after the Repository Manager is
		 * initialized it will be automatically initialized.
		 *
		 * @param {Repository} repository Repository to register.
		 */
		register: function (repository) {
			var manager = this;
			if (!manager.getRepository(repository.repositoryId)) {
				manager.repositories.push(repository);
				if (manager.initialized) {
					manager.initRepository(repository);
				}
			} else {
				Console.warn(manager, 'A repository with name "'
						+ repository.repositoryId
						+ '" already registerd. Ignoring this.');
			}
		},

		/**
		 * Initializes a repository.
		 *
		 * @param {Repository} repository Repository to initialize.
		 */
		initRepository: function (repository) {
			var manager = this;
			if (!repository.settings) {
				repository.settings = {};
			}
			if (manager.settings[repository.repositoryId]) {
				$.extend(repository.settings,
				         manager.settings[repository.repositoryId]);
			}
			repository.init();
		},

		/**
		 * Returns the repository identified by repositoryId.
		 *
		 * @param {String} id Id of repository to retrieve.
		 * @return {Repository|null} Repository or null if none with the given
		 *                           id is found.
		 */
		getRepository: function (id) {
			var manager = this;
			var i;
			for (i = 0; i < manager.repositories.length; i++) {
				if (manager.repositories[i].repositoryId === id) {
					return manager.repositories[i];
				}
			}
			return null;
		},

		/**
		 * Searches all repositories for repositoryObjects matching query and
		 * repositoryObjectType.
		 *
		 * <pre><code>
		 *  // Example:
		 *  var params = {
		 *      queryString: 'hello',
		 *      objectTypeFilter: ['website'],
		 *      filter: null,
		 *      inFolderId: null,
		 *      orderBy: null,
		 *      maxItems: null,
		 *      skipCount: null,
		 *      renditionFilter: null,
		 *      repositoryId: null
		 *  };
		 *  Aloha.RepositoryManager.query(params, function (items) {
		 *      Console.log(items);
		 *  });
		 * </code></pre>
		 *
		 * @param {object<string, mixed>} params
		 *
		 *       queryString: String             The query string for full text
		 *                                       search.
		 *  objectTypeFilter: Array   (optional) Object types to be retrieved.
		 *            filter: Array   (optional) Attributes that will be
		 *                                       included.
		 *        inFolderId: boolean (optional) Whether or not a candidate
		 *                                       object is a child-object of the
		 *                                       folder object identified by the
		 *                                       given inFolderId (objectId).
		 *          inTreeId: boolean (optional) This indicates whether or
		 *                                       not a candidate object is a
		 *                                       descendant-object of the folder
		 *                                       object identified by the given
		 *                                       inTreeId (objectId).
		 *           orderBy: Array   (optional) example: [{
		 *                                           lastModificationDate: 'DESC',
		 *                                           name: 'ASC'
		 *                                       }]
		 *          maxItems: number  (optional) Number of items to include in
		 *                                       result set.
		 *         skipCount: number  (optional) This is tricky in a merged
		 *                                       multi repository scenario.
		 *   renditionFilter: Array   (optional) Instead of termlist, an
		 *                                       array of kind or mimetype is
		 *                                       expected.  If null or an empty
		 *                                       set, then all renditions are
		 *                                       returned. See
		 *                                       http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310
		 *                                       for renditionFilter.
		 *
		 * @param {function(Document|Folder)} callback Function to be invoked
		 *                                             after the repository
		 *                                             manager has finished
		 *                                             querying all
		 *                                             repositories.
		 */
		query: function (params, callback) {
			var manager = this;

			var i;

			// The merged results, collected from repository responses.
			var results = [];

			// The merged metainfo, collected from repository responses.
			var allmetainfo = {
				numItems: 0,
				hasMoreItems: false
			};

			// A counting semaphore (working in reverse, ie: 0 means free).
			var numOpenQueries;

			// Unless the calling client specifies otherwise, the manager will
			// wait a maximum of 5 seconds for all repositories to be queried
			// and respond. 5 seconds is deemed to be the reasonable time to
			// wait when querying the repository manager in the context of
			// something like autocomplete.
			var timeout = (params.timeout && parseInt(params.timeout, 10))
			           || manager.settings.timeout;

			// When this timer times-out, whatever has been collected in
			// `results' will be returned to the calling client and all further
			// processing aborted.
			var timer = window.setTimeout(function () {
				// Store in metainfo that a timeout occurred.
				allmetainfo = allmetainfo || {};
				allmetainfo.timeout = true;

				if (numOpenQueries > 0) {
					Console.warn(manager, numOpenQueries
							+ ' repositories did not return before the '
							+ 'configured timeout of ' + timeout + 'ms.');
					numOpenQueries = 0;
				}
				clearTimeout(timer);
				report(callback, bundle(results, allmetainfo));
			}, timeout);

			/**
			 * Invoked by each repository when it wants to present its results
			 * to the manager.
			 *
			 * Collects the results from each repository, and decrements the
			 * numOpenQueries semaphore to indicate that there is one less
			 * repository for which the manager is waiting for a reponse.
			 *
			 * If a repository invokes this callback after all openCallbacks
			 * have been closed (ie: numOpenQueries == 0), then the repository
			 * was too late ("missed the ship"), and will be ignored.
			 *
			 * If numOpenQueries decrements to 0 during this call, it means that
			 * the the manager is ready to report the results back to the client
			 * through the report() method.
			 *
			 * @param {Array.<Document|Folder>} items Results returned by the
			 *                                        repository.
			 * @param {object<string, number>} metainfo Optional Metainfo
			 *                                          returned by some
			 *                                          repositories.
			 */
			var process = function (items, metainfo) {
				var repository = this;

				if (0 === numOpenQueries) {
					return;
				}

				if (items && items.length) {

					// Because some negligent repository implementations do not
					// set repositoryId properly.
					if (!items[0].repositoryId) {
						var id = repository.repositoryId;
						var i;
						for (i = 0; i < items.length; i++) {
							items[i].repositoryId = id;
						}
					}

					$.merge(results, items);
				}

				if (metainfo && allmetainfo) {
					allmetainfo.numItems =
						($.isNumeric(metainfo.numItems) &&
						 $.isNumeric(allmetainfo.numItems))
							? allmetainfo.numItems + metainfo.numItems
							: undefined;

					allmetainfo.hasMoreItems =
						(typeof metainfo.hasMoreItems === 'boolean' &&
						 typeof allmetainfo.hasMoreItems === 'boolean')
							? allmetainfo.hasMoreItems || metainfo.hasMoreItems
							: undefined;

					if (metainfo.timeout) {
						allmetainfo.timeout = true;
					}
				} else {

					// Because if even one repository does not return metainfo,
					// so we have no aggregated metainfo at all.
					allmetainfo = undefined;
				}

				Console.debug(manager, 'The repository '
						+ repository.repositoryId + 'returned with '
						+ items.length + ' results.');

				// TODO: how to return the metainfo here?
				if (0 === --numOpenQueries) {
					clearTimeout(timer);
					report(callback, bundle(results, allmetainfo));
				}
			};

			var repositories = params.repositoryId
			                 ? [manager.getRepository(params.repositoryId)]
			                 : manager.repositories;

			var queue = filter(repositories, repositoryFilters.query);

			// If none of the repositories implemented the query method, then
			// don't wait for the timeout, simply report to the client.
			if (0 === queue.length) {
				clearTimeout(timer);
				report(callback, bundle(results, allmetainfo));
				return;
			}

			var makeProcess = function (repository) {
				return function () {
					process.apply(repository, arguments);
				};
			};

			numOpenQueries = queue.length;

			for (i = 0; i < queue.length; i++) {
				queue[i].query(params, makeProcess(queue[i]));
			}
		},

		/**
		 * Retrieves children items.
		 *
		 * @param {object<string,mixed>} params Object with properties.
		 *
		 *  objectTypeFilter: Array   (optional) Object types to be retrieved.
		 *            filter: Array   (optional) Attributes to be retrieved.
		 *        inFolderId: boolean (optional) This indicates whether or not
		 *                                       a candidate object is a
		 *                                       child-object of the folder
		 *                                       object identified by the given
		 *                                       inFolderId (objectId).
		 *           orderBy: Array   (optional) example: [{
		 *                                           lastModificationDate: 'DESC',
		 *                                           name: 'ASC'
		 *                                       }]
		 *          maxItems: number  (optional) number Items to return as a result.
		 *         skipCount: number  (optional) This is tricky in a merged
		 *                                       multi repository scenario.
		 *   renditionFilter: Array   (optional) Instead of termlist an Array
		 *                                       of kind or mimetype is
		 *                                       expected. If null or
		 *                                       Array.length == 0 all
		 *                                       renditions are returned. See
		 *                                       http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310
		 *                                       for renditionFilter.
		 *
		 * @param {function(Document|Folder)} callback Function to be invoked
		 *                                             after the repository
		 *                                             manager has finished
		 *                                             querying all
		 *                                             repositories.
		 */
		getChildren: function (params, callback) {
			var manager = this;

			var i;

			// The marged results, collected from repository responses.
			var results = [];

			// A counting semaphore (working in reverse, ie: 0 means free).
			var numOpenQueries = 0;

			var timeout = (params.timeout && parseInt(params.timeout, 10))
			           || manager.settings.timeout;

			var timer = window.setTimeout(function () {
				if (numOpenQueries > 0) {
					Console.warn(manager, numOpenQueries
							+ ' repositories did not respond before the '
							+ 'configured timeout of ' + timeout + 'ms.');
					numOpenQueries = 0;
				}
				clearTimeout(timer);
				report(callback, results);
			}, timeout);

			var process = function (items) {
				if (0 === numOpenQueries) {
					return;
				}
				if (items) {
					$.merge(results, items);
				}
				if (0 === --numOpenQueries) {
					clearTimeout(timer);
					report(callback, results);
				}
			};

			var repositories = params.repositoryId
			                 ? [manager.getRepository(params.repositoryId)]
			                 : manager.repositories;

			if (params.repositoryFilter && params.repositoryFilter.length) {
				repositories = filter(repositories, function (repository) {
					return -1 < $.inArray(repository.repositoryId,
						params.repositoryFilter);
				});
			}

			// If the inFolderId is the default id of 'aloha', then return all
			// registered repositories as the result set.
			if ('aloha' === params.inFolderId) {
				var hasRepoFilter = params.repositoryFilter
				                 && 0 < params.repositoryFilter.length;

				for (i = 0; i < repositories.length; i++) {
					results.push(new Aloha.RepositoryFolder({
						id: repositories[i].repositoryId,
						name: repositories[i].repositoryName,
						repositoryId: repositories[i].repositoryId,
						type: 'repository',
						hasMoreItems: true
					}));
				}

				clearTimeout(timer);
				report(callback, results);
				return;
			}

			var queue = filter(repositories, repositoryFilters.getChildren);

			if (0 === queue.length) {
				clearTimeout(timer);
				report(callback, results);
				return;
			}

			numOpenQueries = queue.length;

			for (i = 0; i < queue.length; i++) {
				queue[i].getChildren(params, process);
			}
		},

		/**
		 * @fixme: Not tested, but the code for this function does not seem to
		 *        compute repository.makeClean will be undefined
		 *
		 * @todo: Rewrite this function header comment so that is clearer
		 *
		 * Pass an object, which represents an marked repository to corresponding
		 * repository, so that it can make the content clean (prepare for saving)
		 *
		 * @param {jQuery} obj - representing an editable
		 * @return void
		 */
		makeClean: function (obj) {
			// iterate through all registered repositories
			var that = this,
				repository = {},
				i = 0,
				j = that.repositories.length;

			// find all repository tags
			obj.find('[data-gentics-aloha-repository=' + this.prefix + ']').each(function () {
				while (i < j) {
					repository.makeClean(obj);
					i += 1;
				}
				Console.debug(that, 'Passing contents of HTML Element with id { ' + this.attr('id') + ' } for cleaning to repository { ' + repository.repositoryId + ' }');
				repository.makeClean(this);
			});
		},

		/**
		 * Marks an object as repository of this type and with this item.id.
		 * Objects can be any DOM objects as A, SPAN, ABBR, etc. or
		 * special objects such as aloha-aloha_block elements.
		 *
		 * Marks the target obj with two private attributes:
		 * (see http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data)
		 *	- data-gentics-aloha-repository: stores the repositoryId
		 *	- data-gentics-aloha-object-id: stores the object.id
		 *
		 * @param {HTMLElement} obj DOM object to mark.
		 * @param {Aloha.Repository.Object} item Item which is applied to obj,
		 *                                       if set to null, the
		 *                                       "data-GENTICS-..." attributes
		 *                                       are removed.
		 */
		markObject: function (obj, item) {
			if (!obj) {
				return;
			}

			var manager = this, $obj = $(obj);

			if (item) {
				var repository = manager.getRepository(item.repositoryId);
				if (repository) {
					// only mark the object if something changed
					if ($obj.attr('data-gentics-aloha-repository') !== item.repositoryId ||
							$obj.attr('data-gentics-aloha-object-id') !== item.id) {
						$obj.attr({
							'data-gentics-aloha-repository': item.repositoryId,
							'data-gentics-aloha-object-id': item.id
						});
						repository.markObject(obj, item);
					}
				} else {
					Console.error(manager, 'Trying to apply a repository "'
							+ item.name
							+ '" to an object, but item has no repositoryId.');
				}
			} else {
				$obj.removeAttr('data-gentics-aloha-repository')
				    .removeAttr('data-gentics-aloha-object-id');
			}
		},

		/**
		 * Get the object for which the given DOM object is marked from the
		 * repository.
		 *
		 * Will initialize the item cache (per repository) if not already done.
		 *
		 * @param {HTMLElement} element DOM object which probably is marked.
		 * @param {function} callback
		 */
		getObject: function (element, callback) {
			var manager = this;
			var $element = $(element);
			var itemId = $element.attr('data-gentics-aloha-object-id');
			var repositoryId = $element.attr('data-gentics-aloha-repository');
			var repository = manager.getRepository(repositoryId);

			if (repository && itemId) {
				if (!manager.itemCache) {
					manager.itemCache = [];
				}

				var cache = manager.itemCache[repositoryId];
				if (!cache) {
					cache = manager.itemCache[repositoryId] = [];
				}

				if (cache[itemId]) {
					callback([cache[itemId]]);
				} else {
					repository.getObjectById(itemId, function (items) {
						cache[itemId] = items[0];
						callback(items);
					});
				}
			}
		},

		/**
		 * Mark a folder as opened.
		 *
		 * Called by a repository client (eg: repository browser) when a folder
		 * is opened.
		 *
		 * @param {object|Folder} folder Object with property repositoryId.
		 */
		folderOpened: function (folder) {
			var repository = this.getRepository(folder.repositoryId);
			if (typeof repository.folderOpened === 'function') {
				repository.folderOpened(folder);
			}
		},

		/**
		 * Mark a folder as closed.
		 *
		 * Called by a repository client (eg: repository browser) when a folder
		 * is closed.
		 *
		 * @param {object|Folder} folder Object with property repositoryId.
		 */
		folderClosed: function (folder) {
			var repository = this.getRepository(folder.repositoryId);
			if (typeof repository.folderClosed === 'function') {
				repository.folderClosed(folder);
			}
		},

		/**
		 * Mark a folder as selected.
		 *
		 * Called by a repository client (eg: repository browser) when a folder
		 * is selected.
		 *
		 * @param {object|Folder} folder Object with property repositoryId.
		 */
		folderSelected: function (folder) {
			var repository = this.getRepository(folder.repositoryId);
			if (typeof repository.folderSelected === 'function') {
				repository.folderSelected(folder);
			}
		},

		/**
		 * Retrieve the selected folder.
		 *
		 * @return {Folder} Selected folder or null if it cannot be found.
		 */
		getSelectedFolder: function () {
			var repositories = filter(this.repositories,
					repositoryFilters.getSelectedFolder);
			var i;
			var selected;
			for (i = 0; i < repositories.length; i++) {
				selected = repositories[i].getSelectedFolder();
				if (selected) {
					return selected;
				}
			}
			return null;
		},

		/**
		 * Human readable representation of repository manager.
		 *
		 * @return {string}
		 */
		toString: function () {
			return 'repositorymanager';
		}

	});

	Aloha.RepositoryManager = new RepositoryManager();

	return Aloha.RepositoryManager;
});

/* repository.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/repository',[
	'aloha/core',
	'util/class',
	'aloha/repositorymanager'
], function (
	Aloha,
	Class,
	RepositoryManager
) {
	

	//	var
	//		$ = jQuery,
	//		GENTICS = window.GENTICS,
	//		Aloha = window.Aloha,
	//		Class = window.Class;

	/**
	 * Abstract Repository Class. Implement that class for your own repository.
	 * @namespace Aloha.Repository
	 * @class Repository
	 * @constructor
	 * @param {String} repositoryId unique repository identifier
	 * @param {String} repositoryName (optional) is the displyed name for this Repository instance
	 */
	var AbstractRepository = Class.extend({
		_constructor: function (repositoryId, repositoryName) {
			/**
			 * @property repositoryId is the unique Id for this Repository instance
			 */
			this.repositoryId = repositoryId;

			/**
			 * contains the repository's settings object
			 * @property settings {Object} the repository's settings stored in an object
			 */
			this.settings = {};

			/**
			 * @property repositoryName is the name for this Repository instance
			 */
			this.repositoryName = repositoryName || repositoryId;

			RepositoryManager.register(this);
		},

		/**
		 * Init method of the repository. Called from Aloha Core to initialize this repository
		 * @return void
		 * @hide
		 */
		init: function () {},

		/**
	 * Searches a repository for object items matching queryString if none found returns null.
	 * The returned object items must be an array of Aloha.Repository.Object
	 *
	<pre><code>
	// simple delicious implementation
	Aloha.Repositories.myRepository.query = function (params, callback) {

		// make local var of this to use in ajax function
		var that = this;

		// handle each word as tag
		var tags = p.queryString.split(' ');

		// if we have a query and no tag matching return
		if ( p.queryString && tags.length == 0 ) {
			callback.call( that, []);
			return;
		}

		// no handling of objectTypeFilter, filter, inFolderId, etc...
		// in real implementation you should handle all parameters

		jQuery.ajax({ type: "GET",
			dataType: "jsonp",
			url: 'http://feeds.delicious.com/v2/json/' + tags.join('+'),
			success: function(data) {
				var items = [];
				// convert data to Aloha objects
				for (var i = 0; i < data.length; i++) {
					if (typeof data[i] != 'function' ) {
						items.push(new Aloha.Repository.Document ({
							id: data[i].u,
							name: data[i].d,
							repositoryId: that.repositoryId,
							type: 'website',
							url: data[i].u
						}));
					}
				}
				callback.call( that, items);
			}
		});
	};
	</code></pre>
	 *
	 * @param {object} params object with properties
	 * <div class="mdetail-params"><ul>
	 * <li><code> queryString</code> :  String <div class="sub-desc">The query string for full text search</div></li>
	 * <li><code> objectTypeFilter</code> : array  (optional) <div class="sub-desc">Object types that will be returned.</div></li>
	 * <li><code> filter</code> : array (optional) <div class="sub-desc">Attributes that will be returned.</div></li>
	 * <li><code> inFolderId</code> : boolean  (optional) <div class="sub-desc">This is indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).</div></li>
	 * <li><code> inTreeId</code> : boolean  (optional) <div class="sub-desc">This indicates whether or not a candidate object is a descendant-object of the folder object identified by the given inTreeId (objectId).</div></li>
	 * <li><code> orderBy</code> : array  (optional) <div class="sub-desc">ex. [{lastModificationDate:’DESC’, name:’ASC’}]</div></li>
	 * <li><code> maxItems</code> : Integer  (optional) <div class="sub-desc">number items to return as result</div></li>
	 * <li><code> skipCount</code> : Integer  (optional) <div class="sub-desc">This is tricky in a merged multi repository scenario</div></li>
	 * <li><code> renditionFilter</code> : array  (optional) <div class="sub-desc">Instead of termlist an array of kind or mimetype is expected. If null or array.length == 0 all renditions are returned. See http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310 for renditionFilter</div></li>
	 * </ul></div>
	 * @param {function} callback this method must be called with all result items</div></li>
	 */
		query: null,
		/*
	query: function( params, callback ) {
		if (typeof callback === 'function') {
			callback([]);
		}
	},
	*/

		/**
		 * Returns all children of a given motherId.
		 *
		 * @param {object} params object with properties
		 * <div class="mdetail-params"><ul>
		 * <li><code> objectTypeFilter</code> : array  (optional) <div class="sub-desc">Object types that will be returned.</div></li>
		 * <li><code> filter</code> : array  (optional) <div class="sub-desc">Attributes that will be returned.</div></li>
		 * <li><code> inFolderId</code> : boolean  (optional) <div class="sub-desc">This indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).</div></li>
		 * <li><code> orderBy</code> : array  (optional) <div class="sub-desc">ex. [{lastModificationDate:’DESC’, name:’ASC’}]</div></li>
		 * <li><code> maxItems</code> : Integer  (optional) <div class="sub-desc">number items to return as result</div></li>
		 * <li><code> skipCount</code> : Integer  (optional) <div class="sub-desc">This is tricky in a merged multi repository scenario</div></li>
		 * <li><code> renditionFilter</code> : array  (optional) <div class="sub-desc">Instead of termlist an array of kind or mimetype is expected. If null or array.length == 0 all renditions are returned. See http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310 for renditionFilter</div></li>
		 * </ul></div>
		 * @param {function} callback this method must be called with all result items
		 */
		getChildren: null,
		/*
	getChildren: function( params, callback ) {
		if (typeof callback === 'function') {
			callback([]);
		}
	},
	*/

		/**
	 * Make the given jQuery object (representing an object marked as object of this type)
	 * clean. All attributes needed for handling should be removed.
	 *
	<pre><code>
	Aloha.Repositories.myRepository.makeClean = function (obj) {
		obj.removeAttr('data-myRepository-name');
	};
	</code></pre>
	 * @param {jQuery} obj jQuery object to make clean
	 * @return void
	 */
		makeClean: function (obj) {},

		/**
	 * This method will be called when a user chooses an item from a repository and wants
	 * to insert this item in his content.
	 * Mark or modify an object as needed by that repository for handling, processing or identification.
	 * Objects can be any DOM object as A, SPAN, ABBR, etc. or
	 * special objects such as aloha-aloha_block elements.
	 * (see http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data)
	 *
	<pre><code>
	Aloha.Repositories.myRepository.markObject = function (obj, resourceItem) {
		obj.attr('data-myRepository-name').text(resourceItem.name);
	};
	</code></pre>
	 *
	 *
	 * @param obj jQuery target object to which the repositoryItem will be applied
	 * @param repositoryItem The selected item. A class constructed from Document or Folder.
	 * @return void
	 */
		markObject: function (obj, repositoryItem) {},

		/**
		 * Set a template for rendering objects of this repository
		 * @param {String} template
		 * @return void
		 * @method
		 */
		setTemplate: function (template) {
			if (template) {
				this.template = template;
			} else {
				this.template = null;
			}
		},

		/**
		 * Checks whether the repository has a template
		 * @return {boolean} true when the repository has a template, false if not
		 * @method
		 */
		hasTemplate: function () {
			return this.template ? true : false;
		},

		/**
		 * Get the parsed template
		 * @return {Object} parsed template
		 * @method
		 */
		getTemplate: function () {
			return this.template;
		},

		/**
		 * Get the repositoryItem with given id
		 * @param itemId {String} id of the repository item to fetch
		 * @param callback {function} callback function
		 * @return {Aloha.Repository.Object} item with given id
		 */
		getObjectById: function (itemId, callback) {
			return true;
		}
	});

	// expose the AbstractRepository
	Aloha.AbstractRepository = AbstractRepository;

	return AbstractRepository;
});

/* aloha.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
(function (global) {
	

	/**
	 * Initialization facilities.
	 */
	var Initialization = {

		/**
		 * A list of all stages that are passed into the Initialization.start()
		 * function.  Unless failure happens, every single one of these phases
		 * will be passed.
		 *
		 * @type {Array.<object>}
		 */
		phases: [],

		/**
		 * Completed phases.
		 *
		 * This array grows as the initialization process progresses through
		 * the initialization phases.  Each phases which is completed is pushed
		 * to the bottom of the list.
		 *
		 * @type {Array.<object>}
		 */
		completed: [],

		/**
		 * Starts the initialization phases.
		 *
		 * @param {object.<object>} phases Initialization phases.
		 * @param {function} callback Callback function to be invoked when
		 *                            initialization is completed.
		 */
		start: function (phases, callback) {
			Initialization.phases = Initialization.phases.concat(phases);
			Initialization.proceed(0, phases, callback);
		},

		/**
		 * Proceeds to next initialization phase.
		 *
		 * @param {number} index The current initialization phase, as an index
		 *                       into `phases'.
		 * @param {Array.<object>} phases
		 * @param {function=} callback Callback function to invoke at the end
		 *                             of the initialization phases.
		 */
		proceed: function (index, phases, callback) {
			if (index < phases.length) {
				var phase = phases[index];
				var next = function () {
					Initialization.proceed(++index, phases, callback);
				};
				var event = function () {
					Initialization.completed.push(phase);
					if (phase.event) {
						Aloha.trigger(phase.event);
					}
				};
				if (phase.fn) {
					phase.fn(event, next);
				} else {
					event();
					next();
				}
			} else if (callback) {
				callback();
			}
		},

		/**
		 * Retreives an phase object whose `event' property string matches the
		 * given event name.
		 *
		 * @param {string} event The event name.
		 * @return {object} A phase object or null.
		 */
		getPhaseByEvent: function (event) {
			var i;
			for (i = 0; i < Initialization.phases.length; i++) {
				if (event === Initialization.phases[i].event) {
					return Initialization.phases[i];
				}
			}
			return null;
		},

		/**
		 * Given and the name of an event, returns a corresponding readiness
		 * state concerning what should be done with that event at the current
		 * stage in the initialization phase.
		 *
		 * @param {string} event Name of event.
		 * @return {string} One of either "immediate", "deferred", or "noraml".
		 */
		getReadiness: function (event) {
			var i;
			for (i = 0; i < Initialization.completed.length; i++) {
				if (event === Initialization.completed[i].event) {
					return 'immediate';
				}
			}
			return Initialization.getPhaseByEvent(event) ? 'deferred'
			                                             : 'normal';
		}
	};

	/**
	 * Gets the configuration for loading Aloha.
	 *
	 * If Aloha.settings.baseUrl is not specified, it will be taken from
	 * the first script element that has a data-aloha-plugins attribute,
	 * or, if there is no such script element, the first script element
	 * of which the src attribute matches /\/aloha.js$/.
	 *
	 * If Aloha.settings.plugins.load is not specified, it will be taken
	 * from the data-aloha-plugins attribute from the first script
	 * element carrying this attribute.
	 *
	 * @return
	 *       A map with two properties:
	 *       baseUrl - the path to aloha.js (this file).
	 *       plugins - an array of plugins to load.
	 */
	function getLoadConfig() {
		var scripts,
		    script,
		    plugins = Aloha.settings.plugins && Aloha.settings.plugins.load,
		    baseUrl = Aloha.settings.baseUrl,
		    pluginsAttr,
		    regexAlohaJs = /\/aloha.js(\?\S*)?$/,
            regexStripFilename = /\/[^\/]*\.js$/,
		    i;

		if (!plugins || !baseUrl) {
			scripts = document.getElementsByTagName('script');
			for (i = 0; i < scripts.length; i++) {
				script = scripts[i];
				pluginsAttr = script.getAttribute('data-aloha-plugins');
				if (null != pluginsAttr) {
					if (!plugins) {
						plugins = pluginsAttr;
					}
					if (!baseUrl) {
						baseUrl = script.src.replace(regexStripFilename, '');
					}
					break;
				}
				if (!baseUrl && regexAlohaJs.test(script.src)) {
					baseUrl = script.src.replace(regexAlohaJs, '');
				}
			}
		}

		if (typeof plugins === 'string' && plugins !== '') {
			plugins = plugins.replace(/\s+/g, '').split(',');
		}

		return {
			baseUrl: baseUrl,
			plugins: plugins || []
		};
	}

	function isDeferInit() {
		var scripts = document.getElementsByTagName('script');
		for (var i = 0; i < scripts.length; i++) {
			var attr = scripts[i].getAttribute('data-aloha-defer-init');
			if ("true" === attr) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Extends the given map with plugin specific requirejs path configuration.
	 *
	 * plugin-name: bundle-path/plugin-name/lib
	 * plugin-name/nls: bundle-path/plugin-name/nls
	 * plugin-name/css: bundle-path/plugin-name/css
	 * plugin-name/vendor: bundle-path/plugin-name/vendor
	 * plugin-name/res: bundle-path/plugin-name/res
	 */
	function mergePluginPaths(paths, bundlePath, pluginName) {
		var resourceFolders = ['nls', 'css', 'vendor', 'res'],
		    resourceFolder,
		    i;
		paths[pluginName] = bundlePath + '/' + pluginName + '/lib';
		for (i = 0; i < resourceFolders.length; i++) {
			var resourceFolder = resourceFolders[i];
			paths[pluginName + '/' + resourceFolder]
				= bundlePath + '/' + pluginName + '/' + resourceFolder;
		}
	}

	/**
	 * Gets the configuration for loading the given plugins.
	 *
	 * The bundle-path for each given plugin is determined in the following manner:
	 * Aloha.settings.basePath + (Aloha.settings.bundles[bundleName] || "../plugins/bundle-name")
	 *
	 * @param plugins
	 *        An array of plugins to get the configuration for in the
	 *        form "bundle-name/plugin-name"
	 * @return
	 *        A map with the following properties:
	 *        paths - requirejs path configuration for each plugin (mergePluginPaths())
	 *        entryPoints - an array of requirejs entry points ("link/link-plugin")
	 *        baseUrlByName - ("link" => "bundle-path/link")
	 *        names - an array of plugin names (the same as the given
	 *                array with the bundle-name stripped)
	 */
	function getPluginLoadConfig(plugins) {
		var paths = {},
		    entryPoints = [],
		    names = [],
		    baseUrlByName = {},
		    map = {},
		    parts,
		    bundleName,
		    pluginName,
		    basePath = Aloha.settings.basePath || '',
		    bundlePath,
		    bundles = Aloha.settings.bundles || {},
		    i;
		for (i = 0; i < plugins.length; i++) {
			parts = plugins[i].split('/');
			bundleName = parts[0];
			pluginName = parts[1];
			if (bundles[bundleName]) {
				bundlePath = basePath + bundles[bundleName];
			} else {
				bundlePath = basePath + '../plugins/' + bundleName;
			}
			mergePluginPaths(paths, bundlePath, pluginName);
			baseUrlByName[pluginName] = bundlePath + '/' + pluginName;
			entryPoints.push(pluginName + '/' + pluginName + '-plugin');
			map[pluginName] = {'jquery': 'aloha/jquery'};
		}
		return {
			paths: paths,
			entryPoints: entryPoints,
			baseUrlByName: baseUrlByName,
			names: names,
			map: map
		};
	}

	/**
	 * Merges properites of all given arguments into a new one.
	 * Duplicate properties will be "seived" out.
	 * Works in a similar way to jQuery.extend.
	 * Necessary because we must not assume that jquery was already
	 * loaded.
	 */
	function mergeObjects () {
		var clone = {};
		var objects = Array.prototype.slice.call(arguments);
		var name;
		var i;
		var obj;
		for (i = 0; i < objects.length; i++) {
			obj = objects[i];
			for (name in obj) {
				if (obj.hasOwnProperty(name)) {
					clone[name] = objects[i][name];
				}
			}
		}
		return clone;
	}

	function createDefine(name, module) {
		define(name, function () {
			return module;
		});
	}

	function load() {
		Aloha.features = {};
		Aloha.defaults = {};
		Aloha.settings = Aloha.settings || {};
		Aloha.initialize = Initialization.start;

		var loadConfig = getLoadConfig();
		var pluginConfig = getPluginLoadConfig(loadConfig.plugins);

		Aloha.settings.baseUrl = loadConfig.baseUrl;
		Aloha.settings.loadedPlugins = pluginConfig.names;
		Aloha.settings._pluginBaseUrlByName = pluginConfig.baseUrlByName;

		var coreMap = {
			'aloha':             {'jquery': 'aloha/jquery'},
			'aloha/jquery':      {'jquery': 'jquery'}, // avoid a circular dependency
			'jqueryui':          {'jquery': 'aloha/jquery'},
			'vendor':            {'jquery': 'aloha/jquery'},
			'util':              {'jquery': 'aloha/jquery'},
			'RepositoryBrowser': {'jquery': 'aloha/jquery'},
			'jstree':            {'jquery': 'aloha/jquery'},
			'jqgrid':            {'jquery': 'aloha/jquery'},
			'jqgrid-locale-en':  {'jquery': 'aloha/jquery'},
			'jqgrid-locale-de':  {'jquery': 'aloha/jquery'},
			'jquery-layout':     {'jquery': 'aloha/jquery'}
		};

		/**
		 * Map the 'jquery' module to the 'aloha/jquery' module. This
		 * enforces Aloha modules to always use aloha/jquery instead of
		 * jquery. One could also just write
		 * define(['aloha/jquery']... to require Aloha's jquery, but
		 * this is problematic in vendor files that don't know anything
		 * about Aloha. Each key in the map is either the module name,
		 * or the firs part of the module name. For example, the mapping
		 * under the key 'aloha' will take effect for all modules with
		 * names like aloha/xxx.  When a new 'paths' entry is added
		 * (browserPaths or other), an entry should also be added the
		 * moduleMap to rename the jquery dependency.
		 * See also define('aloha/jquery', ... below.
		 */
		var moduleMap = mergeObjects(coreMap, pluginConfig.map)

		var defaultConfig = {
			context: 'aloha',
			config: {
				i18n: {
					locale: Aloha.settings.locale || 'en'
				}
			},
			baseUrl: Aloha.settings.baseUrl,
			map: moduleMap
		};

		var defaultPaths = {
			jquery: 'vendor/jquery-1.7.2',
			jqueryui: 'vendor/jquery-ui-1.9.0.custom-aloha'
		};

		var browserPaths = {
			PubSub: 'vendor/pubsub/js/pubsub-unminified',
			'Class': 'vendor/class',
			RepositoryBrowser: 'vendor/repository-browser/js/repository-browser-unminified',
			jstree: 'vendor/jquery.jstree',              // Mutates jquery
			jqgrid: 'vendor/jquery.jqgrid',              // Mutates jquery
			'jquery-layout': 'vendor/jquery.layout-1.3.0-rc29.14',     // Mutates jquery
			'jqgrid-locale-en': 'vendor/grid.locale.en', // Mutates jqgrid
			'jqgrid-locale-de': 'vendor/grid.locale.de', // Mutates jqgrid
			'repository-browser-i18n-de': 'vendor/repository-browser/js/repository-browser-unminified',
			'repository-browser-i18n-en': 'vendor/repository-browser/js/repository-browser-unminified'
		};

		var requireConfig = mergeObjects(
			defaultConfig,
			Aloha.settings.requireConfig
		);

		requireConfig.paths = mergeObjects(
			defaultPaths,
			browserPaths,
			pluginConfig.paths,
			requireConfig.paths
		);

		// Create define() wrappers that will provide the initialized objects
		// that the user passes into Aloha via require() calls.
		var predefinedModules = Aloha.settings.predefinedModules || {};

		if (Aloha.settings.jQuery) {
			predefinedModules.jquery = Aloha.settings.jQuery;
		}

		var moduleName;
		for (moduleName in predefinedModules) if (predefinedModules.hasOwnProperty(moduleName)) {
			createDefine(moduleName, predefinedModules[moduleName]);
			delete requireConfig.paths[moduleName];
		}

		// Configure require and expose the Aloha.require.
		var alohaRequire = require.config(requireConfig);

		Aloha.require = function (callback) {
			// Pass the Aloha object to the given callback.
			if (1 === arguments.length && typeof callback === 'function') {
				return alohaRequire(['aloha'], callback);
			}
			return alohaRequire.apply(this, arguments);
		};

		/**
		 *
		 * @param {string} event Name of event
		 * @param {function} fn Event handler
		 */
		Aloha.bind = function (event, fn) {
			Aloha.require(['aloha/jquery'], function ($) {
				// Because we will only need to load jQuery once
				Aloha.bind = function (event, fn) {
					switch(Initialization.getReadiness(event)) {
					case 'deferred':
						var phase = Initialization.getPhaseByEvent(event);
						if (!phase.deferred) {
							phase.deferred = $.Deferred();
						}
						phase.deferred.done(fn);
						break;
					case 'immediate':
						fn();
						break;
					case 'normal':
						$(Aloha, 'body').bind(event, fn);
						break;
					default:
						throw 'Unknown readiness';
					}
					return this;
				};
				Aloha.bind(event, fn);
			});
			return this;
		};

		Aloha.trigger = function (type, data) {
			Aloha.require(['aloha/jquery'], function ($) {
				Aloha.trigger = function (type, data) {
					var phase = Initialization.getPhaseByEvent(type);
					if (phase) {
						if (phase.deferred) {
							$(phase.deferred.resolve);
							phase.deferred = null;
						}
					}
					$(Aloha, 'body').trigger(type, data);
					return this;
				};
				Aloha.trigger(type, data);
			});
			return this;
		};

		Aloha.unbind = function (typeOrEvent) {
			Aloha.require(['aloha/jquery'], function ($) {
				Aloha.unbind = function (typeOrEvent) {
					$(Aloha, 'body').unbind(typeOrEvent);
				};
				Aloha.unbind(typeOrEvent);
			});
		};

		Aloha.ready = function (fn) {
			this.bind('aloha-ready', fn);
			return this;
		};

		/**
		 * This makes sure that all Aloha modules will receive the same jQuery.
		 *
		 * This is a workaround for when a user includes his own
		 * jQuery _after_ aloha.js has been loaded.
		 *
		 * If multiple 'jquery's are included in the page, each version
		 * will make its own call to define(), and depending on when an
		 * Aloha module is loaded, it may receive a different
		 * 'jquery'. However, 'aloha/jquery' will not be redefined and
		 * will therefore point always to only one particular version.
		 *
		 * !!Important!! to be certain that 'aloha/jquery' points to
		 * the jQuery intended for Aloha, it can't be loaded
		 * dynamically, because if a user loads his own jQuery after
		 * aloha.js, then there is no way to tell whether it is the
		 * user's jQuery or Aloha's jQuery that has finished
		 * loading. Instead, jQuery must be loaded before aloha.js and
		 * passed in to us.
		 */
		var jQueryThatWasPassedToUs = Aloha.settings.jQuery;
		define('aloha/jquery', ['jquery'], function ($) {
			// We prefer Aloha.settings.jQuery, since a dynamically loaded
			// jQuery may have been redefined by a user's jQuery.
			return jQueryThatWasPassedToUs || $;
		});

		// Initialize this early so that the user doesn't have to use
		// Aloha.ready().
		Aloha.jQuery = jQueryThatWasPassedToUs;

		define('aloha', [
			'aloha/jquery',
			'util/json2',
			'aloha/rangy-core',
			'util/class',
			'util/lang',
			'util/range',
			'util/dom',
			'aloha/core',
			'aloha/editable',
			'aloha/console',
			'aloha/markup',
			'aloha/plugin',
			'aloha/selection',
			'aloha/command',
			'aloha/jquery.aloha',
			'aloha/sidebar',
			'util/position',
			'aloha/repositorymanager',
			'aloha/repository',
			'aloha/repositoryobjects',
			'aloha/contenthandlermanager'
		], function($) {
			Aloha.features.jquery = true;

			// Set it again in case jQuery was loaded asynchronously.
			Aloha.jQuery = $;

			// Some core files provide default settings in Aloha.defaults.
			Aloha.settings = $.extendObjects(true, {}, Aloha.defaults,
					Aloha.settings);

			return Aloha;
		});

		// TODO aloha should not make the require call itself.  Instead, user
		// code should require and initialize aloha.
		require(requireConfig, ['aloha', 'aloha/jquery'], function (Aloha, $) {
			require(requireConfig, pluginConfig.entryPoints, function () {
				$(function () {
					// Rangy must be initialized only after the body is
					// available since it accesses the body element during
					// initialization.
					window.rangy.init();

					// The same for Aloha, but probably only because it depends
					// on rangy.
					Aloha.init();
				});
			});
		});
	} // end load()

	global.Aloha = global.Aloha || {};
	global.Aloha.settings = global.Aloha.settings || {};

	// set the locale in the global __DEPS__ here to enable i18n of dependencies
	// like repository browser
	global.__DEPS__ = global.__DEPS__ || {};
	global.__DEPS__.lang = global.Aloha.settings.locale || 'en';
	if (global.Aloha.deferInit || isDeferInit()) {
		global.Aloha.deferInit = load;
	} else {
		// Unless init is deferred above, aloha mus be loaded
		// immediately in the development version, but later in the
		// compiled version. The reason loading must be delayed in the
		// compiled version is that the "include" directive in the r.js
		// build profile, which lists the plugins that will be compiled
		// into aloha.js, will include the plugins *after* this
		// file. Since the require() call that loads the plugins is in
		// this file, it will not see any of the plugin's defines that
		// come after this file. The call to Aloha._load is only made in
		// compiled mode in closure-end.frag. The call to load() below
		// is only made in development mode because the excludeStart and
		// excludeEnd r.js pragmas will exclude everything inbetween in
		// the compiled version.
		// TODO ideally the bootstrap file should not make the require
		//      call at all. Instead, user code should require and
		//      initialize aloha.
		Aloha._load = load;
			}
}(window));

/**
 * The context module provides functions to get at the context element
 * for widgets.
 *
 * Many widgets are created dynamically and append themselves to the
 * body so that they can be shown to the user. The context element is
 * just a div appended to the body, that provides a common parent for
 * these widget elements.
 * 
 * Appending widget elements to the context element provides two benefits:
 * 1 - it would be cleaner for all aloha-specific elements to be
 *     appended to one common parent.
 * 2 - all css rules should have a .aloha context class, and the common
 *     parent provides this class.
 */
define('ui/context',[
	'aloha',
	'jquery',
	'util/class'
], function (
	Aloha,
	$,
	Class
) {
	

	var id = 'aloha-ui-context',
	    selector = '#' + id,
	    element;

	// There is just a single context element in the page
	element = $(selector);
	if (!element.length) {
		element = $('<div>', {'class': 'aloha', 'id': id});
		// In the built aloha.js, init will happend before the body has
		// finished loading, so we have to defer appending the element.
		$(function(){ element.appendTo('#neos-application'); });
	}

	var Context =  Class.extend({
		surfaces: [],
		containers: []
	});

	// static fields

	$.extend(Context, {
		selector: selector,
		element: element
	});

	return Context;
});

define('ui/scopes',[
	'jquery',
	'PubSub',
	'util/maps'
], function (
	$,
	PubSub,
	Maps
) {
	

	var scopes = {
		    'Aloha.empty': [],
		    'Aloha.global': ['Aloha.empty'],
		    'Aloha.continuoustext': ['Aloha.global']
	    },
	    activeScopes = [],
	    addedScopes = {},
	    scopeSetDuringSelectionChanged = false;

	function pushScopeAncestors(ancestorScopes, scope) {
		if (!scopes.hasOwnProperty(scope)) {
			return;
		}
		var parentScopes = scopes[scope];
		for (var i = 0; i < parentScopes.length; i++) {
			var parentScope = parentScopes[i];
			ancestorScopes.push(parentScope);
			pushScopeAncestors(ancestorScopes, parentScope);
		}
	}

	Aloha.bind('aloha-selection-changed-before', function () {
		scopeSetDuringSelectionChanged = false;
	});

	Aloha.bind('aloha-selection-changed-after', function (event, range, originalEvent) {
		// I don't know why we check for originalEvent != 'undefined', here is
		// the original comment:
		// "Only set the specific scope if an event was provided, which means
		// that somehow an editable was selected"
		if (typeof originalEvent !== 'undefined' && ! scopeSetDuringSelectionChanged) {
			Scopes.setScope('Aloha.continuoustext');
		}
	});

	/**
	 * @deprecated
	 *     Scopes don't provide any additional functionality since
	 *     the visibility of containers and components can be
	 *     controlled individually.
	 */
	var Scopes = {

		/**
		 * Increments the scope counter for the given scope and requestor.
		 *
		 * A counter is maintained per scope and requestor. The counter
		 * can be incremented/decremented with enterScope/leaveScope.
		 *
		 * The first increment of the counter (the increment to 1) will
		 * make the scope active (isActiveScope() returns true) and
		 * publish the aloha.ui.scope.change event. Further increments
		 * will do nothing except increment the counter, which will
		 * require more leaveScope calls to be made to leave the scope.
		 *
		 * The last decrement of the counter (the decrement to 0) will
		 * make the scope inactive (isActiveScope() returns false) and
		 * publish the aloha.ui.scope.change event. Further decrements
		 * will do nothing.
		 *
		 * @param scope
		 *        The scope to enter.
		 * @param requestor
		 *        The subsystem or plugin that requests to leave the
		 *        scope. Can be used to isolate the scope counter to a
		 *        particular subsystem, usually a plugin. If not given,
		 *        a global counter will be used instead.
		 * @deprecated
		 *     Scopes don't provide any additional functionality since
		 *     the visibility of containers and components can be
		 *     controlled individually.
		 */
		enterScope: function(scope, requestor) {
			requestor = requestor || '_globalCounter';
			var counters = addedScopes[scope];
			if (!counters) {
				counters = addedScopes[scope] =  {};
			}
			var counter = counters[requestor] || 0;
			counters[requestor] = counter + 1;
			if (!counter) {
				PubSub.pub('aloha.ui.scope.change');
			}
		},

		/**
		 * Decrements the scope counter for the given scope and requestor.
		 *
		 * @param force
		 *        True when the scope should be left even if the counter
		 *        is non-zero after decrementing it.
		 * @see enterScope()
		 * @deprecated
		 *     Scopes don't provide any additional functionality since
		 *     the visibility of containers and components can be
		 *     controlled individually.
		 */
		leaveScope: function(scope, requestor, force) {
			requestor = requestor || '_globalCounter';
			var counters = addedScopes[scope];
			if (!counters) {
				return;
			}
			var counter = counters[requestor];
			if (!counter) {
				return;
			}
			counter -= 1;
			if (counter && !force) {
				counters[requestor] = counter;
			} else {
				delete counters[requestor];
				if (Maps.isEmpty(counters)) {
					delete addedScopes[scope];
				}
				PubSub.pub('aloha.ui.scope.change');
			}
		},

		/**
		 * @deprecated
		 *     Scopes don't provide any additional functionality since
		 *     the visibility of containers and components can be
		 *     controlled individually.
		 */
		isActiveScope: function(scope){
			if (addedScopes[scope]) {
				return true;
			}
			var isActive = (-1 !== $.inArray(scope, activeScopes));
			if (isActive) {
				return true;
			}
			return false;
		},

		/**
		 * @deprecated
		 *     See setScope()
		 */
		getPrimaryScope: function() {
			return activeScopes[0];
		},

		/**
		 * @deprecated
		 *     Problem with setScope is that scopes defined by multiple plugins are exclusive to one another.
		 *     Example: table plugin and link plugin - you want to be able to set both table and link scopes.
		 *     Use enterScope and leaveScope instead.
		 */
		setScope: function(scope) {
			scopeSetDuringSelectionChanged = true;
			if (activeScopes[0] != scope) {
				activeScopes = [scope];
				pushScopeAncestors(activeScopes, scope);
				PubSub.pub('aloha.ui.scope.change');
			}
		},

		/**
		 * @deprecated
		 *     This method was used to define an ancestry for scopes.
		 *     It is unknonwn what problem scope ancestry solved, and
		 *     the method is therefore deprecated.
		 */
		createScope: function(scope, parentScopes){
			if ( ! parentScopes ) {
				parentScopes = ['Aloha.empty'];
			} else if (typeof parentScopes === 'string') {
				parentScopes = [parentScopes];
			}
			scopes[scope] = parentScopes;
		}
	};
	return Scopes;
});

/**
 * Defines a `Container` Class.
 *
 * Containers are activated based on the `showOn` setting for the container.
 * The values are normalized to functions which accept an element and return a
 * boolean; true means the container should be shown.
 *
 * For efficiency, we group all containers that have the same normalized
 * `showOn()' function together, so we can evaluate it once, regardless of how
 * many containers are using the same logic. In order for this to work, the
 * exact same function must be returned from `Container.normalizeShowOn()' when
 * the logic is the same.
 *
 * The list of containers is then stored on the context instance as
 * `context.containers', which is a hash of `showOn()' ids to an array of
 * containers. The `showOn()' ids are unique identifiers that are stored as
 * properties of the `showOn()' function (see `getShowOnId()'). This gives us
 * constant lookup times when grouping containers.
 */

define('ui/container',[
	'jquery',
	'util/class',
	'ui/scopes'
], function (
	$,
	Class,
	Scopes
) {
	

	var uid = 0;

	/**
	 * Gets the id of a normalized showOn option.  If the given function has
	 * not had its showOnId set it will receive one, the first time this
	 * function it is passed to this function.
	 *
	 * @param {function} showOn The function whose id we wish to get.
	 * @return {number} The id of the given function.
	 */
	function getShowOnId(showOn) {
		// Store a unique id on the showOn function.
		// See full explanation at top of file.
		if (!showOn.showOnId) {
			showOn.showOnId = ++uid;
		}
		return showOn.showOnId;
	}

	/**
	 * Show or hide a set of containers.
	 *
	 * @param {Array.<Container>} containers The set of containers to operate
	 *                                       on.
	 * @param {boolean} show Whether to show or hide the given containers.
	 */
	function toggleContainers(containers, show) {
		var action = show ? 'show' : 'hide',
		    i;
		for (i = 0; i < containers.length; i++) {
			containers[i][action]();
		}
	}

	var scopeFns = {};

	var returnTrue = function () {
		return true;
	};

	/**
	 * Normalizes a showOn option into a function.
	 *
	 * @param {(string|boolean|function)} showOn
	 * @return function
	 */
	function normalizeShowOn(container, showOn) {
		switch ($.type(showOn)) {
		case 'function':
			return showOn;
		case 'object':
			if (showOn.scope) {
				if (scopeFns[showOn.scope]) {
					return scopeFns[showOn.scope];
				}
				return scopeFns[showOn.scope] = function () {
					return Scopes.isActiveScope(showOn.scope);
				};
			} else {
				throw "Invalid showOn configuration";
			}
		default:
			return returnTrue;
		}
	}

	/**
	 * Container class.
	 *
	 * @class
	 * @base
	 */
	var Container = Class.extend({

		/**
		 * The containing (wrapper) element for this container.
		 *
		 * @type {jQuery<HTMLElement>}
		 */
		element: null,

		/**
		 * Initialize a new container with the specified properties.
		 *
		 * @param {object=} settings Optional properties, and override methods.
		 * @constructor
		 */
		_constructor: function (context, settings) {
			var showOn = normalizeShowOn(this, settings.showOn),
			    key = getShowOnId(showOn),
			    group = context.containers[key];
			this.context = context;
			if (!group) {
				group = context.containers[key] = {
					shouldShow: showOn,
					containers: []
				};
			}
			group.containers.push(this);
		},

		/**
		 * Must be implemented by extending classes.
		 *
		 * @ingroup api
		 * @{
		 */

		/**
		 * A container is also a component; this is part of the component API.
		 */
		show: function () {},
		/**
		 * A container is also a component; this is part of the component API.
		 */
		hide: function () {},
		/**
		 * A container is also a component; this is part of the component API.
		 */
		focus: function () {},
		/**
		 * A container is also a component; this is part of the component API.
		 */
		foreground: function () {},

		/**
		 * The container was previously hidden, and now has become visible. This
		 * allows a container to let its children react to this.
		 */
		childVisible: function (childComponent, visible) {},
		/**
		 * The container was given focus; this method must give focus to all
		 * children of the container.
		 * Optional. (E.g. tab.js doesn't implement this.)
		 */
		childFocus: function (childComponent) {},
		/**
		 * The container was foregrounded; this method must foreground all children
		 * of the container.
		 */
		childForeground: function (childComponent) {}

		/**
		 * @} End of "ingroup api".
		 */

	});

	// static fields

	$.extend( Container, {
		/**
		 * Given an array of elements, show appropriate containers.
		 *
		 * @param {object} context
		 * @param {string} eventType Type of the event triggered (optional)
		 * @static
		 */
		showContainersForContext: function (context, eventType) {
			var group,
			    groupKey,
			    containerGroups;
			if (!context.containers) {
				// No containers were constructed for the given context, so
				// there is nothing for us to do.
				return;
			}
			containerGroups = context.containers;
			for (groupKey in containerGroups) {
				if (containerGroups.hasOwnProperty(groupKey)) {
					group = containerGroups[groupKey];
					toggleContainers(group.containers, group.shouldShow(eventType));
				}
			}
		}
	});

	return Container;
});

define('ui/surface',[
	'aloha/core',
	'jquery',
	'util/class',
	'ui/container'
], function (
	Aloha,
	$,
	Class,
	Container
) {
	

	/**
	 * The Surface class and manager.
	 *
	 * @class
	 * @base
	 */
	var Surface = Class.extend({
		_constructor: function (context) {
			context.surfaces.push(this);
		},

		/**
		 * Check for whether or not this surface is active--that is, whether is
		 * is visible and the user can interact with it.
		 *
		 * @eturn {boolean} True if this surface is visible.
		 */
		isActive: function () {
			return true;
		}
	});

	// Static fields for the Surface class.

	$.extend(Surface, {

		/**
		 * The range of the current selection.
		 * 
		 * Interacting with a surface removes focus from the editable, so the
		 * surface is responsible for keeping track of the range that should be
		 * modified by the components.
		 * 
		 * @static
		 * @type {Aloha.Selection}
		 */
		range: null,

		/**
		 * Shows all surfaces for a given context.
		 *
		 * @param {!Object} context.
		 */
		show: function (context) {
			$.each(context.surfaces, function (i, surface) {
				surface.show();
			});
		},

		/**
		 * Hides all surfaces for a given context.
		 *
		 * @param {!Object} context
		 */
		hide: function (context) {
			$.each(context.surfaces, function (i, surface) {
				surface.hide();
			});
		},

		/**
		 * Track editable and range when interacting with a surface.
		 *
		 * @param {jQuery<HTMLElement>} element A component or surface for
		 *                                      which we wish to keep track of
		 *                                      the current selection range
		 *                                      when the user interacts with
		 *                                      it.
		 */
		trackRange: function (element) {
			element.bind('mousedown', function (e) {
				e.originalEvent.stopSelectionUpdate = true;
				Aloha.eventHandled = true;
				Surface.suppressHide = true;

				if (Aloha.activeEditable) {
					var selection = Aloha.getSelection();
					Surface.range = (0 < selection.getRangeCount()) ?
						selection.getRangeAt(0) : null;
				}
			});
			
			element.bind('mouseup', function (e) {
				e.originalEvent.stopSelectionUpdate = true;
				Aloha.eventHandled = false;
				Surface.suppressHide = false;
			});
		}
	});

	return Surface;
});

define('ui/component',[
	'aloha/core',
	'jquery',
	'util/class'
], function (
	Aloha,
	$,
	Class
) {
	

	var idCounter = 0;

	/**
	 * Component class and manager.
	 *
	 * This implementation constitues the base of all UI components (buttons,
	 * and labels).  The `Component' constructor object, with its static
	 * properties and functions, manages all components instances.
	 *
	 * @class
	 * @base
	 */
	var Component = Class.extend({

		id: 0,

		/**
		 * Flag to indicate that this is an instance of a component and  not the class object.
		 */
		isInstance: true,

		/**
		 * The Container instance or null if this component was not
		 * adopted by a counter by calling Component.adopt().
		 */
		container: null,

		/**
		 * Will be set in Component.define()
		 */
		type: null,

		/**
		 * @type {boolean} Whether or not this component is visible.
		 */
		visible: true,

		/**
		 * The type property is set in Component.define(), so components should only ever be instantiated through define.
		 * @constructor
		 */
		_constructor: function () {
			this.id = idCounter++;
			this.init();
		},

		adoptParent: function (container) {
			this.container = container;
		},

		/**
		 * Initializes this component.  To be implemented in subclasses.
		 */
		init: function () {},

		isVisible: function () {
			return this.visible;
		},

		/**
		 * Shows this component.
		 */
		show: function (show_opt) {
			if (false === show_opt) {
				this.hide();
				return;
			}
			// Only call container.childVisible if we switch from hidden to visible
			if (!this.visible) {
				this.visible = true;
				this.element.show();
				if (this.container) {
					this.container.childVisible(this, true);
				}
			}
		},

		/**
		 * Hides this component.
		 */
		hide: function () {
			// Only call container.childVisible if we switch from visible to hidden
			if (this.visible) {
				this.visible = false;
				this.element.hide();
				if (this.container) {
					this.container.childVisible(this, false);
				}
			}
		},

		focus: function () {
			this.element.focus();
			if (this.container) {
				this.container.childFocus(this);
			}
		},

		foreground: function () {
			if (this.container) {
				this.container.childForeground(this);
			}
		},

		enable: function (enable_opt) {},
		disable: function () {}
	});

	return Component;
});

define('ui/tab',[
	'aloha/core',
	'jquery',
	'ui/container',
	'ui/component',
	'PubSub',
	'jqueryui'
], function (
	Aloha,
	$,
	Container,
	Component,
	PubSub
) {
	

	var idCounter = 0;
	var slottedComponents = {};

	/**
	 * Defines a Container object that represents a collection of related
	 * component groups to be rendered together on the toolbar.  Tabs are
	 * organized by feature and functionality so that related controls can be
	 * brought in and out of view depending on whether they are
	 * appropriate for a given user context.
	 *
	 * Tabs can be defined declaritively in the Aloha configuration in the
	 * following manner:
	 *
	 *    Aloha.settings.toolbar: [
	 *      {
	 *         label: 'Lists',
	 *         showOn: 'ul,ol,*.parent(.aloha-editable ul,.aloha-editable ol)',
	 *         components: [ [ 'orderedList', 'unorderedList' ] ]
	 *      }
	 *    ]
	 *
	 * Alternatively, tabs can also be created imperatively in this way:
	 * new Tab( options, components ).
	 *
	 * @class
	 * @extends {Container}
	 */
	var Tab = Container.extend({

		_elemBySlot: null,
		_groupBySlot: null,
		_groupByComponent: null,

		/**
		 * All that this constructor does is save the components array into a
		 * local variable, to be used during instantialization.
		 *
		 * @param {object} settings
		 * @param {Array.<Array<string>>} components
		 * @constructor
		 */
		_constructor: function (context, settings, components) {
			var thisTab = this,
				i, j,
				elem,
				groupedComponents,
				group,
				groupProps,
				componentName;

			this._elemBySlot = {};
			this._groupBySlot = {};
			this._groupByComponent = {};
			this._slotsList = [];
			this._super(context, settings);

			this.container = settings.container;
			this.list = this.container.data('list');
			this.panels = this.container.data('panels');
			this.id = 'tab-ui-container-' + (++idCounter);
			this.panel = $('<div>', {id : this.id, 'unselectable': 'on'});
			this.handle = $('<li><a href="' + location.href.replace(/#.*$/, '') + '#' + this.id + '">' +
				settings.label + '</a></li>');

			for (i = 0; i < components.length; i++) {
				if (typeof components[i] === 'string') {
					if (1 === components[i].length && components[i].charCodeAt(0) === 10) {
						this.panel.append('<div>', {'unselectable': 'on'});
					} else {
						elem = $('<span>', {'unselectable': 'on'});
						this._elemBySlot[components[i]] = elem;
						this.panel.append(elem);
					}
				} else {
					// Hide the group until the first button is adopted into it.
					group = $('<div>', {
						'class': 'aloha-ui-component-group aloha-ui-hidden',
						'unselectable': 'on'
					}).appendTo(this.panel);
					groupProps = {element: group, visibleCounter: 0};
					groupedComponents = components[i];
					for (j = 0; j < groupedComponents.length; j++) {
						this._groupBySlot[groupedComponents[j]] = groupProps;
						if (groupedComponents[j] &&
							1 === groupedComponents[j].length &&
						    groupedComponents[j].charCodeAt(0) === 10) {
							group.append($('<div>', {'unselectable': 'on'}));
						} else {
							componentName = groupedComponents[j];
							elem = $('<span>', {'unselectable': 'on'});
							this._elemBySlot[groupedComponents[j]] = elem;
							group.append(elem);
						}
					}
				}
			}

			this.panel.append($('<div>', {'class': 'aloha-ui-clear', 'unselectable': 'on'}));
			this.handle.appendTo(this.list);
			this.panel.appendTo(this.panels);
			this.container.tabs('refresh');

			var alohaTabs = settings.container.data('aloha-tabs');
			this.index = alohaTabs.length;
			alohaTabs.push(this);
		},

		adoptInto: function (slot, component) {
			var elem = this._elemBySlot[slot],
			    group;
			if (!elem) {
				return false;
			}
			slottedComponents[slot] = component;
			component.adoptParent(this);
			elem.append(component.element);
			group = this._groupBySlot[slot];
			this._slotsList.push(slot);
			if (group) {
				this._groupByComponent[component.id] = group;
				if (component.isVisible()) {
					if (!group.visibleCounter) {
						group.element.removeClass('aloha-ui-hidden');
					}
					group.visibleCounter += 1;
				}
			}
			return true;
		},

		foreground: function () {
			this.container.tabs('option', 'active', this.index);
		},

		childForeground: function (childComponent) {
			this.foreground();
		},

		hasVisibleComponents: function () {
			var siblings = this._elemBySlot;
			var slot;
			for (slot in siblings) {
				if (siblings.hasOwnProperty(slot) && slottedComponents[slot]) {
					if (slottedComponents[slot].visible) {
						return true;
					}
				}
			}
			return false;
		},

		childVisible: function (childComponent, visible) {
			if (visible) {
				childComponent.container.show();
			} else if (!childComponent.container.hasVisibleComponents()) {
				childComponent.container.hide();
			}
			var group = this._groupByComponent[childComponent.id];
			if (!group) {
				return;
			}
			if (visible) {
				if (0 === group.visibleCounter) {
					group.element.removeClass('aloha-ui-hidden');
				}
				group.visibleCounter += 1;
			} else {
				group.visibleCounter -= 1;
				if (0 === group.visibleCounter) {
					group.element.addClass('aloha-ui-hidden');
				}
			}
		},

		/**
		 * @override
		 */
		show: function () {
			if (!this.list.children().length || !this.hasVisibleComponents()) {
				return;
			}

			this.handle.show();
			this.visible = true;

			// Hiding all tabs may hide the toolbar, so showing the
			// first tab again must also show the toolbar.
			this.container.show();

			// If no tabs are selected, then select the tab which was just shown.
			if (!this.container.find('.ui-tabs-active').length ||
				this.container.tabs('option', 'selected') === this.index) {
				this.foreground();
			}
		},

		/**
		 * @override
		 */
		hide: function () {
			var tabs = this.list.children();
			if (0 === tabs.length) {
				return;
			}
			this.handle.hide();
			this.visible = false;

			// If the tab we just hid was the selected tab, then we need to
			// select another tab in its stead.  We will select the first
			// visible tab we find, or else we deselect all tabs.
			if (this.index === this.container.tabs('option', 'selected')) {
				tabs = this.container.data('aloha-tabs');

				var i;
				for (i = 0; i < tabs.length; ++i) {
					if (tabs[i].visible) {
						this.container.tabs('select', i);
						return;
					}
				}

				// This does not work...
				// this.container.tabs( 'select', -1 );

				// Why do we remove this class?
				this.handle.removeClass('ui-tabs-active');

				// It doesn't make any sense to leave the toolbar
				// visible after all tabs have been hidden.
				this.container.hide();
			}
		}

	});

	$.extend(Tab, {

		/**
		 * Creates holding elements for jQuery UI Tabs for a surface.
		 *
		 * @static
		 * @return {jQuery.<HTMLElement>} The holder container on which we
		 *                                invoke jQuery UI Tabs once it is
		 *                                populated with tab containers.
		 */
		createContainer: function () {
			var $container = $('<div>', {'unselectable': 'on'});
			var $list = $('<ul>', {'unselectable': 'on'}).appendTo($container);
			var $panels = $('<div>', {'unselectable': 'on'}).appendTo($container);

			$container
				.data('list', $list)
				.data('panels', $panels)
				.data('aloha-tabs', [])
				.tabs({
					select: function (event, ui) {
						var tabs = $container.data('aloha-tabs');
						$container.data('aloha-active-container', tabs[ui.index]);
						PubSub.pub('aloha.ui.container.selected', {data: tabs[ui.index]});
					}
				});

			return $container;
		}
	});

	return Tab;
});

define('ui/subguarded',[
	'aloha/core',
	'jquery',
	'PubSub'
], function (
	Aloha,
	jQuery,
	PubSub
) {
	

	/**
	 * The last uid that was was used to uniquely identify a function.
	 * NB: Make sure to increment this counter before assigning it to a new
	 *     function.
	 *
	 * @type {numder}
	 * @private
	 */
	var alohaUid = 0;

	/**
	 * A spares array, where arguments lists are indexed against the alohaUid of
	 * the guarded dispatch function with which they were registered with.
	 *
	 * @type {object.<number, array>}
	 * @private
	 */
	var registeredArguments = {};

	/**
	 * A spares array of guarded dispatch functions indexed against their
	 * alohaUid.  Each entry in this array will correspond with an entry in the
	 * `registeredArguments' map which is exists on the same index.
	 *
	 * @type {object.<number, array>}
	 * @private
	 */
	var registeredGuards = {};

	/**
	 * For a given function, will derive its unique identifing number.  Be
	 * aware that although this is a "getter" function, it will mutate the
	 * given function, and the closure variable `alohaUid' if the given
	 * function has not had a unique id set to it.
	 *
	 * @param {function} func Function whose uid is to be retreived.
	 * @return {number} The alohaUid property that has been assigned to the
	 *                  given function.
	 */
	function getUid(func) {
		if (!func.alohaUid) {
			func.alohaUid = ++alohaUid;
		}
		return func.alohaUid;
	}

	/**
	 * Retrieves a list of all guard functions that are registered for the
	 * given event.
	 *
	 * @param {string} event The name of the event whose guard functions are to
	 *                       be to retreive.
	 * @return {array.<function>} A list of guarded dispatch functions that
	 *                            were registered to handle the given event.
	 */
	function getRegisteredGuards(event) {
		return registeredGuards[event] || [];
	}

	/**
	 * Retrieves a list of argument lists that were registerd with the given
	 * guard function.
	 *
	 * @paran {function} guard A function that is to filter and dispatch.
	 * @return {array.<*>} A list of arguments list.
	 */
	function getArguments(guard) {
		return registeredArguments[guard.alohaUid] || [];
	}

	/**
	 * Registers the given arguments list against the provided guard function.
	 *
	 * @paran {function} guard A function that is to filter and dispatch a
	 *                         variable number of callbacks.
	 * @return {array.<*>} The list of all registed arguments lists that
	 *                     correspond with the given guard.
	 */
	function registerArguments(guard, args) {
		if (!registeredArguments[guard.alohaUid]) {
			registeredArguments[guard.alohaUid] = [];
		}
		registeredArguments[guard.alohaUid].push(args);
		return registeredArguments[guard.alohaUid];
	}

	/**
	 * Registers the given guard function to the given event.
	 *
	 * @paran {string} event The event on which to invoke the guard.
	 * @paran {function} guard A function that is to filter and dispatch a
	 *                         variable number of callbacks.
	 * @return {array.<*>} The list of all registed guard functions that
	 *                     correspond with the given guard.
	 */
	function registerGuard(event, guard) {
		if (!registeredGuards[event]) {
			registeredGuards[event] = [];
		}
		registeredGuards[event].push(guard);
		return registeredGuards[event];
	}

	/**
	 * Process a guard functions that have been registered on the given event
	 * when the event is triggered.
	 *
	 * @param {Event} event Name of the event.
	 * @param ... any other arguments passed on to the guard function
	 */
	function trigger(event) {
		var guards = getRegisteredGuards(event);
		var i;
		for (i = 0; i < guards.length; i++) {
			guards[i].apply(null, [getArguments(guards[i])].concat(arguments));
		}
	}

	/**
	 * Provides a mechanism to register event handlers that are filtered and
	 * dispatched through a guard function.
	 *
	 * All arguments following the guard parameter will be passed to the guard
	 * function in a list containing a tuple of arguments.  It is expected that
	 * one of the arguments will be a callback function that will be call if
	 * the other arguments pass the condition implemented in the guarded
	 * dispatch function.
	 *
	 * USAGE:
	 *    sub(event, dispatch [, ... ])
	 *
	 * @param {string} event
	 * @param {function(array.<array.<*...>>)} guard A function that will be
	 *                                               invoked when the specified
	 *                                               event is fired.  This
	 *                                               function will receive a
	 *                                               array consisting of
	 *                                               arguments tuples, followed
	 *                                               by the the arguments that
	 *                                               are received from the
	 *                                               event.
	 * @param {*...} args A variable number of arguments which will be passed
	 *                    as a list in a list to the dispatch function.
	 */
	function sub() {
		var args = Array.prototype.slice.call(arguments);
		var events = args.shift();
		if (typeof events === 'string') {
			events = [events];
		}
		var guard = args.shift();
		getUid(guard);
		registerArguments(guard, args);
		var i;
		var event;
		var bindHandler = function ($event, range, nativeEvent) {
			trigger(event, $event, range, nativeEvent);
		};
		var pubsubHandler = function () {
			trigger(event);
		};
		for (i = 0; i < events.length; i++) {
			event = events[i];
			registerGuard(event, guard);
			Aloha.bind(event, bindHandler);
			PubSub.sub(event, pubsubHandler);
		}
	}

	return sub;

});

define('vendor/amplify.store',['util/json2'],function(){
var amplify = {};

/*!
 * Amplify Store - Persistent Client-Side Storage 1.1.0
 * 
 * Copyright 2011 appendTo LLC. (http://appendto.com/team)
 * Dual licensed under the MIT or GPL licenses.
 * http://appendto.com/open-source-licenses
 * 
 * http://amplifyjs.com
 */
(function( amplify, undefined ) {

var store = amplify.store = function( key, value, options, type ) {
	var type = store.type;
	if ( options && options.type && options.type in store.types ) {
		type = options.type;
	}
	return store.types[ type ]( key, value, options || {} );
};

store.types = {};
store.type = null;
store.addType = function( type, storage ) {
	if ( !store.type ) {
		store.type = type;
	}

	store.types[ type ] = storage;
	store[ type ] = function( key, value, options ) {
		options = options || {};
		options.type = type;
		return store( key, value, options );
	};
}
store.error = function() {
	return "amplify.store quota exceeded"; 
};

var rprefix = /^__amplify__/;
function createFromStorageInterface( storageType, storage ) {
	store.addType( storageType, function( key, value, options ) {
		var storedValue, parsed, i, remove,
			ret = value,
			now = (new Date()).getTime();

		if ( !key ) {
			ret = {};
			remove = [];
			i = 0;
			try {
				// accessing the length property works around a localStorage bug
				// in Firefox 4.0 where the keys don't update cross-page
				// we assign to key just to avoid Closure Compiler from removing
				// the access as "useless code"
				// https://bugzilla.mozilla.org/show_bug.cgi?id=662511
				key = storage.length;

				while ( key = storage.key( i++ ) ) {
					if ( rprefix.test( key ) ) {
						parsed = JSON.parse( storage.getItem( key ) );
						if ( parsed.expires && parsed.expires <= now ) {
							remove.push( key );
						} else {
							ret[ key.replace( rprefix, "" ) ] = parsed.data;
						}
					}
				}
				while ( key = remove.pop() ) {
					storage.removeItem( key );
				}
			} catch ( error ) {}
			return ret;
		}

		// protect against name collisions with direct storage
		key = "__amplify__" + key;

		if ( value === undefined ) {
			storedValue = storage.getItem( key );
			parsed = storedValue ? JSON.parse( storedValue ) : { expires: -1 };
			if ( parsed.expires && parsed.expires <= now ) {
				storage.removeItem( key );
			} else {
				return parsed.data;
			}
		} else {
			if ( value === null ) {
				storage.removeItem( key );
			} else {
				parsed = JSON.stringify({
					data: value,
					expires: options.expires ? now + options.expires : null
				});
				try {
					storage.setItem( key, parsed );
				// quota exceeded
				} catch( error ) {
					// expire old data and try again
					store[ storageType ]();
					try {
						storage.setItem( key, parsed );
					} catch( error ) {
						throw store.error();
					}
				}
			}
		}

		return ret;
	});
}

// localStorage + sessionStorage
// IE 8+, Firefox 3.5+, Safari 4+, Chrome 4+, Opera 10.5+, iPhone 2+, Android 2+
for ( var webStorageType in { localStorage: 1, sessionStorage: 1 } ) {
	// try/catch for file protocol in Firefox
	try {
		if ( window[ webStorageType ].getItem ) {
			createFromStorageInterface( webStorageType, window[ webStorageType ] );
		}
	} catch( e ) {}
}

// globalStorage
// non-standard: Firefox 2+
// https://developer.mozilla.org/en/dom/storage#globalStorage
if ( window.globalStorage ) {
	// try/catch for file protocol in Firefox
	try {
		createFromStorageInterface( "globalStorage",
			window.globalStorage[ window.location.hostname ] );
		// Firefox 2.0 and 3.0 have sessionStorage and globalStorage
		// make sure we default to globalStorage
		// but don't default to globalStorage in 3.5+ which also has localStorage
		if ( store.type === "sessionStorage" ) {
			store.type = "globalStorage";
		}
	} catch( e ) {}
}

// userData
// non-standard: IE 5+
// http://msdn.microsoft.com/en-us/library/ms531424(v=vs.85).aspx
(function() {
	// IE 9 has quirks in userData that are a huge pain
	// rather than finding a way to detect these quirks
	// we just don't register userData if we have localStorage
	if ( store.types.localStorage ) {
		return;
	}

	// append to html instead of body so we can do this from the head
	var div = document.createElement( "div" ),
		attrKey = "amplify";
	div.style.display = "none";
	document.getElementsByTagName( "head" )[ 0 ].appendChild( div );

	// we can't feature detect userData support
	// so just try and see if it fails
	// surprisingly, even just adding the behavior isn't enough for a failure
	// so we need to load the data as well
	try {
		div.addBehavior( "#default#userdata" );
		div.load( attrKey );
	} catch( e ) {
		div.parentNode.removeChild( div );
		return;
	}

	store.addType( "userData", function( key, value, options ) {
		div.load( attrKey );
		var attr, parsed, prevValue, i, remove,
			ret = value,
			now = (new Date()).getTime();

		if ( !key ) {
			ret = {};
			remove = [];
			i = 0;
			while ( attr = div.XMLDocument.documentElement.attributes[ i++ ] ) {
				parsed = JSON.parse( attr.value );
				if ( parsed.expires && parsed.expires <= now ) {
					remove.push( attr.name );
				} else {
					ret[ attr.name ] = parsed.data;
				}
			}
			while ( key = remove.pop() ) {
				div.removeAttribute( key );
			}
			div.save( attrKey );
			return ret;
		}

		// convert invalid characters to dashes
		// http://www.w3.org/TR/REC-xml/#NT-Name
		// simplified to assume the starting character is valid
		// also removed colon as it is invalid in HTML attribute names
		key = key.replace( /[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g, "-" );

		if ( value === undefined ) {
			attr = div.getAttribute( key );
			parsed = attr ? JSON.parse( attr ) : { expires: -1 };
			if ( parsed.expires && parsed.expires <= now ) {
				div.removeAttribute( key );
			} else {
				return parsed.data;
			}
		} else {
			if ( value === null ) {
				div.removeAttribute( key );
			} else {
				// we need to get the previous value in case we need to rollback
				prevValue = div.getAttribute( key );
				parsed = JSON.stringify({
					data: value,
					expires: (options.expires ? (now + options.expires) : null)
				});
				div.setAttribute( key, parsed );
			}
		}

		try {
			div.save( attrKey );
		// quota exceeded
		} catch ( error ) {
			// roll the value back to the previous value
			if ( prevValue === null ) {
				div.removeAttribute( key );
			} else {
				div.setAttribute( key, prevValue );
			}

			// expire old data and try again
			store.userData();
			try {
				div.setAttribute( key, parsed );
				div.save( attrKey );
			} catch ( error ) {
				// roll the value back to the previous value
				if ( prevValue === null ) {
					div.removeAttribute( key );
				} else {
					div.setAttribute( key, prevValue );
				}
				throw store.error();
			}
		}
		return ret;
	});
}() );

// in-memory storage
// fallback for all browsers to enable the API even if we can't persist data
(function() {
	var memory = {},
		timeout = {};

	function copy( obj ) {
		return obj === undefined ? undefined : JSON.parse( JSON.stringify( obj ) );
	}

	store.addType( "memory", function( key, value, options ) {
		if ( !key ) {
			return copy( memory );
		}

		if ( value === undefined ) {
			return copy( memory[ key ] );
		}

		if ( timeout[ key ] ) {
			clearTimeout( timeout[ key ] );
			delete timeout[ key ];
		}

		if ( value === null ) {
			delete memory[ key ];
			return null;
		}

		memory[ key ] = value;
		if ( options.expires ) {
			timeout[ key ] = setTimeout(function() {
				delete memory[ key ];
				delete timeout[ key ];
			}, options.expires );
		}

		return value;
	});
}() );

}( amplify ) );
return amplify;
});

/* floating.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 *
 * @overview
 * Implements position and floating animation effect for UI surfaces.
 */
define('ui/floating',[
	'jquery',
	'aloha/core',
	'ui/surface',
	'ui/subguarded',
	'PubSub',
	'vendor/amplify.store',
	'util/browser'
], function (
	$,
	Aloha,
	Surface,
	subguarded,
	PubSub,
	amplifyStore,
	Browser
) {
	

	/**
	 * The distance that the floating surface should maintain from the editable
	 * it is floating to.
	 *
	 * @type {string}
	 * @const
	 */
	var DISTANCE = 10;

	/**
	 * The duration of the floating animation in milliseconds.
	 *
	 * @type {number}
	 * @const
	 */
	var DURATION = 500;

	/**
	 * jQuery unit set containing a feference to the global window.
	 *
	 * @type {jQuery.<window>}
	 * @const
	 */
	var $WINDOW = $(window);

	/**
	 * The "position" style value.
	 *
	 * IE 7 does not support "fixed" position styling.  Since "fixed" position
	 * results in smoother animation the use of "absolute" is made as a special
	 * accomodation for IE 7.
	 *
	 * @type {string}
	 * @const
	 */
	var POSITION_STYLE = Browser.ie7 ? 'absolute' : 'fixed';

	/**
	 * The position of the floating menu.
	 *
	 * Used to float dialoges (eg special character-picker) with the floating
	 * menu.
	 *
	 * @type {object<string,*>}
	 */
	var POSITION = {
		style: POSITION_STYLE,
		offset: {
			top: 0,
			left: 0
		}
	};

	/**
	 * Animates a surface element to the given position.
	 *
	 * @param {jQuery.<HTMLElement>} $element jQuery unit set of the DOM
	 *                                        element to move.
	 * @param {object} position The x and y position to which the element
	 *                          should end up.
	 * @param {number} duration The length of time (in milliseconds) that the
	 *                          animation should run for.
	 * @param {function} callback Function to be invoked when animation
	 *                            completes.
	 */
	function floatTo($element, position, duration, callback) {
		if ('absolute' === POSITION_STYLE) {
			position.top += $WINDOW.scrollTop();
			position.left += $WINDOW.scrollLeft();
		}

		POSITION.offset = position;

		$element.stop().animate(position, duration, function () {
			callback(position);
			PubSub.pub('aloha.floating.changed', {
				position: $.extend({}, POSITION)
			});
		});
	}

	/**
	 * Moves an element above the given coordinates.
	 *
	 * @param {jQuery.<HTMLElement>} $element jQuery unit set of the DOM
	 *                                        element to move.
	 * @param {object} position The x and y position to which the element
	 *                          should end up.
	 * @param {number} duration The length of time (in milliseconds) that the
	 *                          animation should run for.
	 * @param {function} callback Function to be invoked when animation
	 *                            completes.
	 */
	function floatAbove($element, position, duration, callback) {
		position.top -= $element.height() + DISTANCE;
		floatTo($element, position, duration, callback);
	}

	/**
	 * Moves the element below the given coordinates.
	 *
	 * @param {jQuery.<HTMLElement>} $element jQuery unit set of the DOM
	 *                                        element to move.
	 * @param {object} position The x and y position to which the element
	 *                          should end up.
	 * @param {number} duration The length of time (in milliseconds) that the
	 *                          animation should run for.
	 * @param {function} callback Function to be invoked when animation
	 *                            completes.
	 */
	function floatBelow($element, position, duration, callback) {
		position.top += DISTANCE;
		floatTo($element, position, duration, callback);
	}

	/**
	 * Persist the "top" and "left" positions of the FloatingMenu surface.
	 */
	function storePinPosition(offset) {
		amplifyStore.store('Aloha.FloatingMenu.pinned', 'true');
		amplifyStore.store('Aloha.FloatingMenu.top', offset.top);
		amplifyStore.store('Aloha.FloatingMenu.left', offset.left);
	}

	/**
	 * Clears persisted state of the FloatingMenu surface.
	 */
	function unstorePinPosition() {
		amplifyStore.store('Aloha.FloatingMenu.pinned', null);
		amplifyStore.store('Aloha.FloatingMenu.top', null);
		amplifyStore.store('Aloha.FloatingMenu.left', null);
	}

	/**
	 * Retreive the persisted pinned position of the FloatingMenu surface.
	 *
	 * @return {object}
	 */
	function getPinState() {
		if (amplifyStore.store('Aloha.FloatingMenu.pinned') === 'true') {
			return {
				top: parseInt(amplifyStore.store('Aloha.FloatingMenu.top'), 10),
				left: parseInt(amplifyStore.store('Aloha.FloatingMenu.left'), 10),
				isPinned: true
			};
		}
		return {
			top: null,
			left: null,
			isPinned: false
		};
	}

	/**
	 * Constrains the given position coordinates to be within the viewport.
	 *
	 * @param {object} position "Top" and "left" coordinates.
	 * @param {object} Constrained "top" and "left" coordinates.
	 */
	function forcePositionIntoWindow(position) {
		var left = position.left;
		var top = position.top;

		if (top < 0) {
			top = 0;
		} else if (top > $WINDOW.height()) {
			top = $WINDOW.height() / 2;
		}

		if (left < 0) {
			left = 0;
		} else if (left > $WINDOW.width()) {
			left = $WINDOW.width() / 2;
		}

		return {
			top: top,
			left: left
		};
	}

	/**
	 * Floats a surface to the appropriate position around the given editable.
	 *
	 * @param {Surface} surface The surface to be positioned.
	 * @param {Aloha.Editable} editable The editable around which the surface
	 *                                  should be positioned.
	 * @param {number} duration The length of time (in milliseconds) for the
	 *                          animation should run.
	 * @param {function} callback Function to be invoked after the animation
	 *                            is completed.
	 */
	function floatSurface(surface, editable, duration, callback) {
		if (typeof duration !== 'number') {
			duration = DURATION;
		}

		var topGutter = (parseInt($('body').css('marginTop'), 10) || 0)
		              + (parseInt($('body').css('paddingTop'), 10) || 0);
		var $surface = surface.$element;
		var offset = editable.obj.offset();
		var top = offset.top;
		var left = offset.left;
		var scrollTop = $WINDOW.scrollTop();
		var scrollLeft = $WINDOW.scrollLeft();
		var availableSpace = top - scrollTop - topGutter;
		// consider horizontal scrolling (important for rtl pages that are scrolled to the left)
		left = left - scrollLeft;
		var horizontalOverflow = left + $surface.width() - $WINDOW.width();

		if (horizontalOverflow > 0) {
			left = Math.max(0, left - horizontalOverflow);
		}

		// never ever float outside of the visible area (to the left)
		left = Math.max(0, left);

		if (availableSpace >= $surface.height()) {
			floatAbove($surface, {
				top: top - scrollTop,
				left: left
			}, duration, callback);
		} else if (availableSpace + $surface.height() >
		           availableSpace + editable.obj.height()) {
			floatBelow($surface, {
				top: top + editable.obj.height() - scrollTop,
				left: left
			}, duration, callback);
		} else {
			floatBelow($surface, {
				top: topGutter,
				left: left
			}, duration, callback);
		}
	}

	/**
	 * Pins a surface at the speficied position on the viewport.
	 *
	 * @param {Surface} surfaces The surfaces that are to be pinned.
	 * @param {object} position The "top" and "left" position of where the
	 *                          surface is to be pinned.
	 * @param {boolean} isFloating Whether or not the surface type is in
	 *                             "floating" mode or not.
	 */
	function togglePinSurface(surface, position, isFloating) {
		var $surface = surface.$element;
		if (isFloating) {
			unstorePinPosition();
			$surface.find('.aloha-ui-pin').removeClass('aloha-ui-pin-down');
		} else {
			storePinPosition(position);
			$surface.find('.aloha-ui-pin').addClass('aloha-ui-pin-down');
		}
		$surface.css({
			position: 'fixed',
			top: position.top
		});
	}

	/**
	 * Filters surface activation events.
	 */
	function onActivatedSurface(tuples, eventName, $event, range, nativeEvent) {
		var i;
		for (i = 0; i < tuples.length; i++) {
			if (tuples[i][0].isActive()) {
				tuples[i][1]($event, range, nativeEvent);
			}
		}
	}

	/**
	 * Sets the surface's DOM element's "position" property to "fixed."
	 *
	 * IE7 will not properly set the position property to "fixed" if our
	 * element is not rendered.  We therefore have to do a rigmarole to
	 * temorarily render the element in order to set the position correctly.
	 *
	 * @param {Surface} surface
	 */
	function setPositionStyleToFixed(surface) {
		if ($.browser.msie) {
			var $parent = surface.$element.parent();
			surface.$element.appendTo('body');
			surface.$element.css('position', POSITION_STYLE);
			if ($parent.length) {
				surface.$element.appendTo($parent);
			} else {
				surface.$element.detach();
			}
		} else {
			surface.$element.css('position', POSITION_STYLE);
		}
	}

	/**
	 * Binds floating facilities on a surface.
	 *
	 * @TODO:
	 * Resizable toolbars are possible, and would be a nice feature:
	 * surface.$element.resizable();
	 *
	 * @param {Surface} surface A UI Surface instance.
	 * @param {object} SurfaceTypeManager
	 */
	function makeFloating(surface, SurfaceTypeManager) {
		subguarded([
			'aloha-selection-changed',
			'aloha.ui.container.selected'
		], onActivatedSurface, surface, function () {
			surface._move();
		});

		var updateSurfacePosition = function () {
			var position = forcePositionIntoWindow({
				top: SurfaceTypeManager.pinTop,
				left: SurfaceTypeManager.pinLeft
			});
			SurfaceTypeManager.setFloatingPosition(position);
			surface.$element.css({
				top: position.top,
				left: position.left
			});
		};

		$WINDOW.scroll(function () {
			// TODO: only do this for active surfaces.
			surface._move(0);
		});

		$WINDOW.resize(function () {
			if (!SurfaceTypeManager.isFloatingMode) {
				updateSurfacePosition();
			}
		});

		surface.addPin();
		setPositionStyleToFixed(surface);

		if (!SurfaceTypeManager.isFloatingMode) {
			updateSurfacePosition();
		}

		surface.$element.css('z-index', 10100).draggable({
			distance: 20,
			stop: function (event, ui) {
				SurfaceTypeManager.setFloatingPosition(ui.position);
				if (!SurfaceTypeManager.isFloatingMode) {
					storePinPosition(ui.position);
				}
			}
		});
	}

	return {
		getPinState: getPinState,
		makeFloating: makeFloating,
		floatSurface: floatSurface,
		togglePinSurface: togglePinSurface,
		POSITION_STYLE: POSITION_STYLE
	};
});

/**
 * @license RequireJS i18n 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/requirejs/i18n for details
 */
/*jslint regexp: true */
/*global require: false, navigator: false, define: false */

/**
 * This plugin handles i18n! prefixed modules. It does the following:
 *
 * 1) A regular module can have a dependency on an i18n bundle, but the regular
 * module does not want to specify what locale to load. So it just specifies
 * the top-level bundle, like "i18n!nls/colors".
 *
 * This plugin will load the i18n bundle at nls/colors, see that it is a root/master
 * bundle since it does not have a locale in its name. It will then try to find
 * the best match locale available in that master bundle, then request all the
 * locale pieces for that best match locale. For instance, if the locale is "en-us",
 * then the plugin will ask for the "en-us", "en" and "root" bundles to be loaded
 * (but only if they are specified on the master bundle).
 *
 * Once all the bundles for the locale pieces load, then it mixes in all those
 * locale pieces into each other, then finally sets the context.defined value
 * for the nls/colors bundle to be that mixed in locale.
 *
 * 2) A regular module specifies a specific locale to load. For instance,
 * i18n!nls/fr-fr/colors. In this case, the plugin needs to load the master bundle
 * first, at nls/colors, then figure out what the best match locale is for fr-fr,
 * since maybe only fr or just root is defined for that locale. Once that best
 * fit is found, all of its locale pieces need to have their bundles loaded.
 *
 * Once all the bundles for the locale pieces load, then it mixes in all those
 * locale pieces into each other, then finally sets the context.defined value
 * for the nls/fr-fr/colors bundle to be that mixed in locale.
 */
(function () {
    

    //regexp for reconstructing the master bundle name from parts of the regexp match
    //nlsRegExp.exec("foo/bar/baz/nls/en-ca/foo") gives:
    //["foo/bar/baz/nls/en-ca/foo", "foo/bar/baz/nls/", "/", "/", "en-ca", "foo"]
    //nlsRegExp.exec("foo/bar/baz/nls/foo") gives:
    //["foo/bar/baz/nls/foo", "foo/bar/baz/nls/", "/", "/", "foo", ""]
    //so, if match[5] is blank, it means this is the top bundle definition.
    var nlsRegExp = /(^.*(^|\/)nls(\/|$))([^\/]*)\/?([^\/]*)/;

    //Helper function to avoid repeating code. Lots of arguments in the
    //desire to stay functional and support RequireJS contexts without having
    //to know about the RequireJS contexts.
    function addPart(locale, master, needed, toLoad, prefix, suffix) {
        if (master[locale]) {
            needed.push(locale);
            if (master[locale] === true || master[locale] === 1) {
                toLoad.push(prefix + locale + '/' + suffix);
            }
        }
    }

    function addIfExists(req, locale, toLoad, prefix, suffix) {
        var fullName = prefix + locale + '/' + suffix;
        if (require._fileExists(req.toUrl(fullName))) {
            toLoad.push(fullName);
        }
    }

    /**
     * Simple function to mix in properties from source into target,
     * but only if target does not already have a property of the same name.
     * This is not robust in IE for transferring methods that match
     * Object.prototype names, but the uses of mixin here seem unlikely to
     * trigger a problem related to that.
     */
    function mixin(target, source, force) {
        var prop;
        for (prop in source) {
            if (source.hasOwnProperty(prop) && (!target.hasOwnProperty(prop) || force)) {
                target[prop] = source[prop];
            } else if (typeof source[prop] === 'object') {
                mixin(target[prop], source[prop], force);
            }
        }
    }

    define('i18n',['module'], function (module) {
        var masterConfig = module.config();

        return {
            version: '2.0.1',
            /**
             * Called when a dependency needs to be loaded.
             */
            load: function (name, req, onLoad, config) {
                config = config || {};

                if (config.locale) {
                    masterConfig.locale = config.locale;
                }

                var masterName,
                    match = nlsRegExp.exec(name),
                    prefix = match[1],
                    locale = match[4],
                    suffix = match[5],
                    parts = locale.split("-"),
                    toLoad = [],
                    value = {},
                    i, part, current = "";

                //If match[5] is blank, it means this is the top bundle definition,
                //so it does not have to be handled. Locale-specific requests
                //will have a match[4] value but no match[5]
                if (match[5]) {
                    //locale-specific bundle
                    prefix = match[1];
                    masterName = prefix + suffix;
                } else {
                    //Top-level bundle.
                    masterName = name;
                    suffix = match[4];
                    locale = masterConfig.locale;
                    if (!locale) {
                        locale = masterConfig.locale =
                            typeof navigator === "undefined" ? "root" :
                            (navigator.language ||
                             navigator.userLanguage || "root").toLowerCase();
                    }
                    parts = locale.split("-");
                }

                if (config.isBuild) {
                    //Check for existence of all locale possible files and
                    //require them if exist.
                    toLoad.push(masterName);
                    addIfExists(req, "root", toLoad, prefix, suffix);
                    for (i = 0; i < parts.length; i++) {
                        part = parts[i];
                        current += (current ? "-" : "") + part;
                        addIfExists(req, current, toLoad, prefix, suffix);
                    }

                    req(toLoad, function () {
                        onLoad();
                    });
                } else {
                    //First, fetch the master bundle, it knows what locales are available.
                    req([masterName], function (master) {
                        //Figure out the best fit
                        var needed = [],
                            part;

                        //Always allow for root, then do the rest of the locale parts.
                        addPart("root", master, needed, toLoad, prefix, suffix);
                        for (i = 0; i < parts.length; i++) {
                            part = parts[i];
                            current += (current ? "-" : "") + part;
                            addPart(current, master, needed, toLoad, prefix, suffix);
                        }

                        //Load all the parts missing.
                        req(toLoad, function () {
                            var i, partBundle, part;
                            for (i = needed.length - 1; i > -1 && needed[i]; i--) {
                                part = needed[i];
                                partBundle = master[part];
                                if (partBundle === true || partBundle === 1) {
                                    partBundle = req(prefix + part + '/' + suffix);
                                }
                                mixin(value, partBundle);
                            }

							// MODIFICATION FROM ALOHA START: add a t() function
							value.t = function( key, defaultValue ) {
								if ( this[key] ) {
									return this[key];
								} else if ( defaultValue ) {
									return defaultValue;
								} else {
									return key;
								}
							}
							// END OF ALOHA MODIFICATION

                            //All done, notify the loader.
                            onLoad(value);
                        });
                    });
                }
            }
        };
    });
}());

define('ui/nls/i18n',{
	"root":  {
		"button.dismiss.label": "Dismiss",
		"button.bold.label": "Bold",
		"button.italic.label": "Italic",
		"button.strikethrough.label": "Strikethrough",
		"button.subscript.label": "Subscript",
		"button.superscript.label": "Superscript",
		"button.underline.label": "Underline",
		"button.yes.label": "Yes",
		"button.no.label": "No",

		// paragraph
		"button.p.label": "Paragraph",
		"button.h1.label": "Heading 1",
		"button.h2.label": "Heading 2",
		"button.h3.label": "Heading 3",
		"button.h4.label": "Heading 4",
		"button.h5.label": "Heading 5",
		"button.h6.label": "Heading 6",
		"button.pre.label": "Preformatted text",
		"button.removeFormatting.label": "Remove formatting",

		// list
		"button.ol.label": "Insert ordered list",
		"button.ul.label": "Insert unordered list",
		"button.indent.label": "Indent list",
		"button.outdent.label": "Outdent list",
		"button.createLink.label": "Insert link",
		"button.removeLink.label": "Remove link",
		"button.createAbbr.label": "Insert abbreviation",
		"button.characterPicker.label": "Pick special characters",

		// link
		"button.createLink.label": "Insert link",
		"button.removeLink.label": "Remove link",

		// abbr
		"button.createAbbr.label": "Insert abbreviation",

		// character picker
		"button.characterPicker.label": "Pick special characters",

		// justify
		"button.justifyLeft.label": "Align to the left",
		"button.justifyRight.label": "Align to the right",
		"button.justifyCenter.label": "Center",
		"button.justifyFull.label": "Justify",

		// horizontal rule
		"button.horizontalRule.label": "Insert horizontal rule",
		"button.createLanguageAnnotation.label": "Insert language annotation",
		"button.metaview.label": "Switch between meta and normal view",
		"button.quote.label": "Format selection as quote",
		"button.blockquote.label": "Format selection as blockquote",
		"tab.format.label": "Format",
		"tab.insert.label": "Insert",
		"tab.abbr.label": "Abbreviation",
		"tab.img.label": "Image",
		"tab.link.label": "Link",
		"tab.cite.label": "Cite",
		"tab.list.label": "List",
		"tab.table.label": "Table",
		"tab.col.label": "Table Column",
		"tab.row.label": "Table Row",
		"tab.cell.label": "Table Cell",
		"tab.wai-lang.label": "Language annotation"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

define('ui/toolbar',[
	'jquery',
	'aloha/core',
	'ui/surface',
	'ui/tab',
	'ui/floating',
	'ui/context',
	'i18n!ui/nls/i18n',
	'jqueryui'
], function (
	$,
	Aloha,
	Surface,
	Tab,
	floating,
	Context,
	i18n
) {
	

	function isFloatingEnabled() {
		return !Aloha.settings
			|| !Aloha.settings.toolbar
			|| Aloha.settings.toolbar.floating !== false;
	}

	/**
	 * The toolbar is configured via `settings.toolbar` and is defined as an
	 * array of tabs with component groups, where the groups are arrays of
	 * controls.
	 *
	 * There are separate components for each context, but only the components
	 * for the active context are shown.
	 *
	 * As a container for tabs, the toolbar serves to group together groups of
	 * control components so that they can be shown and hidden together in their
	 * feature/functional set.  For example groups of table controls would be
	 * placed in a table tab, groups of list controls in an image tab, and so
	 * forth.
	 *
	 * Toolbar class and manager
	 *
	 * @class
	 * @extends {Surface}
	 */
	var Toolbar = Surface.extend({
		_moveTimeout: null,
		$_container: null,
		_tabBySlot: null,
		_tabs: [],

		/**
		 * Toolbar constructor.
		 *
		 * @param {!Array.<(Object|Array|string)>} tabs
		 * @constructor
		 * @override
		 */
		_constructor: function (context, tabs) {
			var tabSettings,
			    tabInstance,
			    i,
			    key;
			this._super(context);
			this.$element = $('<div>', {'class': 'aloha-ui aloha-ui-toolbar', 'unselectable': 'on'});
			this.$_container = Tab.createContainer().appendTo(this.$element);
			this._tabBySlot = {};

			for (i = 0; i < tabs.length; i++) {
				tabSettings = tabs[i];
				tabInstance = new Tab(context, {
					label: i18n.t(tabSettings.label, tabSettings.label),
					showOn: tabSettings.showOn,
					container: this.$_container
				}, tabSettings.components);
				for (key in tabInstance._elemBySlot) {
					if (tabInstance._elemBySlot.hasOwnProperty(key)) {
						this._tabBySlot[key] = tabInstance;
					}
				}
				this._tabs.push({tab: tabInstance, settings: tabSettings});
			}

			// Pinning behaviour is global in that if one toolbar is pinned,
			// then all other toolbars will be pinned to that position.
			if (isFloatingEnabled()) {
				floating.makeFloating(this, Toolbar);
			}
		},

		adoptInto: function (slot, component) {
			var tab = this._tabBySlot[slot];
			return tab && tab.adoptInto(slot, component);
		},

		getActiveContainer: function () {
			return this.$_container.data('aloha-active-container');
		},

		getContainers: function () {
			return this.$_container.data('aloha-tabs');
		},

		/**
		 * Moves the toolbar into the optimal position near the active editable.
		 *
		 * @param {number} duration The length of time the moving animation
		 *                          should run.
		 */
		_move: function (duration) {
			// We need to order the invocation of the floating animation to
			// occur after the the height of the toolbar's DOM has been
			// caluclated.
			var toolbar = this;
			if (toolbar._moveTimeout) {
				window.clearTimeout(toolbar._moveTimeout);
			}
			toolbar._moveTimeout = window.setTimeout(function () {
				toolbar._moveTimeout = null;
				if (Aloha.activeEditable && Toolbar.isFloatingMode) {
					floating.floatSurface(
						toolbar,
						Aloha.activeEditable,
						duration,
						Toolbar.setFloatingPosition
					);
				}
				// 20ms should be small enough to appear instantaneous to the
				// user but large enough to avoid doing unnecessary work when
				// the selection changes multiple times within a short time
				// frame.
			}, 20);
		},

		addPin: function () {
			var $pin = $('<div class="aloha-ui-pin">');
			var $element = this.$element;
			$element.find('.ui-tabs').append($pin);
			$element.find('.ui-tabs').hover(function () {
				$element.addClass('aloha-ui-hover');
			}, function () {
				$element.removeClass('aloha-ui-hover');
			});

			if (!Toolbar.isFloatingMode) {
				$pin.addClass('aloha-ui-pin-down');
			}

			var surface = this;

			$pin.click(function () {
				Toolbar.isFloatingMode = !Toolbar.isFloatingMode;
				var position;

				if (Toolbar.isFloatingMode) {
					position = {
						top: Toolbar.pinTop,
						left: Toolbar.pinLeft
					};
				} else {
					position = surface.$element.offset();
					position.top -= $(window).scrollTop();
				}

				Toolbar.setFloatingPosition(position);
				floating.togglePinSurface(surface, position, Toolbar.isFloatingMode);
			});
		},

		/**
		 * Shows the toolbar.
		 */
		show: function () {
			Toolbar.$surfaceContainer.children().detach();
			Toolbar.$surfaceContainer.append(this.$element);
			Toolbar.$surfaceContainer.stop().fadeTo(200, 1);
			if (isFloatingEnabled()) {
				var position = Toolbar.getFloatingPosition();
				this.$element.stop().css({
					top: position.top,
					left: position.left
				});
				this._move();
			}
		},

		/**
		 * Hides the toolbar.
		 */
		hide: function () {
			Toolbar.$surfaceContainer.stop().fadeOut(200, function () {
				Toolbar.$surfaceContainer.children().detach();
			});
		}
	});

	$.extend(Toolbar, {

		/**
		 * An element on which all toolbar surfaces are to be rendered on the
		 * page.
		 * @type {jQuery.<HTMLElement>}
		 */
		$surfaceContainer: null,

		/**
		 * Whether or not floating toolbar surfaces should be pinned.
		 * @type {boolean}
		 */
		isFloatingMode: true,

		/**
		 * Left position of pinned toolbars.
		 * @type {number}
		 */
		pinLeft: 0,

		/**
		 * Top position of pinned toolbars.
		 * @type {number}
		 */
		pinTop: 0,

		/**
		 * Initializes the toolbar manager.  Adds the surface container
		 * element, and sets up floating behaviour settings.
		 */
		init: function () {
			// TODO should use context.js to get the context element
			Toolbar.$surfaceContainer = $('<div>', {
				'class': 'aloha aloha-surface aloha-toolbar',
				'unselectable': 'on'
			}).hide();

			// In the built aloha.js, init will happend before the body has
			// finished loading, so we have to defer appending the element.
			$(function () {
				Toolbar.$surfaceContainer.appendTo('body');
			});
			Surface.trackRange(Toolbar.$surfaceContainer);
			var pinState = floating.getPinState();
			Toolbar.pinTop = pinState.top;
			Toolbar.pinLeft = pinState.left;
			Toolbar.isFloatingMode = !pinState.isPinned;
		},

		setFloatingPosition: function (position) {
			Toolbar.pinTop = position.top;
			Toolbar.pinLeft = position.left;
		},

		getFloatingPosition: function () {
			return {
				top: Toolbar.pinTop,
				left: Toolbar.pinLeft
			};
		}
	});

	Toolbar.init();

	return Toolbar;
});

define('ui/settings',['jquery', 'util/arrays', 'util/maps', 'util/trees'], function ($, Arrays, Maps, Trees) {
	var defaultToolbarSettings = {
		tabs: [
			// Format Tab
			{
				label: 'tab.format.label',
				showOn: { scope: 'Aloha.continuoustext' },
				components: [
					[
						'bold', 'strong', 'italic', 'emphasis', 'underline', '\n',
						'subscript', 'superscript', 'strikethrough', 'code', 'quote'
					], [
						'formatLink', 'formatAbbr', 'formatNumeratedHeaders', 'toggleDragDrop', '\n',
						'toggleMetaView', 'wailang', 'toggleFormatlessPaste'
					], [
						'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', '\n',
						'orderedListFormatSelector', 'unorderedListFormatSelector', 'definitionListFormatSelector', '\n', 'indentList', 'outdentList', 'colorPicker'
					], [
						'formatBlock'
					]
				]
			},
			// Insert Tab
			{
				label: "tab.insert.label",
				showOn: { scope: 'Aloha.continuoustext' },
				components: [
					[ "createTable", "characterPicker", "insertLink",
					  "insertImage", "insertAbbr", "insertToc",
					  "insertHorizontalRule", "insertTag"]
				]
			},
			// Link Tab
			{
				label: 'tab.link.label',
				showOn: { scope: 'link' },
				components: [ 'editLink', 'removeLink', 'linkBrowser' ]
			},
			// Cite Tab
			{
				label : 'tab.cite.label',
				showOn : { scope : 'cite' },
				components : [ 'editCite', 'removeCite', '\n', 'editNote' ]
			},
            // Image Tab
            {
                label: "tab.img.label",
                showOn: {scope: 'image'},
                components: [
					[ "imageSource", "\n",
					  "imageTitle" ],
					[ "imageResizeWidth", "\n",
					  "imageResizeHeight" ],
					[ "imageAlignLeft", "imageAlignRight", "imageAlignNone", "imageIncPadding", "\n",
					  "imageCropButton", "imageCnrReset", "imageCnrRatio", "imageDecPadding" ],
					[ "imageBrowser" ]
                ]
            },
            // Abbr Tab
            {   label: "tab.abbr.label",
                showOn: { scope: 'abbr' },
                components: [
                    [ "abbrText", "removeAbbr" ]
                ]
            },
            // Wailang Tab
            {   label: "tab.wai-lang.label",
				showOn: { scope: 'wai-lang' },
                components: [ [ "wailangfield", "removewailang" ] ]
            },
			// Table Tabs
			{
				label: "tab.table.label",
				showOn: { scope: 'table.cell' },
				components: [
					[ "mergecells", "splitcells", "tableCaption",
					  "naturalFit", "tableSummary" ],
					[ "formatTable" ]
				]
			},
			{
				label: "tab.col.label",
				showOn: { scope: 'table.column' },
				components: [
					[ "addcolumnleft", "addcolumnright", "deletecolumns",
					  "columnheader", "mergecellsColumn", "splitcellsColumn",
					  "formatColumn" ]
				]
			},
			{
				label: "tab.row.label",
				showOn: { scope: 'table.row' },
				components: [
					[ "addrowbefore", "addrowafter", "deleterows", "rowheader",
					  "mergecellsRow", "splitcellsRow", "formatRow" ]
				]
			},
			{
				label: "tab.cell.label",
				showOn: { scope: 'table.cell' },
				components: [
					[ "alignTop", "alignMiddle", "alignBottom", "formatCell" ]
				]
			}

		]
	};

	/**
	 * Combines two toolbar configurations.
	 *
	 * The rules for combining configurations are as follows
	 *
	 * * remove all components and tabs from the default toolbar configuration
	 *   that are listed in the given exclude array,
	 * * add all remaining tabs from the default configuration to the user
	 *   configuration,
	 * * and merge tabs with the same name such that a tab property that is
	 *   omitted in the user configuration will be taken from the default
	 *   configuration,
	 * * and, if both the default tab and the user's tab configuration contain
	 *   a components property, and unless the exclusive property on a tab is
	 *   true, append all remaining components from the default tab to the
	 *   user's tab configuration.
	 *
	 * @param userTabs
	 *        a list of tab configurations
	 * @param defaultTabs
	 *        a list of tab configurations
	 * @param exclude
	 *        a list of component names and tab labels to ignore
	 *        in the given defaultTabs configuration.
	 * @return
	 *
	 */
	function combineToolbarSettings(userTabs, defaultTabs, exclude) {
		var defaultTabsByLabel = Maps.fillTuples({}, Arrays.map(defaultTabs, function(tab) {
			return [tab.label, tab];
		}));
		var exclusionLookup = makeExclusionMap(userTabs, exclude);
		function pruneDefaultComponents(form) {
			return 'array' === $.type(form) ? !form.length : exclusionLookup[form];
		}
		userTabs = mergeDefaultComponents(userTabs, defaultTabsByLabel, pruneDefaultComponents);
		defaultTabs = remainingDefaultTabs(defaultTabs, exclusionLookup, pruneDefaultComponents);
		return userTabs.concat(defaultTabs);
	}

	function remainingDefaultTabs(defaultTabs, exclusionLookup, pruneDefaultComponents) {
		var i,
		    tab,
		    tabs = [],
		    defaultTab,
		    components;
		for (i = 0; i < defaultTabs.length; i++) {
			defaultTab = defaultTabs[i];
			if (!exclusionLookup[defaultTab.label]) {
				components = Trees.postprune(defaultTab.components, pruneDefaultComponents);
				if (components) {
					tab = $.extend({}, defaultTab);
					tab.components = components;
					tabs.push(tab);
				}
			}
		}
		return tabs;
	}

	function mergeDefaultComponents(userTabs, defaultTabsByLabel, pruneDefaultComponents) {
		var i,
			tab,
			tabs = [],
			userTab,
			components,
			defaultTab,
			defaultComponents;

		for (i = 0; i < userTabs.length; i++) {
			userTab = userTabs[i];
			components = userTab.components || [];
			defaultTab = defaultTabsByLabel[userTab.label];
			if (!userTab.exclusive && defaultTab) {
				defaultComponents = Trees.postprune(defaultTab.components, pruneDefaultComponents);
				if (defaultComponents) {
					components = components.concat(defaultComponents);
				}
			}
			tab = $.extend({}, defaultTab || {}, userTab);
			tab.components = components;
			tabs.push(tab);
		}
		return tabs;
	}

	function makeExclusionMap(userTabs, exclude) {
		var i,
		    map = Maps.fillKeys({}, exclude, true);
		for (i = 0; i < userTabs.length; i++) {
			map[userTabs[i].label] = true;
			Maps.fillKeys(map, Trees.flatten(userTabs[i].components), true);
		}
		return map;
	}

	return {
		defaultToolbarSettings: defaultToolbarSettings,
		combineToolbarSettings: combineToolbarSettings
	};
});

/* ui-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * The ui/ui-plugin module controls the creation and display of the UI.
 */
define('ui/ui-plugin', [
	'jquery',
	'aloha',
	'ui/context',
	'ui/container',
	'ui/surface',
	'ui/toolbar',
	'ui/scopes',
	'ui/settings',
	'PubSub',
	// Most modules of the ui plugin depend on jquery-ui, but its easy
	// to forget to add the dependency so we do it here.
	'jqueryui'
], function (
	$,
	Aloha,
	Context,
	Container,
	Surface,
	Toolbar,
	Scopes,
	Settings,
	PubSub
) {
	

	var context = new Context(),
		toolbar = new Toolbar(context, getToolbarSettings());

	Aloha.bind('aloha-editable-activated', function (event, alohaEvent) {
		Surface.show(context);
		Container.showContainersForContext(context, event);
	});

	Aloha.bind('aloha-editable-deactivated', function (event, alohaEvent) {
		if (!Surface.suppressHide) {
			Surface.hide(context);
		}
	});

	PubSub.sub('aloha.ui.scope.change', function () {
		Container.showContainersForContext(context);
		primaryScopeForegroundTab(Scopes.getPrimaryScope());
	});

	function getToolbarSettings() {
		var userSettings = Aloha.settings.toolbar,
		    defaultSettings = Settings.defaultToolbarSettings;
		if (!userSettings) {
			return defaultSettings.tabs;
		}
		return Settings.combineToolbarSettings(
			userSettings.tabs || [],
			defaultSettings.tabs,
			userSettings.exclude || []
		);
	}

	function primaryScopeForegroundTab() {
		var tabs = toolbar._tabs,
		    primaryScope = Scopes.getPrimaryScope(),
		    settings,
		    i;
		for (i = 0; i < tabs.length; i++) {
			settings = tabs[i].settings;
			if ('object' === $.type(settings.showOn) && settings.showOn.scope === primaryScope) {
				tabs[i].tab.foreground();
				break;
			}
		}
	}

	/**
	 * Adopts a component instance into the UI.
	 *
	 * Usually, the implementation of this method will display the
	 * component, at a position in the UI given by the slot
	 * argument.
	 *
	 * @param slot
	 *        A position argument that is interpreted by the UI however it likes.
	 * @param component
	 *        An instance of a component to adopt into the given slot.
	 * @api
	 */
	function adoptInto(slot, component) {
		return toolbar.adoptInto(slot, component);
	}

	/**
	 * Shows the toolbar.
	 *
	 * By default, the toolbar will be hidden when no editable is
	 * activated, and shown when an editable is activated. Calling
	 * this function will show the toolbar regardless of whether an
	 * editable is activated.
	 *
	 * The toolbar will only become visible if tabs are visible as well.
	 * To make tabs visible, set a scope. For example
	 * Scopes.setScope('Aloha.continuoustext');		
	 *
	 * Please note that the toolbar will not remain visible if an
	 * editable is subsequently deactivated.
	 *
	 * @param {?Object} event
	 *        An optional event argument that caused the toolbar to be show.
	 *        Will be passed on to Aloha.settings.toolbar.tabs[i].showOn functions.
	 * @api
	 */
	function showToolbar(event) {
		Surface.show(context);
		Container.showContainersForContext(context, event);
	}

	/**
	 * This module is part of the Aloha API.
	 * It is valid to override this module via requirejs to provide a
	 * custom behaviour. An overriding module must implement all API
	 * methods. Every member must have an api annotation. No non-api
	 * members are allowed.
	 * @api
	 */
	return {
		/**
		 * Adopts a component instance into the UI.
		 *
		 * Usually, the implementation of this method will display the
		 * component, at a position in the UI given by the slot
		 * argument.
		 *
		 * @param slot
		 *        A position argument that is interpreted by the UI however it likes.
		 * @param component
		 *        An instance of a component to adopt into the given slot.
		 * @api
		 */
		adoptInto: adoptInto,
		showToolbar: showToolbar
	};
});

/**
 * Aloha Editor User Interface API Semantics
 * =========================================
 *
 * Note
 * ===
 *    * This documentation is "thinking out loud," and very much "work in work
 *      in progress--as is the Aloha UI API itself.
 *
 *    * For flexibility and ease, it seems that it would be best that the Aloha
 *      UI API will not constrain the developer to these semantics, but will
 *      naively assume that these semantics are observed.
 *
 * Components (Buttons, Labels, Icons)
 * ---
 * Aloha Editor represents its user interface using objects called
 * `components`.  A uniform interface for these components allows them to be
 * agnostic to what container they are rendered on.
 *
 * Controls (Buttons)
 * ---
 * Interactive components like buttons, are called `controls`, to distinguish
 * them from non-interactive components like labels, and icons.
 *
 * Containers (Tabs, Panels)
 * ---
 * In rendering the UI, components are organized in visual groups, and these
 * groups are in turn bundled onto `containers`.  Containers can be tabs, as in
 * the case of the floating menu, or panels like in the sidebar.  Containers
 * allow a collection of controls that represent a feature set to be rendered
 * as a group and to be brought in and out of view together.
 *
 * Surfaces (Toolbar, Sidebar, Ribbon)
 * ---
 * `Surfaces` are areas on a web page in which containers can be placed.  The
 * sidebar, and the toolbar are examples of such surfaces.  The possibility
 * exists for other surfaces to be defined--such as a ribbon, or a footer menu.
 *
 *
 * Class structure
 * ===
 *
 *         Surface
 *	          |
 *	     ,----+----.
 *       |         |
 *	  Toolbar   Sidebar
 *
 *
 *        Container
 *	          |
 *	     ,----+----.
 *       |         |
 *	   Panel      Tab
 *
 *
 *        Component
 *            |
 *	     ,----+----.
 *       |         |
 *	   Label    Control
 *
 *
 * Enforcing good dependencies
 * ===
 * In order to void subtle errors that arise from erroneous dependency
 * declarations, we will have ill-defined or missing dependencies fail early
 * and noisily.  We achieve this by never exposing individual modules through
 * the Aloha.ui hash unless absolutely necessary. Modules are accessibly only
 * through require.
 *
 * How this works:
 * For example, a ui module "uiModule" would not be exposed through
 * `Aloha.ui.uiModule` but from require's `define` call:
 *
 *		define([ 'ui/uiModule' ], function( uiModule ) {});
 *
 * or
 *
 *		var uiModule = Aloha.require( 'ui/uiModule' );
 * 
 * This will force more deliberate and precise usage of dependencies.  The
 * developer will have to know exactly which direct dependencies they are using;
 * any missing or unsuccessfully require dependency will not inadvertantly be
 * provided from another require somewhere else in Aloha thereby silencing an
 * error that will emerge unexpectedly if that require is ever removed.
 */

define('ui/ui', [
	'jquery',
	'ui/ui-plugin'
],
function (
	$,
	UiPlugin
) {
	

	/**
	 * A hash map of components mapped against the slots into which they have
	 * been adopted.
	 *
	 * @type {Object<string, Component>}
	 */
	var components = {};

	/**
	 * Adopts a component into the UI.
	 *
	 * Only adopted components will become part of the UI.
	 *
	 * Where the component is placed is decided by looking the name up
	 * in the configuration.
	 * 
	 * If adoption is successful, the component will have its
	 * adoptParent() method invoked by the container the component ends
	 * up in.
	 *
	 * @param {string?} name
	 *        Names the component to be adopted.
	 *        The name is used by the UI to identify the component,
	 *        and place the component.
	 *        Making multiple calles with different components but
	 *        the same name is valid.
	 *        If not given, settings must be given and contain a
	 *        name property.
	 * @param {Object?} SuperTypeOrInstance
	 *        Either a component type that will be instantiated, or an
	 *        already instantiated component instance.
	 * @param {Object?} settings
	 *        An optional map of component settings which will be used
	 *        to extend the SuperType.
	 *        Ignored if an component instance is given.
	 *        Must contain a name property if no name is given.
	 * @return
	 *        If a SuperTypeOrInstance is a SuperType, the new component
	 *        instantiated from SuperType, or the given component
	 *        instance.
	 * @api
	 */
	function adopt(name, SuperTypeOrInstance, settings) {
		var Type,
		    component;

		if ('string' !== $.type(name)) {
			settings = SuperTypeOrInstance;
			SuperTypeOrInstance = name;
			name = settings.name;
		} else {
			settings = $.extend({name: name}, settings);
		}

		if (!SuperTypeOrInstance.isInstance) {
			Type = settings ? SuperTypeOrInstance.extend(settings) : SuperTypeOrInstance;
			component = new Type();
		} else {
			component = SuperTypeOrInstance;
		}

		components[name] = component;
		UiPlugin.adoptInto(name, component);

		return component;
	}

	/**
	 * Retreives the component that was adopted at the given UI slot.
	 *
	 * @param {string} slot The name of the slot.
	 * @return {Component?} A component, or null if no slot was adopted
	 *                      into the slot.
	 */
	function getComponentAtSlot(slot) {
		return components[slot] || null;
	}

	return {
		adopt: adopt,
		getAdoptedComponent: getComponentAtSlot
	};
});

/* utils.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('ui/utils',['jquery', 'jqueryui'], function ($) {
	

	/**
	 * Wraps an element such that a label is displayed alongside it.
	 *
	 * Contrary to tooltips, a label is always visible and takes up
	 * place in the toolbar.
	 *
	 * The label will wrap the given element to make an implicit
	 * association between label and element (click on the label will
	 * give focus to a wrapped input element for example).
	 *
	 * @param {string} labelText
	 *       The already internationalized text the label should contain.
	 * @param {!jQuery} element
	 *       Any element to wrap.
	 * @return {!jQuery}
	 *       A new label element that wraps the given element.
	 */
	function wrapWithLabel(labelText, element) {
		return $('<label>', {'class': 'aloha-ui-label'})
			.append($('<span>', {'class': 'aloha-ui-label-text', 'text': labelText}))
			.append(element);
	}

	function makeButton(button, props, hasMenu) {
		button.uibutton({
			label: makeButtonLabel(props),
			text: !!(props.text || props.html),
			icons: {
				primary: props.icon || (props.iconUrl && 'aloha-ui-inline-icon-container') || null,
				secondary: (hasMenu && 'aloha-jqueryui-icon ui-icon-triangle-1-s') || null
			}
		});
		if (props.iconUrl) {
			button.uibutton('widget')
				.children('.ui-button-icon-primary')
				.append(makeButtonIconFromUrl(props.iconUrl));
		}
		return button;
	}

	function makeButtonLabel(props) {
		// TODO text should be escaped
		return props.html || props.text || props.tooltip;
	}

	function makeButtonLabelWithIcon(props) {
		var label = makeButtonLabel(props);
		if (props.iconUrl) {
			label = makeButtonIconFromUrl(props.iconUrl) + label;
		}
		return label;
	}

	function makeButtonIconFromUrl(iconUrl) {
		return '<img class="aloha-ui-inline-icon" src="' + iconUrl + '">';
	}

	function makeButtonElement(attr) {
		// Set type to button to avoid problems with IE which
		// considers buttons to be of type submit by default. One
		// problem that occurd was that hitting enter inside a
		// text-input caused a click event in the button right next
		// to it.
		return $('<button>', attr).attr('type', 'button');
	}

	return {
		wrapWithLabel: wrapWithLabel,
		makeButton: makeButton,
		makeButtonElement: makeButtonElement,
		makeButtonLabel: makeButtonLabel,
		makeButtonLabelWithIcon: makeButtonLabelWithIcon,
		makeButtonIconFromUrl: makeButtonIconFromUrl
	};
});

define('ui/vendor/jquery-ui-autocomplete-html',["jquery", "jqueryui"], function(jQuery){
/*
 * jQuery UI Autocomplete HTML Extension
 *
 * Copyright 2010, Scott González (http://scottgonzalez.com)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * http://github.com/scottgonzalez/jquery-ui-extensions
 */
(function( $ ) {

var proto = $.ui.autocomplete.prototype,
	initSource = proto._initSource;

function filter( array, term ) {
	var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
	return $.grep( array, function(value) {
		return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() );
	});
}

$.extend( proto, {
	_initSource: function() {
		if ( this.options.html && $.isArray(this.options.source) ) {
			this.source = function( request, response ) {
				response( filter( this.options.source, request.term ) );
			};
		} else {
			initSource.call( this );
		}
	},

	_renderItem: function( ul, item) {
		return $( "<li></li>" )
			.data( "item.autocomplete", item )
			.append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) )
			.appendTo( ul );
	}
});

})( jQuery );
});

/**
 * This is a helper module for porting plugins from the old
 * ui-attributefield.js in the aloha core to the new ui-plugin.
 * This interface is obsolete and must not be used for new implementations.
 */
define('ui/port-helper-attribute-field',[
	'jquery',
	'ui/ui',
	'ui/component',
	'ui/scopes',
	'ui/context',
	'ui/utils',
	'aloha/repositorymanager',
	'aloha/selection',
	'aloha/console',
	'ui/vendor/jquery-ui-autocomplete-html'
], function (
	$,
	Ui,
	Component,
	Scopes,
	Context,
	Utils,
	RepositoryManager,
	Selection,
	console
) {
	

	// Main responsibilities implemented by the attribute-field are
	//
	// * setting a target object and attribute and the subsequent change of
	//   that target attribute (example link plugin, table plugin)
	// * background color highlighting of the target object (example link plugin)
	// * a placeholder in the attribute field with a grey foreground color
	//   (example link plugin)
	// * maintain a current repository item to distinguish link plugin
	//   repository items from literal values typed in the attribute field
	//   (example link plugin)
	// * repository manager markObject on the target object if a repository
	//   item was selected (example link plugin)

	/**
	 * Creates a new attribute field.
	 *
	 * @param {!Object} props
	 *        A map containing the following properties
	 *        name         -
	 *        label        - some text that will be displayed alongside
	 *                       the attribute field,
	 *        labelClass   - a class to identify the label element,
	 *        valueField   -
	 *        displayField -
	 *        objectTypeFilter -
	 *        placeholder  -
	 *        noTargetHighlight -
	 *        targetHighlightCass - a class to be identify focused element
	 *        cls          -
	 *        width        -
	 *        scope        -
	 *        element      - the <input> element to use.
	 *                       If not supplied, a new one will be created.
	 */
	var AttributeField = function (props) {
		var valueField = props.valueField || 'id',
			displayField = props.displayField || 'name',
			objectTypeFilter = props.objectTypeFilter || ['all'],
			placeholder = props.placeholder,
			noTargetHighlight = !!props.noTargetHighlight,
			targetHighlightClass = props.targetHighlightClass,
			element = props.element ? $(props.element) : $('<input id="aloha-attribute-field-' + props.name + '">'),
			component,
			template,
			resourceItem,
			resourceValue,
			targetObject,
			targetAttribute,
			lastAttributeValue,
			additionalTargetObjects = [];

		if (props.cls) {
			element.addClass(props.cls);
		}
		if (props.width) {
			element.width(props.width);
		}

		component = Ui.adopt(props.name, Component, {
			scope: props.scope,
			init: function () {

				if (props.element) {
					this.element = element;
				} else {
					if (props.label) {
						this.element = Utils.wrapWithLabel(props.label, element);
						if (props.labelClass) {
							this.element.addClass(props.labelClass);
						}
					} else {
						// Why do we have to wrap the element in a span? It
						// doesn't seem to work otherwise.
						this.element = $('<span>').append(element);
					}
				}

				element.autocomplete({
					'html': true,
					'appendTo': Context.selector,
					'source': function (req, res) {
						RepositoryManager.query({
							queryString: req.term,
							objectTypeFilter: objectTypeFilter
						}, function (data) {
							res($.map(data.items, function (item) {
								return {
									label: parse(template, item),
									value: item.name,
									obj: item
								};
							}));
						});
					},
					"open": props.open,
					"select": onSelect
				});
			}
		});

		element
			.bind("focus", onFocus)
			.bind("blur", onBlur)
			.bind("keydown", onKeyDown)
			.bind("keyup", onKeyup)
			.bind("change", onChange);

		setPlaceholder();

		// Because IE7 doesn't give us the blur event when the editable
		// is deactivated and the toolbar disappears (other browsers do).
		// TODO unbind, otherwise mermory leak
		Aloha.bind('aloha-editable-deactivated', onBlur);

		/**
		 * Update the attribute in the target element
		 */
		function updateTarget() {
			// If this attribute field currently refers to a repository
			// item, and the user edits the contents of the input field,
			// this attribute field seizes to refer to the repository item.
			if (resourceItem && resourceValue !== getValue()) {
				resourceItem = null;
				resourceValue = null;
			}

			// This handles attribute updates for non-repository, literal urls typed into the input field.
			// Input values that refer to a repository item are handled via setItem().
			if ( ! resourceItem ) {
				setAttribute(targetAttribute, getValue());
			}
		}

		function onSelect(event, ui) {
			if (ui.item) {
				setItem(ui.item.obj);
			}
			finishEditing();
		}

		function onBlur() {
			finishEditing();
		}

		function onFocus(event, ui) {
			if (!$(event.target).is(':visible')) {
				// The check for visible fixes the bug that the background
				// color of the target element is not restored.
				// Rationale: it's possible for the input to receive the focus event,
				// for example if it was triggered programatically, even if
				// it isn't visible. Problem is, if it's not visible, then
				// it will not really get focused and consequently, there
				// will be no blur event either. However, we must be able to
				// assume that the blur event will be fired so that we can
				// clean up the background color.
				return;
			}
			changeTargetBackground();

			// Remove placeholder
			if (getValue() === placeholder) {
				setValue('');
			}
		}

		function onKeyDown(event) {
			// on ENTER or ESC leave the editing
			if (event.keyCode == 13 || event.keyCode == 27) {
				event.preventDefault();
			}
		}

		function onKeyup(event) {
			updateTarget();

			if ( ( event.keyCode == 13 || event.keyCode == 27 ) ) {
				// Set focus to link element and select the object
				Selection.getRangeObject().select();
				finishEditing();
			}
		}

		function onChange(event) {
			updateTarget();
		}

		function finishEditing() {
			restoreTargetBackground();

			if (!targetObject || lastAttributeValue === $(targetObject).attr(targetAttribute)) {
				return;
			}

			// when no resource item was selected, remove any marking of the target object
			if (!resourceItem) {
				RepositoryManager.markObject( targetObject );
			}

			if (getValue() === '') {
				setPlaceholder();
			}
		}

		/**
		 * Execute a function for every targets this attribute
		 *
		 * fields is pointing to.
		 * @param {Function} fn Function to execute for each target
		 */
		function executeForTargets(fn) {
			var target = $(targetObject);
			fn(target);
			for (var i = 0, len = additionalTargetObjects.length; i < len ; i++) {
				fn($(additionalTargetObjects[i]));
			}
		}

		/**
		 * Change target background so the targets are
		 * highlighted
		 */
		function changeTargetBackground() {
			var target = $(targetObject);
			if (targetHighlightClass) {
				executeForTargets(function (target) {
					target.addClass(targetHighlightClass);
				});
			}

			if (noTargetHighlight) {
				return;
			}

			// Make sure that multiple invokations of
			// changeTargetBackground don't set an incorrect
			// data-original-background-color.
			restoreTargetBackground();

			// set background color to give visual feedback which link is modified
			if (target.context && target.context.style &&
				target.context.style['background-color']) {
				executeForTargets(function (target) {
					target.attr('data-original-background-color',
					            target.context.style['background-color']);
				});
			}
			executeForTargets(function (target) {
				target.css('background-color', '#80B5F2');
			});
		}

		function restoreTargetBackground() {
			var target = $(targetObject);
			if (targetHighlightClass) {
				executeForTargets(function (target) {
					target.removeClass(targetHighlightClass);
				});
			}
			if (noTargetHighlight) {
				return;
			}
			// Remove the highlighting and restore original color if was set before
			var color = target.attr('data-original-background-color');
			executeForTargets(function (target) {
				target.css('background-color', color || '');
			});
			if (!target.attr('style')) {
				executeForTargets(function (target) {
					target.removeAttr('style');
				});
			}
			executeForTargets(function (target) {
				target.removeAttr('data-original-background-color');
			});
		}

		function parse(template, item) {
			return template.replace(/\{([^}]+)\}/g, function (_, name) {
				return name in item ? item[name] : "";
			});
		}

		function setPlaceholder() {
			if (null === placeholder) {
				return;
			}
			element.css('color', '#AAA');
			element.val(placeholder);
		}

		function setTemplate(tmpl) {
			template = tmpl;
		}

		/**
		 * When at least on objectType is set the value in the Attribute field does a query to all registered repositories.
		 * @param {Array} objectTypeFilter The array of objectTypeFilter to be searched for.
		 * @void
		 */
		function setObjectTypeFilter(objTypeFilter) {
			objectTypeFilter = objTypeFilter;
		}

		/**
		 * Adding a listener to the field
		 * @param {String} eventname The name of the event. Ex. 'keyup'
		 * @param {function} handler The function that should be called when the event happens.
		 */
		function addListener(eventName, handler) {
			element.bind(eventName, $.proxy(handler, attrField));
		}

		function getValue() {
			return element.val();
		}

		function setValue(value) {
			element.val(value);
			element.css('color', 'black');
		}

		function setItem(item) {
			resourceItem = item;

			if (item) {
				// TODO split display field by '.' and get corresponding attribute, because it could be a properties attribute.
				var v = item[displayField];
				// set the value into the field
				setValue(v);
				// store the value to be the "reference" value for the currently selected resource item
				resourceValue = v;
				setAttribute(targetAttribute, item[valueField]);
				executeForTargets(function (target) { RepositoryManager.markObject(target, item); });
			} else {
				resourceValue = null;
			}
			
			element.trigger('item-change');
		}

		function getItem() {
			return resourceItem;
		}

		/**
		 * Sets an attribute optionally based on a regex on reference
		 * @param {String} attr The Attribute name which should be set. Ex. "lang"
		 * @param {String} value The value to set. Ex. "de-AT"
		 * @param {String} regex The regex when the attribute should be set. The regex is applied to the value of refernece.
		 * @param {String} reference The value for the regex.
		 */
		function setAttribute(attr, value, regex, reference) {
			if (targetObject) {
				// check if a reference value is submitted to check against with a regex
				if (typeof reference === 'undefined' || reference.match(new RegExp(regex))) {
					executeForTargets(function (target) {
						target.attr(attr, value);
					});
				} else {
					$(targetObject).removeAttr(attr);
				}
			}
		}

		/**
		 * Sets the target Object of which the Attribute should be modified
		 * @param {jQuery} obj the target object
		 * @param {String} attr Attribute to be modified ex. "href" of a link
		 * @void
		 */
		function setTargetObject(obj, attr) {
			var $obj = $(obj); // Just to be sure, we have a jQuery object.
			targetObject = obj;
			targetAttribute = attr;
			additionalTargetObjects = [];
			
			if (obj && attr) {
				lastAttributeValue = $obj.attr(attr);
				setValue(lastAttributeValue);

				setItem(null);
			} else {
				setValue('');

				setItem(null);

				return;
			}

			var ignoreAutoValues = $obj.attr('data-ignore-auto-values');

			// check whether a repository item is linked to the object
			RepositoryManager.getObject( obj, function ( items ) {
				if (items && items.length > 0) {
					if (ignoreAutoValues) {
						$obj.attr('data-ignore-auto-values', ignoreAutoValues);
					}

					setItem(items[0]);

					if (ignoreAutoValues) {
						$obj.removeAttr('data-ignore-auto-values');
					}
				}
			});
		}

		function addAdditionalTargetObject(targetObj) {
			additionalTargetObjects.push(targetObj);
		}

		function getTargetObject() {
			return targetObject;
		}

		function focus() {
			component.focus();
			element.focus();
		}

		function foreground() {
			component.foreground();
		}

		function show() {
			element.show();
		}

		function hide() {
			element.hide();
		}
		
		/**
		 * Disables input text, so the text can not be edit.
		 */
		function disableInput() {
			element.attr('disabled','disabled'); 
		}
		
		/**
		 * Enables input text, so the text can be edit.
		 */
		function enableInput() {
			element.removeAttr('disabled');
		}

		function getInputId(){
			return element.attr("id");
		}

		function hasInputElem() {
			return true;
		}

		function getInputElem() {
			return element[0];
		}

		function getInputJQuery() {
			return element;
		}

		var attrField = {
			getInputElem: getInputElem,
			hasInputElem: hasInputElem,
			getInputId: getInputId,
			hide: hide,
			show: show,
			foreground: foreground,
			focus: focus,
			getTargetObject: getTargetObject,
			setTargetObject: setTargetObject,
			addAdditionalTargetObject: addAdditionalTargetObject,
			setAttribute: setAttribute,
			getItem: getItem,
			setItem: setItem,
			setValue: setValue,
			getValue: getValue,
			addListener: addListener,
			setObjectTypeFilter: setObjectTypeFilter,
			setTemplate: setTemplate,
			setPlaceholder: setPlaceholder,
			getInputJQuery: getInputJQuery,
			enableInput: enableInput,
			disableInput: disableInput
		};

		return attrField;
	};

	return AttributeField;
});

/**
 * NOTES:
 *    - Why do we maintain 2 references to the DOM element for a button
 *    component (this.element = this.buttonElement)?
 */

define('ui/button',[
	'jquery',
	'ui/component',
	'ui/utils',
	'jqueryui'
],
function (jQuery, Component, Utils) {
	

	/**
	 * Implements a simple button component.  All interactive UI controls (that
	 * is--anything that is not a label) will most probably extend the Button
	 * component.
	 *
	 * An extending class should optionally define the following properties
	 * tooltip - the internationalized tooltip text,
	 * icon    - the icon class,
	 * class   - an additional class to add to the button element,
	 * click   - the click handler for the button.
	 *
	 * @class
	 * @name Button
	 * @extends {Component}
	 */
	var Button = Component.extend({

		/**
		 * Initializes this button instance.
		 * The initialization is done when the component is rendered, not when
		 * it is created.  This is necessary to allow multiple renderings of
		 * the same component.  For example, you may want a component to be in
		 * the toolbar and in the sidebar.
		 *
		 * @override
		 */
		init: function () {
			this._super();
			this.createButtonElement();
			Utils.makeButton(this.buttonElement, this)
				.uibutton('widget')
				.tooltip({
					placement: 'bottom',
					position: {
						my: 'left top',
						at: 'right bottom'
					}
				})
				.zIndex(0)    // fix for IE7,8 so the icon shown disabled
				.click(jQuery.proxy(function () {

					// Ensure tooltips are always hidden after a button
					// is clicked because sometimes the tooltip doesn't
					// get closed automatically, for example after table
					// cells are merged or split.
					// IE needs the force argument to be true, Chrome doesn't.
					// The event argument can be ignored.
					this.closeTooltip();

					this._onClick();
				}, this));
		},

		closeTooltip: function () {
			// 'close', /*event*/, /*force*/
			this.buttonElement.tooltip('hide');
		},

		/**
		 * May be overridden by component subclasses to implement
		 * component-specific behaviour.  The default implementation just calls
		 * the public click method.
		 *
		 * @protected
		 */
		_onClick: function () {
			this.click();
		},

		/**
		 * Handles mouse clicks on this button's rendered elements.
		 */
		click: function () {},

		/**
		 * Creates the DOM element to be rendered for user interaction.
		 *
		 * @return {jQuery<HTMLElement>}
		 */
		createButtonElement: function () {
			var button = Utils.makeButtonElement();

			if (this['class']) {
				button.addClass(this['class']);
			}
			this.element = this.buttonElement = button;

			var that = this;
			button.bind('mouseleave', function () {
				that.closeTooltip();
			});

			return button;
		},

		/**
		 * Shows the button in a greyed-out inactive (unclickable) state.
		 */
		disable: function () {
			this.element.uibutton('option', 'disabled', false);
		},

		/**
		 * Enables the button again after it has previously been disabled.
		 */
		enable: function (enable_opt) {
			this.element.uibutton('option', 'disabled', enable_opt === false);
		}
	});

	return Button;
});

define('ui/toggleButton',[
	'jquery',
	'ui/button',
	'jqueryui'
],
function (jQuery, Button) {
	

	var idCounter = 0;

	/**
	 * ToggleButton control. Extends the Button component type to provide an
	 * easy way to create buttons that can transition between "checked" and
	 * "unchecked" states.
	 *
	 * @class
	 * @name ToggleButton
	 * @extends {Button}
	 */
	var ToggleButton = Button.extend({

		_checked: false,

		/**
		 * Sets the state of the toggleButton and updates its visual display
		 * accordingly.
		 *
		 * @param {boolean} toggled Whether the button is to be set to the
		 *                          "toggled/checked" state.
		 */
		setState: function (toggled) {
			// It is very common to set the button state on every
			// selection change even if the state hasn't changed.
			// Profiling showed that this is very inefficient.
			if (this._checked === toggled) {
				return;
			}
			this._checked = toggled;
			if (toggled) {
				this.element.addClass("aloha-button-active");
			} else {
				this.element.removeClass("aloha-button-active");
			}
		},

		getState: function () {
			return this._checked;
		},

		_onClick: function () {
			this.setState(! this._checked);
			this.click();
		}
	});

	return ToggleButton;
});

define('link/nls/i18n',{
	"root":  {
		"button.addlink.tooltip": "Insert Link",
		"button.removelink.tooltip": "Remove Link",
		"newlink.defaulttext": "New Link",
		"floatingmenu.tab.link": "Link",
		"link.target.self": "Self",
		"link.target.blank": "Blank",
		"link.target.parent": "Parent",
		"link.target.top": "Top",
		"link.target.framename": "Framename",
		"link.target.legend": "Target",
		"link.title.legend": "Title",
		"href.lang.legend": "Language",
		"insertLink": "ctrl+k"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* keys.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/keys',['jquery'], function ($) {
	

	var KEYCODE_TOKEN_MAP = (function () {
		var map = {};
		$.each({
			8  : 'backspace',
			9  : 'tab',
			13 : 'enter',
			16 : 'shift control alt',
			20 : 'capslock',
			27 : 'escape',
			32 : 'space',
			33 : 'pageup',
			34 : 'pagedown',
			37 : 'left up right down',
			45 : 'insert',
			46 : 'delete',
			48 : '0 1 2 3 4 5 6 7 8 9',
			65 : 'a b c d e f g h i j k l m n o p q r s t u v w x y z',
			112: 'f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15'
		}, function (key, tokens) {
			var start = parseInt(key, 10);
			$.each(tokens.split(' '), function (i, token) {
				map[start + i] = token;
			});
		});
		return map;
	}());

	function getTokenByKeyCode(keyCode) {
		return KEYCODE_TOKEN_MAP[keyCode];
	}

	return {
		getToken: getTokenByKeyCode,
		keyCodeTokenMap: $.extend({}, KEYCODE_TOKEN_MAP)
	};
});



define('link/../../../shared/languages/./iso639-1-de',
		[],
		function() {

			var iso = {
				"aa" : {
					"name" : "Afar"
				},
				"ab" : {
					"name" : "Abchasen"
				},
				"af" : {
					"name" : "Afrikaans"
				},
				"ak" : {
					"name" : "Akan"
				},
				"sq" : {
					"name" : "Albanisch"
				},
				"am" : {
					"name" : "Amharisch"
				},
				"ar" : {
					"name" : "Arabisch",
					"flag" : "ae"
				},
				"an" : {
					"name" : "Aragonesisch"
				},
				"hy" : {
					"name" : "Armenisch"
				},
				"as" : {
					"name" : "Assamesisch"
				},
				"av" : {
					"name" : "Avarisch"
				},
				"ae" : {
					"name" : "Avestan"
				},
				"ay" : {
					"name" : "Aymara"
				},
				"az" : {
					"name" : "Aserbaidschanisch"
				},
				"ba" : {
					"name" : "Baschkirisch"
				},
				"bm" : {
					"name" : "Bambara"
				},
				"eu" : {
					"name" : "Baskisch"
				},
				"be" : {
					"name" : "Weißrussisch",
					"flag" : "by"
				},
				"bn" : {
					"name" : "Bengalisch"
				},
				"bh" : {
					"name" : "Bihari Sprachen"
				},
				"bi" : {
					"name" : "Bislama"
				},
				"bs" : {
					"name" : "Bosnisch"
				},
				"br" : {
					"name" : "Bretonisch"
				},
				"bg" : {
					"name" : "Bulgarisch",
					"flag" : "bg"
				},
				"my" : {
					"name" : "Birmanisch"
				},
				"ca" : {
					"name" : "Katalanisch, Valencia",
					"flag" : "es"
				},
				"ch" : {
					"name" : "Chamorro"
				},
				"ce" : {
					"name" : "Tschetschenisch"
				},
				"zh" : {
					"name" : "Chinesisch"
				},
				"cu" : {
					"name" : "Kirchenslawisch; Altslawisch; Altbulgarisch; Altkirchenslawisch"
				},
				"cv" : {
					"name" : "Tschuwaschische Sprache"
				},
				"kw" : {
					"name" : "Cornish"
				},
				"co" : {
					"name" : "Korsisch"
				},
				"cr" : {
					"name" : "Cree"
				},
				"cs" : {
					"name" : "Tschechisch"
				},
				"da" : {
					"name" : "Dänisch",
					"flag" : "dk"
				},
				"dv" : {
					"name" : "Divehi; Dhivehi; maledivische"
				},
				"nl" : {
					"name" : "Niederländisch, Flämisch"
				},
				"dz" : {
					"name" : "Dzongkha"
				},
				"en" : {
					"name" : "Englisch",
					"flag" : "gb"
				},
				"eo" : {
					"name" : "Esperanto"
				},
				"et" : {
					"name" : "Estnisch",
					"flag" : "ee"
				},
				"ee" : {
					"name" : "Ewe"
				},
				"fo" : {
					"name" : "Färöisch"
				},
				"fj" : {
					"name" : "Fidschi"
				},
				"fi" : {
					"name" : "Finnisch",
					"flag" : "fi"
				},
				"fr" : {
					"name" : "Französisch"
				},
				"fy" : {
					"name" : "Westfriesisch"
				},
				"ff" : {
					"name" : "Fulah"
				},
				"ka" : {
					"name" : "Georgisch"
				},
				"de" : {
					"name" : "Deutsch",
					"flag" : "de"
				},
				"gd" : {
					"name" : "Gälisch, Schottisch-Gälisch"
				},
				"ga" : {
					"name" : "Irisch",
					"flag" : "ie"
				},
				"gl" : {
					"name" : "Galizisch"
				},
				"gv" : {
					"name" : "Manx"
				},
				"el" : {
					"name" : "Griechisch, Modern (1453 -)"
				},
				"gn" : {
					"name" : "Guarani"
				},
				"gu" : {
					"name" : "Gujarati"
				},
				"ht" : {
					"name" : "Haitianisch"
				},
				"ha" : {
					"name" : "Hausa"
				},
				"he" : {
					"name" : "Hebräisch",
					"flag" : "il"
				},
				"hz" : {
					"name" : "Herero"
				},
				"hi" : {
					"name" : "Hindi",
					"flag" : "in"
				},
				"ho" : {
					"name" : "Hiri Motu"
				},
				"hr" : {
					"name" : "Kroatisch",
					"flag" : "hr"
				},
				"hu" : {
					"name" : "Ungarisch",
					"flag" : "hu"
				},
				"ig" : {
					"name" : "Igbo"
				},
				"is" : {
					"name" : "Isländisch"
				},
				"io" : {
					"name" : "Ido"
				},
				"ii" : {
					"name" : "Sichuan Yi; Nuosu"
				},
				"iu" : {
					"name" : "Inuktitut"
				},
				"ie" : {
					"name" : "Interlingue; Okzidental"
				},
				"ia" : {
					"name" : "Interlingua (International Auxiliary Language Association)"
				},
				"id" : {
					"name" : "Indonesisch",
					"flag" : "id"
				},
				"ik" : {
					"name" : "Inupiaq"
				},
				"it" : {
					"name" : "Italienisch",
					"flag" : "it"
				},
				"jv" : {
					"name" : "Javaner"
				},
				"ja" : {
					"name" : "Japanisch",
					"flag" : "jp"
				},
				"kl" : {
					"name" : "Kalaallisut; Grönländisch"
				},
				"kn" : {
					"name" : "Kannada"
				},
				"ks" : {
					"name" : "Kashmiri"
				},
				"kr" : {
					"name" : "Kanuri"
				},
				"kk" : {
					"name" : "Kasachisch"
				},
				"km" : {
					"name" : "Mittel-Khmer"
				},
				"ki" : {
					"name" : "Kikuyu; Gikuyu"
				},
				"rw" : {
					"name" : "Kinyarwanda"
				},
				"ky" : {
					"name" : "Kirgisisch"
				},
				"kv" : {
					"name" : "Komi"
				},
				"kg" : {
					"name" : "Kongo"
				},
				"ko" : {
					"name" : "Koreanisch",
					"flag" : "kr"
				},
				"kj" : {
					"name" : "Kuanyama; Kwanyama"
				},
				"ku" : {
					"name" : "Kurdisch"
				},
				"lo" : {
					"name" : "Lao"
				},
				"la" : {
					"name" : "Latein"
				},
				"lv" : {
					"name" : "Lettisch",
					"flag" : "lv"
				},
				"li" : {
					"name" : "Limburgisch"
				},
				"ln" : {
					"name" : "Lingála"
				},
				"lt" : {
					"name" : "Litauisch",
					"flag" : "lt"
				},
				"lb" : {
					"name" : "Luxemburgisch, Lëtzebuergesch"
				},
				"lu" : {
					"name" : "Luba-Katanga"
				},
				"lg" : {
					"name" : "Ganda"
				},
				"mk" : {
					"name" : "Mazedonisch"
				},
				"mh" : {
					"name" : "Marshallese"
				},
				"ml" : {
					"name" : "Malayalam"
				},
				"mi" : {
					"name" : "Maori"
				},
				"mr" : {
					"name" : "Marathi"
				},
				"ms" : {
					"name" : "Malaiisch"
				},
				"mg" : {
					"name" : "Malagasy"
				},
				"mt" : {
					"name" : "Maltesisch",
					"flag" : "mt"
				},
				"mn" : {
					"name" : "Mongolisch"
				},
				"na" : {
					"name" : "Nauru"
				},
				"nv" : {
					"name" : "Navajo; Navaho"
				},
				"nr" : {
					"name" : "Ndebele, Süd, Süd-Ndebele"
				},
				"nd" : {
					"name" : "Ndebele, Nord, Nord-Ndebele"
				},
				"ng" : {
					"name" : "Ndonga"
				},
				"ne" : {
					"name" : "Nepali"
				},
				"nn" : {
					"name" : "Norwegisch Nynorsk"
				},
				"nb" : {
					"name" : "Bokmål, Norwegisch, Norwegisch Bokmål"
				},
				"no" : {
					"name" : "Norwegisch",
					"flag" : "no"
				},
				"ny" : {
					"name" : "Chichewa; Chewa; Nyanja"
				},
				"oc" : {
					"name" : "Occitan (post 1500); provenzalischen"
				},
				"oj" : {
					"name" : "Ojibwa"
				},
				"or" : {
					"name" : "Oriya"
				},
				"om" : {
					"name" : "Oromo"
				},
				"os" : {
					"name" : "Ossetisch"
				},
				"pa" : {
					"name" : "Panjabi, Punjabi"
				},
				"fa" : {
					"name" : "Persisch"
				},
				"pi" : {
					"name" : "Pali"
				},
				"pl" : {
					"name" : "Polnisch",
					"flag" : "pl"
				},
				"pt" : {
					"name" : "Portugiesisch",
					"flag" : "pt"
				},
				"ps" : {
					"name" : "Paschtu, Paschtu"
				},
				"qu" : {
					"name" : "Quechua"
				},
				"rm" : {
					"name" : "Rätoromanisch"
				},
				"ro" : {
					"name" : "Rumänisch; Moldawisch; Moldauisch"
				},
				"rn" : {
					"name" : "Rundi"
				},
				"ru" : {
					"name" : "Russisch",
					"flag" : "ru"
				},
				"sg" : {
					"name" : "Sango"
				},
				"sa" : {
					"name" : "Sanskrit"
				},
				"si" : {
					"name" : "Singhalesisch"
				},
				"sk" : {
					"name" : "Slowakisch"
				},
				"sl" : {
					"name" : "Slowenisch",
					"flag" : "si"
				},
				"se" : {
					"name" : "Nord Sami"
				},
				"sm" : {
					"name" : "Samoaner"
				},
				"sn" : {
					"name" : "Shona"
				},
				"sd" : {
					"name" : "Sindhi"
				},
				"so" : {
					"name" : "Somali"
				},
				"st" : {
					"name" : "Sotho, Südlich"
				},
				"es" : {
					"name" : "Spanisch, Kastilisch",
					"flag" : "es"
				},
				"sc" : {
					"name" : "Sardisch"
				},
				"sr" : {
					"name" : "Serbisch",
					"flag" : "rs"
				},
				"ss" : {
					"name" : "Swati"
				},
				"su" : {
					"name" : "Sundanesisch"
				},
				"sw" : {
					"name" : "Swahili"
				},
				"sv" : {
					"name" : "Schwedisch",
					"flag" : "se"
				},
				"ty" : {
					"name" : "Tahitianisch"
				},
				"ta" : {
					"name" : "Tamilisch"
				},
				"tt" : {
					"name" : "Tatar"
				},
				"te" : {
					"name" : "Telugu"
				},
				"tg" : {
					"name" : "Tadschikisch"
				},
				"tl" : {
					"name" : "Tagalog"
				},
				"th" : {
					"name" : "Thailändisch",
					"flag" : "th"
				},
				"bo" : {
					"name" : "Tibetisch"
				},
				"ti" : {
					"name" : "Tigrinya"
				},
				"to" : {
					"name" : "Tonga (Tonga-Inseln)"
				},
				"tn" : {
					"name" : "Tswana"
				},
				"ts" : {
					"name" : "Tsonga"
				},
				"tk" : {
					"name" : "Turkmenisch"
				},
				"tr" : {
					"name" : "Türkisch",
					"flag" : "tr"
				},
				"tw" : {
					"name" : "Twi"
				},
				"ug" : {
					"name" : "Uigurisch"
				},
				"uk" : {
					"name" : "Ukrainisch",
					"flag" : "ua"
				},
				"ur" : {
					"name" : "Urdu"
				},
				"uz" : {
					"name" : "Usbekisch"
				},
				"ve" : {
					"name" : "Venda"
				},
				"vi" : {
					"name" : "Vietnamesisch",
					"flag" : "vn"
				},
				"vo" : {
					"name" : "Volapük"
				},
				"cy" : {
					"name" : "Walisisch"
				},
				"wa" : {
					"name" : "Wallonisch"
				},
				"wo" : {
					"name" : "Wolof"
				},
				"xh" : {
					"name" : "Xhosa"
				},
				"yi" : {
					"name" : "Jiddisch"
				},
				"yo" : {
					"name" : "Yoruba"
				},
				"za" : {
					"name" : "Zhuang; Chuang"
				},
				"zu" : {
					"name" : "Zulu"
				}
			};
			
			return iso;
		});

define('link/../../../shared/languages/./iso639-2-de',
		[],
		function() {

			var iso = {
				"aar" : {
					"name" : "Afar"
				},
				"abk" : {
					"name" : "Abchasen"
				},
				"ace" : {
					"name" : "Achinese"
				},
				"ach" : {
					"name" : "Acholi"
				},
				"ada" : {
					"name" : "Adangme"
				},
				"ady" : {
					"name" : "Adyghe; Adygei"
				},
				"afa" : {
					"name" : "Afro-Asiatische Sprachen"
				},
				"afh" : {
					"name" : "Afrihili"
				},
				"afr" : {
					"name" : "Afrikaans"
				},
				"ain" : {
					"name" : "Ainu"
				},
				"aka" : {
					"name" : "Akan"
				},
				"akk" : {
					"name" : "Akkadisch"
				},
				"alb" : {
					"name" : "Albanisch"
				},
				"sqi" : {
					"name" : "Albanisch",
					"flag" : "al"
				},
				"ale" : {
					"name" : "Aleutisch"
				},
				"alg" : {
					"name" : "Algonkin-Sprachen"
				},
				"alt" : {
					"name" : "Südaltai"
				},
				"amh" : {
					"name" : "Amharisch"
				},
				"ang" : {
					"name" : "Englisch, Alt (ca.450-1100)"
				},
				"anp" : {
					"name" : "Angika"
				},
				"apa" : {
					"name" : "Apache Sprachen"
				},
				"ara" : {
					"name" : "Arabisch",
					"flag" : "ae"
				},
				"arc" : {
					"name" : "Offizielles Aramäisch (700-300 v. Chr.); Reichsaramäisch (700-300 v. Chr.)"
				},
				"arg" : {
					"name" : "Aragonesisch"
				},
				"arm" : {
					"name" : "Armenisch"
				},
				"hye" : {
					"name" : "Armenisch"
				},
				"arn" : {
					"name" : "Mapudungun; Mapuche"
				},
				"arp" : {
					"name" : "Arapaho"
				},
				"art" : {
					"name" : "Künstliche Sprachen"
				},
				"arw" : {
					"name" : "Arawak"
				},
				"asm" : {
					"name" : "Assamesisch"
				},
				"ast" : {
					"name" : "Asturisch"
				},
				"ath" : {
					"name" : "Athapascan Sprachen"
				},
				"aus" : {
					"name" : "Australische Sprachen"
				},
				"ava" : {
					"name" : "Avarisch"
				},
				"ave" : {
					"name" : "Avestan"
				},
				"awa" : {
					"name" : "Awadhi"
				},
				"aym" : {
					"name" : "Aymara"
				},
				"aze" : {
					"name" : "Aserbaidschanisch"
				},
				"bad" : {
					"name" : "Banda Sprachen"
				},
				"bai" : {
					"name" : "Bamileke Sprachen"
				},
				"bak" : {
					"name" : "Baschkirisch"
				},
				"bal" : {
					"name" : "Baluchi"
				},
				"bam" : {
					"name" : "Bambara"
				},
				"ban" : {
					"name" : "Balinesisch"
				},
				"baq" : {
					"name" : "Baskisch"
				},
				"eus" : {
					"name" : "Baskisch"
				},
				"bas" : {
					"name" : "Basa"
				},
				"bat" : {
					"name" : "Baltische Sprachen"
				},
				"bej" : {
					"name" : "Beja; Bedawiyet"
				},
				"bel" : {
					"name" : "Weißrussisch",
					"flag" : "by"
				},
				"bem" : {
					"name" : "Bemba"
				},
				"ben" : {
					"name" : "Bengalisch"
				},
				"ber" : {
					"name" : "Berbersprachen"
				},
				"bho" : {
					"name" : "Bhojpuri"
				},
				"bih" : {
					"name" : "Bihari Sprachen"
				},
				"bik" : {
					"name" : "Bikol"
				},
				"bin" : {
					"name" : "Bini, Edo"
				},
				"bis" : {
					"name" : "Bislama"
				},
				"bla" : {
					"name" : "Siksika"
				},
				"bnt" : {
					"name" : "Bantusprachen"
				},
				"bos" : {
					"name" : "Bosnisch"
				},
				"bra" : {
					"name" : "Braj"
				},
				"bre" : {
					"name" : "Bretonisch"
				},
				"btk" : {
					"name" : "Batak Sprachen"
				},
				"bua" : {
					"name" : "Buriatisch"
				},
				"bug" : {
					"name" : "Buginesisch"
				},
				"bul" : {
					"name" : "Bulgarisch",
					"flag" : "bg"
				},
				"bur" : {
					"name" : "Birmanisch"
				},
				"mya" : {
					"name" : "Birmanisch"
				},
				"byn" : {
					"name" : "Blin; Bilin"
				},
				"cad" : {
					"name" : "Caddo"
				},
				"cai" : {
					"name" : "Zentralamerikanische indische Sprachen"
				},
				"car" : {
					"name" : "Galibi Carib"
				},
				"cat" : {
					"name" : "Katalanisch, Valencia",
					"flag" : "es"
				},
				"cau" : {
					"name" : "Kaukasische Sprachen"
				},
				"ceb" : {
					"name" : "Cebuano"
				},
				"cel" : {
					"name" : "Keltische Sprachen"
				},
				"cha" : {
					"name" : "Chamorro"
				},
				"chb" : {
					"name" : "Chibcha"
				},
				"che" : {
					"name" : "Tschetschenisch"
				},
				"chg" : {
					"name" : "Dschag."
				},
				"chi" : {
					"name" : "Chinesisch"
				},
				"zho" : {
					"name" : "Chinesisch",
					"flag" : "cn"
				},
				"chk" : {
					"name" : "Chuukese"
				},
				"chm" : {
					"name" : "Mari"
				},
				"chn" : {
					"name" : "Chinook-Jargon"
				},
				"cho" : {
					"name" : "Choctaw"
				},
				"chp" : {
					"name" : "Chipewyan; Dene Suline"
				},
				"chr" : {
					"name" : "Cherokee"
				},
				"chu" : {
					"name" : "Kirchenslawisch; Altslawisch; Altbulgarisch; Altkirchenslawisch"
				},
				"chv" : {
					"name" : "Tschuwaschische Sprache"
				},
				"chy" : {
					"name" : "Cheyenne"
				},
				"cmc" : {
					"name" : "Chamic Sprachen"
				},
				"cop" : {
					"name" : "Koptisch"
				},
				"cor" : {
					"name" : "Cornish"
				},
				"cos" : {
					"name" : "Korsisch"
				},
				"cpe" : {
					"name" : "Kreolsprachen, englisch-basiert"
				},
				"cpf" : {
					"name" : "Kreolsprachen, französisch-basiert"
				},
				"cpp" : {
					"name" : "Kreolsprachen, portugiesisch-basiert"
				},
				"cre" : {
					"name" : "Cree"
				},
				"crh" : {
					"name" : "Krimtatarisch; Krim-türkisch"
				},
				"crp" : {
					"name" : "Kreolsprachen"
				},
				"csb" : {
					"name" : "Kaschubisch"
				},
				"cus" : {
					"name" : "Kuschitische Sprachen"
				},
				"cze" : {
					"name" : "Tschechisch"
				},
				"ces" : {
					"name" : "Tschechisch",
					"flag" : "cz"
				},
				"dak" : {
					"name" : "Dakota"
				},
				"dan" : {
					"name" : "Dänisch",
					"flag" : "dk"
				},
				"dar" : {
					"name" : "Dargwa"
				},
				"day" : {
					"name" : "Land Dayak Sprachen"
				},
				"del" : {
					"name" : "Delaware"
				},
				"den" : {
					"name" : "Slave (Athapascan)"
				},
				"dgr" : {
					"name" : "Dogrib"
				},
				"din" : {
					"name" : "Dinka"
				},
				"div" : {
					"name" : "Divehi; Dhivehi; maledivische"
				},
				"doi" : {
					"name" : "Dogri"
				},
				"dra" : {
					"name" : "Dravidische Sprachen"
				},
				"dsb" : {
					"name" : "Niedersorbisch"
				},
				"dua" : {
					"name" : "Duala"
				},
				"dum" : {
					"name" : "Niederländisch, Mittel (ca.1050-1350)"
				},
				"dut" : {
					"name" : "Niederländisch, Flämisch"
				},
				"nld" : {
					"name" : "Niederländisch, Flämisch",
					"flag" : "nl"
				},
				"dyu" : {
					"name" : "Dyula"
				},
				"dzo" : {
					"name" : "Dzongkha"
				},
				"efi" : {
					"name" : "Efik"
				},
				"egy" : {
					"name" : "Ägyptisch (Altertümlich)"
				},
				"eka" : {
					"name" : "Ekajuk"
				},
				"elx" : {
					"name" : "Elamite"
				},
				"eng" : {
					"name" : "Englisch",
					"flag" : "gb"
				},
				"enm" : {
					"name" : "Englisch, Mittle (1100-1500)"
				},
				"epo" : {
					"name" : "Esperanto"
				},
				"est" : {
					"name" : "Estnisch",
					"flag" : "ee"
				},
				"ewe" : {
					"name" : "Ewe"
				},
				"ewo" : {
					"name" : "Ewondo"
				},
				"fan" : {
					"name" : "Fang"
				},
				"fao" : {
					"name" : "Färöisch"
				},
				"fat" : {
					"name" : "Fanti"
				},
				"fij" : {
					"name" : "Fidschi"
				},
				"fil" : {
					"name" : "Filipinisch"
				},
				"fin" : {
					"name" : "Finnisch",
					"flag" : "fi"
				},
				"fiu" : {
					"name" : "Finno-ugrischen Sprachen"
				},
				"fon" : {
					"name" : "Fon"
				},
				"fre" : {
					"name" : "Französisch"
				},
				"fra" : {
					"name" : "Französisch",
					"flag" : "fr"
				},
				"frm" : {
					"name" : "Französisch, Mittel (ca.1400-1600)"
				},
				"fro" : {
					"name" : "Französisch, Alt (842-ca.1400)"
				},
				"frr" : {
					"name" : "Nordfriesisch"
				},
				"frs" : {
					"name" : "Ostfriesisch"
				},
				"fry" : {
					"name" : "Westfriesisch"
				},
				"ful" : {
					"name" : "Fulah"
				},
				"fur" : {
					"name" : "Friaulisch"
				},
				"gaa" : {
					"name" : "Ga"
				},
				"gay" : {
					"name" : "Gayo"
				},
				"gba" : {
					"name" : "Gbaya"
				},
				"gem" : {
					"name" : "Germanische Sprachen"
				},
				"geo" : {
					"name" : "Georgisch"
				},
				"kat" : {
					"name" : "Georgisch"
				},
				"ger" : {
					"name" : "Deutsch",
					"flag" : "de"
				},
				"deu" : {
					"name" : "Deutsch",
					"flag" : "de"
				},
				"gez" : {
					"name" : "Geez"
				},
				"gil" : {
					"name" : "Gilbertesisch"
				},
				"gla" : {
					"name" : "Gälisch, Schottisch-Gälisch"
				},
				"gle" : {
					"name" : "Irisch",
					"flag" : "ie"
				},
				"glg" : {
					"name" : "Galizisch"
				},
				"glv" : {
					"name" : "Manx"
				},
				"gmh" : {
					"name" : "Deutsch, Mittlehoch (ca.1050-1500)"
				},
				"goh" : {
					"name" : "Deutsch, Althoch (ca.750-1050)"
				},
				"gon" : {
					"name" : "Gondi"
				},
				"gor" : {
					"name" : "Gorontalo"
				},
				"got" : {
					"name" : "Gotisch"
				},
				"grb" : {
					"name" : "Grebo"
				},
				"grc" : {
					"name" : "Altgriechisch (bis 1453)"
				},
				"gre" : {
					"name" : "Griechisch, Modern (1453 -)"
				},
				"ell" : {
					"name" : "Griechisch, Modern (1453 -)",
					"flag" : "gr"
				},
				"grn" : {
					"name" : "Guarani"
				},
				"gsw" : {
					"name" : "Schweizerdeutsch, Alemannisch, Elsässisch"
				},
				"guj" : {
					"name" : "Gujarati"
				},
				"gwi" : {
					"name" : "Gwich'in"
				},
				"hai" : {
					"name" : "Haida"
				},
				"hat" : {
					"name" : "Haitianisch"
				},
				"hau" : {
					"name" : "Hausa"
				},
				"haw" : {
					"name" : "Hawaii"
				},
				"heb" : {
					"name" : "Hebräisch",
					"flag" : "il"
				},
				"her" : {
					"name" : "Herero"
				},
				"hil" : {
					"name" : "Hiligaynon"
				},
				"him" : {
					"name" : "Himachali Sprachen; Pahari westliche Sprachen"
				},
				"hin" : {
					"name" : "Hindi",
					"flag" : "in"
				},
				"hit" : {
					"name" : "Hethitisch"
				},
				"hmn" : {
					"name" : "Hmong, Mong"
				},
				"hmo" : {
					"name" : "Hiri Motu"
				},
				"hrv" : {
					"name" : "Kroatisch",
					"flag" : "hr"
				},
				"hsb" : {
					"name" : "Obersorbisch"
				},
				"hun" : {
					"name" : "Ungarisch",
					"flag" : "hu"
				},
				"hup" : {
					"name" : "Hupa"
				},
				"iba" : {
					"name" : "Iban"
				},
				"ibo" : {
					"name" : "Igbo"
				},
				"ice" : {
					"name" : "Isländisch"
				},
				"isl" : {
					"name" : "Isländisch",
					"flag" : "is"
				},
				"ido" : {
					"name" : "Ido"
				},
				"iii" : {
					"name" : "Sichuan Yi; Nuosu"
				},
				"ijo" : {
					"name" : "Ijo Sprachen"
				},
				"iku" : {
					"name" : "Inuktitut"
				},
				"ile" : {
					"name" : "Interlingue; Okzidental"
				},
				"ilo" : {
					"name" : "Iloko"
				},
				"ina" : {
					"name" : "Interlingua (International Auxiliary Language Association)"
				},
				"inc" : {
					"name" : "Indische Sprachen"
				},
				"ind" : {
					"name" : "Indonesisch",
					"flag" : "id"
				},
				"ine" : {
					"name" : "Indoeuropäische Sprachen"
				},
				"inh" : {
					"name" : "Inguschisch"
				},
				"ipk" : {
					"name" : "Inupiaq"
				},
				"ira" : {
					"name" : "Iranische Sprachen"
				},
				"iro" : {
					"name" : "Irokesen Sprachen"
				},
				"ita" : {
					"name" : "Italienisch",
					"flag" : "it"
				},
				"jav" : {
					"name" : "Javaner"
				},
				"jbo" : {
					"name" : "Lojban"
				},
				"jpn" : {
					"name" : "Japanisch",
					"flag" : "jp"
				},
				"jpr" : {
					"name" : "Jüdisch-persisch"
				},
				"jrb" : {
					"name" : "Jüdisch-arabisch"
				},
				"kaa" : {
					"name" : "Kara-Kalpak"
				},
				"kab" : {
					"name" : "Kabyle"
				},
				"kac" : {
					"name" : "Kachin; Jingpho"
				},
				"kal" : {
					"name" : "Kalaallisut; Grönländisch"
				},
				"kam" : {
					"name" : "Kamba"
				},
				"kan" : {
					"name" : "Kannada"
				},
				"kar" : {
					"name" : "Karen Sprachen"
				},
				"kas" : {
					"name" : "Kashmiri"
				},
				"kau" : {
					"name" : "Kanuri"
				},
				"kaw" : {
					"name" : "Kawi"
				},
				"kaz" : {
					"name" : "Kasachisch"
				},
				"kbd" : {
					"name" : "Kabardinisch"
				},
				"kha" : {
					"name" : "Khasi"
				},
				"khi" : {
					"name" : "Khoisan-Sprache"
				},
				"khm" : {
					"name" : "Mittel-Khmer"
				},
				"kho" : {
					"name" : "Khotanesisch; Sakan"
				},
				"kik" : {
					"name" : "Kikuyu; Gikuyu"
				},
				"kin" : {
					"name" : "Kinyarwanda"
				},
				"kir" : {
					"name" : "Kirgisisch"
				},
				"kmb" : {
					"name" : "Kimbundu"
				},
				"kok" : {
					"name" : "Konkani"
				},
				"kom" : {
					"name" : "Komi"
				},
				"kon" : {
					"name" : "Kongo"
				},
				"kor" : {
					"name" : "Koreanisch",
					"flag" : "kr"
				},
				"kos" : {
					"name" : "Kosraeanisch"
				},
				"kpe" : {
					"name" : "Kpelle"
				},
				"krc" : {
					"name" : "Karatschai-Balkar"
				},
				"krl" : {
					"name" : "Karelisch"
				},
				"kro" : {
					"name" : "Kru Sprachen"
				},
				"kru" : {
					"name" : "Kurukh"
				},
				"kua" : {
					"name" : "Kuanyama; Kwanyama"
				},
				"kum" : {
					"name" : "Kumykisch"
				},
				"kur" : {
					"name" : "Kurdisch"
				},
				"kut" : {
					"name" : "Kutenai"
				},
				"lad" : {
					"name" : "Ladino"
				},
				"lah" : {
					"name" : "Lahnda"
				},
				"lam" : {
					"name" : "Lamba"
				},
				"lao" : {
					"name" : "Lao"
				},
				"lat" : {
					"name" : "Latein"
				},
				"lav" : {
					"name" : "Lettisch",
					"flag" : "lv"
				},
				"lez" : {
					"name" : "Lesgisch"
				},
				"lim" : {
					"name" : "Limburgisch"
				},
				"lin" : {
					"name" : "Lingála"
				},
				"lit" : {
					"name" : "Litauisch",
					"flag" : "lt"
				},
				"lol" : {
					"name" : "Mongo"
				},
				"loz" : {
					"name" : "Lozi"
				},
				"ltz" : {
					"name" : "Luxemburgisch, Lëtzebuergesch"
				},
				"lua" : {
					"name" : "Luba-Lulua"
				},
				"lub" : {
					"name" : "Luba-Katanga"
				},
				"lug" : {
					"name" : "Ganda"
				},
				"lui" : {
					"name" : "Luiseno"
				},
				"lun" : {
					"name" : "Lunda"
				},
				"luo" : {
					"name" : "Luo (Kenia und Tansania)"
				},
				"lus" : {
					"name" : "Lushai"
				},
				"mac" : {
					"name" : "Mazedonisch"
				},
				"mkd" : {
					"name" : "Mazedonisch",
					"flag" : "mk"
				},
				"mad" : {
					"name" : "Maduresisch"
				},
				"mag" : {
					"name" : "Magahi"
				},
				"mah" : {
					"name" : "Marshallese"
				},
				"mai" : {
					"name" : "Maithili"
				},
				"mak" : {
					"name" : "Makasar"
				},
				"mal" : {
					"name" : "Malayalam"
				},
				"man" : {
					"name" : "Mandingo"
				},
				"mao" : {
					"name" : "Maori"
				},
				"mri" : {
					"name" : "Maori"
				},
				"map" : {
					"name" : "Austronesische Sprachen"
				},
				"mar" : {
					"name" : "Marathi"
				},
				"mas" : {
					"name" : "Masai"
				},
				"may" : {
					"name" : "Malaiisch"
				},
				"msa" : {
					"name" : "Malaiisch",
					"flag" : "my"
				},
				"mdf" : {
					"name" : "Moksha"
				},
				"mdr" : {
					"name" : "Mandar"
				},
				"men" : {
					"name" : "Mende"
				},
				"mga" : {
					"name" : "Irisch, Mittel (900-1200)"
				},
				"mic" : {
					"name" : "Mi'kmaq; Micmac"
				},
				"min" : {
					"name" : "Minangkabau"
				},
				"mis" : {
					"name" : "Unkodierte Sprachen"
				},
				"mkh" : {
					"name" : "Mon-Khmer-Sprachen"
				},
				"mlg" : {
					"name" : "Malagasy"
				},
				"mlt" : {
					"name" : "Maltesisch",
					"flag" : "mt"
				},
				"mnc" : {
					"name" : "Manchu"
				},
				"mni" : {
					"name" : "Manipuri"
				},
				"mno" : {
					"name" : "Manobo Sprachen"
				},
				"moh" : {
					"name" : "Mohawk"
				},
				"mon" : {
					"name" : "Mongolisch"
				},
				"mos" : {
					"name" : "Mossi"
				},
				"mul" : {
					"name" : "Mehrere Sprachen"
				},
				"mun" : {
					"name" : "Munda-Sprachen"
				},
				"mus" : {
					"name" : "Bach"
				},
				"mwl" : {
					"name" : "Mirandesisch"
				},
				"mwr" : {
					"name" : "Marwari"
				},
				"myn" : {
					"name" : "Maya-Sprachen"
				},
				"myv" : {
					"name" : "Erzya"
				},
				"nah" : {
					"name" : "Nahuatl Sprachen"
				},
				"nai" : {
					"name" : "Nordamerikanische Indianer Sprachen"
				},
				"nap" : {
					"name" : "Neapolitanisch"
				},
				"nau" : {
					"name" : "Nauru"
				},
				"nav" : {
					"name" : "Navajo; Navaho"
				},
				"nbl" : {
					"name" : "Ndebele, Süd, Süd-Ndebele"
				},
				"nde" : {
					"name" : "Ndebele, Nord, Nord-Ndebele"
				},
				"ndo" : {
					"name" : "Ndonga"
				},
				"nds" : {
					"name" : "Plattdeutsch; Niedersächsisch, Deutsch"
				},
				"nep" : {
					"name" : "Nepali"
				},
				"new" : {
					"name" : "Nepal Bhasa; Newari"
				},
				"nia" : {
					"name" : "Nias"
				},
				"nic" : {
					"name" : "Niger-Kordofanianische Sprachen"
				},
				"niu" : {
					"name" : "Niuean"
				},
				"nno" : {
					"name" : "Norwegisch Nynorsk"
				},
				"nob" : {
					"name" : "Bokmål, Norwegisch, Norwegisch Bokmål"
				},
				"nog" : {
					"name" : "Nogai"
				},
				"non" : {
					"name" : "Norwegisch, Alt"
				},
				"nor" : {
					"name" : "Norwegisch",
					"flag" : "no"
				},
				"nqo" : {
					"name" : "N'Ko"
				},
				"nso" : {
					"name" : "Pedi; Sepedi; Nord Sotho"
				},
				"nub" : {
					"name" : "Nubische Sprachen"
				},
				"nwc" : {
					"name" : "Klassische Newari; Alt Newari; Klassische Nepal Bhasa"
				},
				"nya" : {
					"name" : "Chichewa; Chewa; Nyanja"
				},
				"nym" : {
					"name" : "Nyamwezi"
				},
				"nyn" : {
					"name" : "Nyankole"
				},
				"nyo" : {
					"name" : "Nyoro"
				},
				"nzi" : {
					"name" : "Nzima"
				},
				"oci" : {
					"name" : "Occitan (post 1500); provenzalischen"
				},
				"oji" : {
					"name" : "Ojibwa"
				},
				"ori" : {
					"name" : "Oriya"
				},
				"orm" : {
					"name" : "Oromo"
				},
				"osa" : {
					"name" : "Osage"
				},
				"oss" : {
					"name" : "Ossetisch"
				},
				"ota" : {
					"name" : "Türkisch, osmanische (1500-1928)"
				},
				"oto" : {
					"name" : "Otomian Sprachen"
				},
				"paa" : {
					"name" : "Papua-Sprachen"
				},
				"pag" : {
					"name" : "Pangasinan"
				},
				"pal" : {
					"name" : "Pahlavi"
				},
				"pam" : {
					"name" : "Pampanga; Kapampangan"
				},
				"pan" : {
					"name" : "Panjabi, Punjabi"
				},
				"pap" : {
					"name" : "Papiamento"
				},
				"pau" : {
					"name" : "Palau"
				},
				"peo" : {
					"name" : "Persisch, Alt (ca.600-400 v. Chr.)"
				},
				"per" : {
					"name" : "Persisch"
				},
				"fas" : {
					"name" : "Persisch"
				},
				"phi" : {
					"name" : "Philippinische Sprachen"
				},
				"phn" : {
					"name" : "Phönizisch"
				},
				"pli" : {
					"name" : "Pali"
				},
				"pol" : {
					"name" : "Polnisch",
					"flag" : "pl"
				},
				"pon" : {
					"name" : "Pohnpeian"
				},
				"por" : {
					"name" : "Portugiesisch",
					"flag" : "pt"
				},
				"pra" : {
					"name" : "Prakrit Sprachen"
				},
				"pro" : {
					"name" : "Provençal, Alt (bis 1500)"
				},
				"pus" : {
					"name" : "Paschtu, Paschtu"
				},
				"qaa-qtz" : {
					"name" : "Reserviert für den lokalen Gebrauch"
				},
				"que" : {
					"name" : "Quechua"
				},
				"raj" : {
					"name" : "Rajasthani"
				},
				"rap" : {
					"name" : "Rapanui"
				},
				"rar" : {
					"name" : "Rarotongan; Cook Islands Maori"
				},
				"roa" : {
					"name" : "Romanische Sprachen"
				},
				"roh" : {
					"name" : "Rätoromanisch"
				},
				"rom" : {
					"name" : "Roma"
				},
				"rum" : {
					"name" : "Rumänisch; Moldawisch; Moldauisch"
				},
				"ron" : {
					"name" : "Rumänisch; Moldawisch; Moldauisch",
					"flag" : "ro"
				},
				"run" : {
					"name" : "Rundi"
				},
				"rup" : {
					"name" : "Aromunischen; Arumanian; Macedo-Rumänischen"
				},
				"rus" : {
					"name" : "Russisch",
					"flag" : "ru"
				},
				"sad" : {
					"name" : "Sandawe"
				},
				"sag" : {
					"name" : "Sango"
				},
				"sah" : {
					"name" : "Yakut"
				},
				"sai" : {
					"name" : "Südamerikanische Indianer (Andere)"
				},
				"sal" : {
					"name" : "Salishan Sprachen"
				},
				"sam" : {
					"name" : "Samariter Aramäisch"
				},
				"san" : {
					"name" : "Sanskrit"
				},
				"sas" : {
					"name" : "Sasak"
				},
				"sat" : {
					"name" : "Santali"
				},
				"scn" : {
					"name" : "Sizilianisch"
				},
				"sco" : {
					"name" : "Schottisch"
				},
				"sel" : {
					"name" : "Selkupisch"
				},
				"sem" : {
					"name" : "Semitische Sprachen"
				},
				"sga" : {
					"name" : "Irisch, Alt (bis 900)"
				},
				"sgn" : {
					"name" : "Registrierte Sprachen"
				},
				"shn" : {
					"name" : "Shan"
				},
				"sid" : {
					"name" : "Sidamo"
				},
				"sin" : {
					"name" : "Singhalesisch"
				},
				"sio" : {
					"name" : "Sioux-Sprachen"
				},
				"sit" : {
					"name" : "Sino-tibetische Sprachen"
				},
				"sla" : {
					"name" : "Slawische Sprachen"
				},
				"slo" : {
					"name" : "Slowakisch"
				},
				"slk" : {
					"name" : "Slowakisch",
					"flag" : "sk"
				},
				"slv" : {
					"name" : "Slowenisch",
					"flag" : "si"
				},
				"sma" : {
					"name" : "Süd Sami"
				},
				"sme" : {
					"name" : "Nord Sami"
				},
				"smi" : {
					"name" : "Sami Sprachen"
				},
				"smj" : {
					"name" : "Lulesamisch"
				},
				"smn" : {
					"name" : "Inari-Samen"
				},
				"smo" : {
					"name" : "Samoaner"
				},
				"sms" : {
					"name" : "Skolt Sami"
				},
				"sna" : {
					"name" : "Shona"
				},
				"snd" : {
					"name" : "Sindhi"
				},
				"snk" : {
					"name" : "Soninke"
				},
				"sog" : {
					"name" : "Sogdisch"
				},
				"som" : {
					"name" : "Somali"
				},
				"son" : {
					"name" : "Songhai Sprachen"
				},
				"sot" : {
					"name" : "Sotho, Südlich"
				},
				"spa" : {
					"name" : "Spanisch, Kastilisch",
					"flag" : "es"
				},
				"srd" : {
					"name" : "Sardisch"
				},
				"srn" : {
					"name" : "Sranan Tongo"
				},
				"srp" : {
					"name" : "Serbisch",
					"flag" : "rs"
				},
				"srr" : {
					"name" : "Serer"
				},
				"ssa" : {
					"name" : "Nilosaharanische Sprachen"
				},
				"ssw" : {
					"name" : "Swati"
				},
				"suk" : {
					"name" : "Sukuma"
				},
				"sun" : {
					"name" : "Sundanesisch"
				},
				"sus" : {
					"name" : "Susu"
				},
				"sux" : {
					"name" : "Sumerisch"
				},
				"swa" : {
					"name" : "Swahili"
				},
				"swe" : {
					"name" : "Schwedisch",
					"flag" : "se"
				},
				"syc" : {
					"name" : "Klassisch Syrisch"
				},
				"syr" : {
					"name" : "Syrisch"
				},
				"tah" : {
					"name" : "Tahitianisch"
				},
				"tai" : {
					"name" : "Tai Sprachen"
				},
				"tam" : {
					"name" : "Tamilisch"
				},
				"tat" : {
					"name" : "Tatar"
				},
				"tel" : {
					"name" : "Telugu"
				},
				"tem" : {
					"name" : "Timne"
				},
				"ter" : {
					"name" : "Tereno"
				},
				"tet" : {
					"name" : "Tetum"
				},
				"tgk" : {
					"name" : "Tadschikisch"
				},
				"tgl" : {
					"name" : "Tagalog"
				},
				"tha" : {
					"name" : "Thailändisch",
					"flag" : "th"
				},
				"tib" : {
					"name" : "Tibetisch"
				},
				"bod" : {
					"name" : "Tibetisch"
				},
				"tig" : {
					"name" : "Tigre"
				},
				"tir" : {
					"name" : "Tigrinya"
				},
				"tiv" : {
					"name" : "Tiv"
				},
				"tkl" : {
					"name" : "Tokelau"
				},
				"tlh" : {
					"name" : "Klingonisch; tlhIngan-Hol"
				},
				"tli" : {
					"name" : "Tlingit"
				},
				"tmh" : {
					"name" : "Tamashek"
				},
				"tog" : {
					"name" : "Tonga (Nyasa)"
				},
				"ton" : {
					"name" : "Tonga (Tonga-Inseln)"
				},
				"tpi" : {
					"name" : "Tok Pisin"
				},
				"tsi" : {
					"name" : "Tsimshian"
				},
				"tsn" : {
					"name" : "Tswana"
				},
				"tso" : {
					"name" : "Tsonga"
				},
				"tuk" : {
					"name" : "Turkmenisch"
				},
				"tum" : {
					"name" : "Tumbuka"
				},
				"tup" : {
					"name" : "Tupi-Sprachen"
				},
				"tur" : {
					"name" : "Türkisch",
					"flag" : "tr"
				},
				"tut" : {
					"name" : "Altaische Sprachen"
				},
				"tvl" : {
					"name" : "Tuvalu"
				},
				"twi" : {
					"name" : "Twi"
				},
				"tyv" : {
					"name" : "Tuvinisch"
				},
				"udm" : {
					"name" : "Udmurtisch"
				},
				"uga" : {
					"name" : "Ugaritisch"
				},
				"uig" : {
					"name" : "Uigurisch"
				},
				"ukr" : {
					"name" : "Ukrainisch",
					"flag" : "ua"
				},
				"umb" : {
					"name" : "Umbundu"
				},
				"und" : {
					"name" : "Unbestimmt"
				},
				"urd" : {
					"name" : "Urdu"
				},
				"uzb" : {
					"name" : "Usbekisch"
				},
				"vai" : {
					"name" : "Vai"
				},
				"ven" : {
					"name" : "Venda"
				},
				"vie" : {
					"name" : "Vietnamesisch",
					"flag" : "vn"
				},
				"vol" : {
					"name" : "Volapük"
				},
				"vot" : {
					"name" : "Wotisch"
				},
				"wak" : {
					"name" : "Wakashan Sprachen"
				},
				"wal" : {
					"name" : "Walamo"
				},
				"war" : {
					"name" : "Waray"
				},
				"was" : {
					"name" : "Washo"
				},
				"wel" : {
					"name" : "Walisisch"
				},
				"cym" : {
					"name" : "Walisisch"
				},
				"wen" : {
					"name" : "Sorbische Sprachen"
				},
				"wln" : {
					"name" : "Wallonisch"
				},
				"wol" : {
					"name" : "Wolof"
				},
				"xal" : {
					"name" : "Kalmücken; Oirat"
				},
				"xho" : {
					"name" : "Xhosa"
				},
				"yao" : {
					"name" : "Yao"
				},
				"yap" : {
					"name" : "Yap"
				},
				"yid" : {
					"name" : "Jiddisch"
				},
				"yor" : {
					"name" : "Yoruba"
				},
				"ypk" : {
					"name" : "Yupik Sprachen"
				},
				"zap" : {
					"name" : "Zapoteken"
				},
				"zbl" : {
					"name" : "Bliss-Symbole; Blissymbolics; Bliss"
				},
				"zen" : {
					"name" : "Zenaga"
				},
				"zha" : {
					"name" : "Zhuang; Chuang"
				},
				"znd" : {
					"name" : "Zande Sprachen"
				},
				"zul" : {
					"name" : "Zulu"
				},
				"zun" : {
					"name" : "Zuni"
				},
				"zxx" : {
					"name" : "Kein sprachlicher Inhalt; Entfällt"
				},
				"zza" : {
					"name" : "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki"
				}
			};
			
			return iso;
		});

define('link/../../../shared/languages/./iso639-1-en',
		[],
		function() {

			var iso = {
				"aa" : {
					"name" : "Afar"
				},
				"ab" : {
					"name" : "Abkhazian"
				},
				"af" : {
					"name" : "Afrikaans"
				},
				"ak" : {
					"name" : "Akan"
				},
				"sq" : {
					"name" : "Albanian"
				},
				"am" : {
					"name" : "Amharic"
				},
				"ar" : {
					"name" : "Arabic",
					"flag" : "ae"
				},
				"an" : {
					"name" : "Aragonese"
				},
				"hy" : {
					"name" : "Armenian"
				},
				"as" : {
					"name" : "Assamese"
				},
				"av" : {
					"name" : "Avaric"
				},
				"ae" : {
					"name" : "Avestan"
				},
				"ay" : {
					"name" : "Aymara"
				},
				"az" : {
					"name" : "Azerbaijani"
				},
				"ba" : {
					"name" : "Bashkir"
				},
				"bm" : {
					"name" : "Bambara"
				},
				"eu" : {
					"name" : "Basque"
				},
				"be" : {
					"name" : "Belarusian",
					"flag" : "by"
				},
				"bn" : {
					"name" : "Bengali"
				},
				"bh" : {
					"name" : "Bihari languages"
				},
				"bi" : {
					"name" : "Bislama"
				},
				"bs" : {
					"name" : "Bosnian"
				},
				"br" : {
					"name" : "Breton"
				},
				"bg" : {
					"name" : "Bulgarian",
					"flag" : "bg"
				},
				"my" : {
					"name" : "Burmese"
				},
				"ca" : {
					"name" : "Catalan; Valencian",
					"flag" : "es"
				},
				"ch" : {
					"name" : "Chamorro"
				},
				"ce" : {
					"name" : "Chechen"
				},
				"zh" : {
					"name" : "Chinese"
				},
				"cu" : {
					"name" : "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"
				},
				"cv" : {
					"name" : "Chuvash"
				},
				"kw" : {
					"name" : "Cornish"
				},
				"co" : {
					"name" : "Corsican"
				},
				"cr" : {
					"name" : "Cree"
				},
				"cs" : {
					"name" : "Czech"
				},
				"da" : {
					"name" : "Danish",
					"flag" : "dk"
				},
				"dv" : {
					"name" : "Divehi; Dhivehi; Maldivian"
				},
				"nl" : {
					"name" : "Dutch; Flemish"
				},
				"dz" : {
					"name" : "Dzongkha"
				},
				"en" : {
					"name" : "English",
					"flag" : "gb"
				},
				"eo" : {
					"name" : "Esperanto"
				},
				"et" : {
					"name" : "Estonian",
					"flag" : "ee"
				},
				"ee" : {
					"name" : "Ewe"
				},
				"fo" : {
					"name" : "Faroese"
				},
				"fj" : {
					"name" : "Fijian"
				},
				"fi" : {
					"name" : "Finnish",
					"flag" : "fi"
				},
				"fr" : {
					"name" : "French"
				},
				"fy" : {
					"name" : "Western Frisian"
				},
				"ff" : {
					"name" : "Fulah"
				},
				"ka" : {
					"name" : "Georgian"
				},
				"de" : {
					"name" : "German",
					"flag" : "de"
				},
				"gd" : {
					"name" : "Gaelic; Scottish Gaelic"
				},
				"ga" : {
					"name" : "Irish",
					"flag" : "ie"
				},
				"gl" : {
					"name" : "Galician"
				},
				"gv" : {
					"name" : "Manx"
				},
				"el" : {
					"name" : "Greek, Modern (1453-)"
				},
				"gn" : {
					"name" : "Guarani"
				},
				"gu" : {
					"name" : "Gujarati"
				},
				"ht" : {
					"name" : "Haitian; Haitian Creole"
				},
				"ha" : {
					"name" : "Hausa"
				},
				"he" : {
					"name" : "Hebrew",
					"flag" : "il"
				},
				"hz" : {
					"name" : "Herero"
				},
				"hi" : {
					"name" : "Hindi",
					"flag" : "in"
				},
				"ho" : {
					"name" : "Hiri Motu"
				},
				"hr" : {
					"name" : "Croatian",
					"flag" : "hr"
				},
				"hu" : {
					"name" : "Hungarian",
					"flag" : "hu"
				},
				"ig" : {
					"name" : "Igbo"
				},
				"is" : {
					"name" : "Icelandic"
				},
				"io" : {
					"name" : "Ido"
				},
				"ii" : {
					"name" : "Sichuan Yi; Nuosu"
				},
				"iu" : {
					"name" : "Inuktitut"
				},
				"ie" : {
					"name" : "Interlingue; Occidental"
				},
				"ia" : {
					"name" : "Interlingua (International Auxiliary Language Association)"
				},
				"id" : {
					"name" : "Indonesian",
					"flag" : "id"
				},
				"ik" : {
					"name" : "Inupiaq"
				},
				"it" : {
					"name" : "Italian",
					"flag" : "it"
				},
				"jv" : {
					"name" : "Javanese"
				},
				"ja" : {
					"name" : "Japanese",
					"flag" : "jp"
				},
				"kl" : {
					"name" : "Kalaallisut; Greenlandic"
				},
				"kn" : {
					"name" : "Kannada"
				},
				"ks" : {
					"name" : "Kashmiri"
				},
				"kr" : {
					"name" : "Kanuri"
				},
				"kk" : {
					"name" : "Kazakh"
				},
				"km" : {
					"name" : "Central Khmer"
				},
				"ki" : {
					"name" : "Kikuyu; Gikuyu"
				},
				"rw" : {
					"name" : "Kinyarwanda"
				},
				"ky" : {
					"name" : "Kirghiz; Kyrgyz"
				},
				"kv" : {
					"name" : "Komi"
				},
				"kg" : {
					"name" : "Kongo"
				},
				"ko" : {
					"name" : "Korean",
					"flag" : "kr"
				},
				"kj" : {
					"name" : "Kuanyama; Kwanyama"
				},
				"ku" : {
					"name" : "Kurdish"
				},
				"lo" : {
					"name" : "Lao"
				},
				"la" : {
					"name" : "Latin"
				},
				"lv" : {
					"name" : "Latvian",
					"flag" : "lv"
				},
				"li" : {
					"name" : "Limburgan; Limburger; Limburgish"
				},
				"ln" : {
					"name" : "Lingala"
				},
				"lt" : {
					"name" : "Lithuanian",
					"flag" : "lt"
				},
				"lb" : {
					"name" : "Luxembourgish; Letzeburgesch"
				},
				"lu" : {
					"name" : "Luba-Katanga"
				},
				"lg" : {
					"name" : "Ganda"
				},
				"mk" : {
					"name" : "Macedonian"
				},
				"mh" : {
					"name" : "Marshallese"
				},
				"ml" : {
					"name" : "Malayalam"
				},
				"mi" : {
					"name" : "Maori"
				},
				"mr" : {
					"name" : "Marathi"
				},
				"ms" : {
					"name" : "Malay"
				},
				"mg" : {
					"name" : "Malagasy"
				},
				"mt" : {
					"name" : "Maltese",
					"flag" : "mt"
				},
				"mn" : {
					"name" : "Mongolian"
				},
				"na" : {
					"name" : "Nauru"
				},
				"nv" : {
					"name" : "Navajo; Navaho"
				},
				"nr" : {
					"name" : "Ndebele, South; South Ndebele"
				},
				"nd" : {
					"name" : "Ndebele, North; North Ndebele"
				},
				"ng" : {
					"name" : "Ndonga"
				},
				"ne" : {
					"name" : "Nepali"
				},
				"nn" : {
					"name" : "Norwegian Nynorsk; Nynorsk, Norwegian"
				},
				"nb" : {
					"name" : "Bokmål, Norwegian; Norwegian Bokmål"
				},
				"no" : {
					"name" : "Norwegian",
					"flag" : "no"
				},
				"ny" : {
					"name" : "Chichewa; Chewa; Nyanja"
				},
				"oc" : {
					"name" : "Occitan (post 1500); Provençal"
				},
				"oj" : {
					"name" : "Ojibwa"
				},
				"or" : {
					"name" : "Oriya"
				},
				"om" : {
					"name" : "Oromo"
				},
				"os" : {
					"name" : "Ossetian; Ossetic"
				},
				"pa" : {
					"name" : "Panjabi; Punjabi"
				},
				"fa" : {
					"name" : "Persian"
				},
				"pi" : {
					"name" : "Pali"
				},
				"pl" : {
					"name" : "Polish",
					"flag" : "pl"
				},
				"pt" : {
					"name" : "Portuguese",
					"flag" : "pt"
				},
				"ps" : {
					"name" : "Pushto; Pashto"
				},
				"qu" : {
					"name" : "Quechua"
				},
				"rm" : {
					"name" : "Romansh"
				},
				"ro" : {
					"name" : "Romanian; Moldavian; Moldovan"
				},
				"rn" : {
					"name" : "Rundi"
				},
				"ru" : {
					"name" : "Russian",
					"flag" : "ru"
				},
				"sg" : {
					"name" : "Sango"
				},
				"sa" : {
					"name" : "Sanskrit"
				},
				"si" : {
					"name" : "Sinhala; Sinhalese"
				},
				"sk" : {
					"name" : "Slovak"
				},
				"sl" : {
					"name" : "Slovenian",
					"flag" : "si"
				},
				"se" : {
					"name" : "Northern Sami"
				},
				"sm" : {
					"name" : "Samoan"
				},
				"sn" : {
					"name" : "Shona"
				},
				"sd" : {
					"name" : "Sindhi"
				},
				"so" : {
					"name" : "Somali"
				},
				"st" : {
					"name" : "Sotho, Southern"
				},
				"es" : {
					"name" : "Spanish; Castilian",
					"flag" : "es"
				},
				"sc" : {
					"name" : "Sardinian"
				},
				"sr" : {
					"name" : "Serbian",
					"flag" : "rs"
				},
				"ss" : {
					"name" : "Swati"
				},
				"su" : {
					"name" : "Sundanese"
				},
				"sw" : {
					"name" : "Swahili"
				},
				"sv" : {
					"name" : "Swedish",
					"flag" : "se"
				},
				"ty" : {
					"name" : "Tahitian"
				},
				"ta" : {
					"name" : "Tamil"
				},
				"tt" : {
					"name" : "Tatar"
				},
				"te" : {
					"name" : "Telugu"
				},
				"tg" : {
					"name" : "Tajik"
				},
				"tl" : {
					"name" : "Tagalog"
				},
				"th" : {
					"name" : "Thai",
					"flag" : "th"
				},
				"bo" : {
					"name" : "Tibetan"
				},
				"ti" : {
					"name" : "Tigrinya"
				},
				"to" : {
					"name" : "Tonga (Tonga Islands)"
				},
				"tn" : {
					"name" : "Tswana"
				},
				"ts" : {
					"name" : "Tsonga"
				},
				"tk" : {
					"name" : "Turkmen"
				},
				"tr" : {
					"name" : "Turkish",
					"flag" : "tr"
				},
				"tw" : {
					"name" : "Twi"
				},
				"ug" : {
					"name" : "Uighur; Uyghur"
				},
				"uk" : {
					"name" : "Ukrainian",
					"flag" : "ua"
				},
				"ur" : {
					"name" : "Urdu"
				},
				"uz" : {
					"name" : "Uzbek"
				},
				"ve" : {
					"name" : "Venda"
				},
				"vi" : {
					"name" : "Vietnamese",
					"flag" : "vn"
				},
				"vo" : {
					"name" : "Volapük"
				},
				"cy" : {
					"name" : "Welsh"
				},
				"wa" : {
					"name" : "Walloon"
				},
				"wo" : {
					"name" : "Wolof"
				},
				"xh" : {
					"name" : "Xhosa"
				},
				"yi" : {
					"name" : "Yiddish"
				},
				"yo" : {
					"name" : "Yoruba"
				},
				"za" : {
					"name" : "Zhuang; Chuang"
				},
				"zu" : {
					"name" : "Zulu"
				}
			};
			
			return iso;
		});

define('link/../../../shared/languages/./iso639-2-en',
		[],
		function() {

			var iso = {
				"aar" : {
					"name" : "Afar"
				},
				"abk" : {
					"name" : "Abkhazian"
				},
				"ace" : {
					"name" : "Achinese"
				},
				"ach" : {
					"name" : "Acoli"
				},
				"ada" : {
					"name" : "Adangme"
				},
				"ady" : {
					"name" : "Adyghe; Adygei"
				},
				"afa" : {
					"name" : "Afro-Asiatic languages"
				},
				"afh" : {
					"name" : "Afrihili"
				},
				"afr" : {
					"name" : "Afrikaans"
				},
				"ain" : {
					"name" : "Ainu"
				},
				"aka" : {
					"name" : "Akan"
				},
				"akk" : {
					"name" : "Akkadian"
				},
				"alb" : {
					"name" : "Albanian"
				},
				"sqi" : {
					"name" : "Albanian",
					"flag" : "al"
				},
				"ale" : {
					"name" : "Aleut"
				},
				"alg" : {
					"name" : "Algonquian languages"
				},
				"alt" : {
					"name" : "Southern Altai"
				},
				"amh" : {
					"name" : "Amharic"
				},
				"ang" : {
					"name" : "English, Old (ca.450-1100)"
				},
				"anp" : {
					"name" : "Angika"
				},
				"apa" : {
					"name" : "Apache languages"
				},
				"ara" : {
					"name" : "Arabic",
					"flag" : "ae"
				},
				"arc" : {
					"name" : "Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)"
				},
				"arg" : {
					"name" : "Aragonese"
				},
				"arm" : {
					"name" : "Armenian"
				},
				"hye" : {
					"name" : "Armenian"
				},
				"arn" : {
					"name" : "Mapudungun; Mapuche"
				},
				"arp" : {
					"name" : "Arapaho"
				},
				"art" : {
					"name" : "Artificial languages"
				},
				"arw" : {
					"name" : "Arawak"
				},
				"asm" : {
					"name" : "Assamese"
				},
				"ast" : {
					"name" : "Asturian; Bable; Leonese; Asturleonese"
				},
				"ath" : {
					"name" : "Athapascan languages"
				},
				"aus" : {
					"name" : "Australian languages"
				},
				"ava" : {
					"name" : "Avaric"
				},
				"ave" : {
					"name" : "Avestan"
				},
				"awa" : {
					"name" : "Awadhi"
				},
				"aym" : {
					"name" : "Aymara"
				},
				"aze" : {
					"name" : "Azerbaijani"
				},
				"bad" : {
					"name" : "Banda languages"
				},
				"bai" : {
					"name" : "Bamileke languages"
				},
				"bak" : {
					"name" : "Bashkir"
				},
				"bal" : {
					"name" : "Baluchi"
				},
				"bam" : {
					"name" : "Bambara"
				},
				"ban" : {
					"name" : "Balinese"
				},
				"baq" : {
					"name" : "Basque"
				},
				"eus" : {
					"name" : "Basque"
				},
				"bas" : {
					"name" : "Basa"
				},
				"bat" : {
					"name" : "Baltic languages"
				},
				"bej" : {
					"name" : "Beja; Bedawiyet"
				},
				"bel" : {
					"name" : "Belarusian",
					"flag" : "by"
				},
				"bem" : {
					"name" : "Bemba"
				},
				"ben" : {
					"name" : "Bengali"
				},
				"ber" : {
					"name" : "Berber languages"
				},
				"bho" : {
					"name" : "Bhojpuri"
				},
				"bih" : {
					"name" : "Bihari languages"
				},
				"bik" : {
					"name" : "Bikol"
				},
				"bin" : {
					"name" : "Bini; Edo"
				},
				"bis" : {
					"name" : "Bislama"
				},
				"bla" : {
					"name" : "Siksika"
				},
				"bnt" : {
					"name" : "Bantu (Other)"
				},
				"bos" : {
					"name" : "Bosnian"
				},
				"bra" : {
					"name" : "Braj"
				},
				"bre" : {
					"name" : "Breton"
				},
				"btk" : {
					"name" : "Batak languages"
				},
				"bua" : {
					"name" : "Buriat"
				},
				"bug" : {
					"name" : "Buginese"
				},
				"bul" : {
					"name" : "Bulgarian",
					"flag" : "bg"
				},
				"bur" : {
					"name" : "Burmese"
				},
				"mya" : {
					"name" : "Burmese"
				},
				"byn" : {
					"name" : "Blin; Bilin"
				},
				"cad" : {
					"name" : "Caddo"
				},
				"cai" : {
					"name" : "Central American Indian languages"
				},
				"car" : {
					"name" : "Galibi Carib"
				},
				"cat" : {
					"name" : "Catalan; Valencian",
					"flag" : "es"
				},
				"cau" : {
					"name" : "Caucasian languages"
				},
				"ceb" : {
					"name" : "Cebuano"
				},
				"cel" : {
					"name" : "Celtic languages"
				},
				"cha" : {
					"name" : "Chamorro"
				},
				"chb" : {
					"name" : "Chibcha"
				},
				"che" : {
					"name" : "Chechen"
				},
				"chg" : {
					"name" : "Chagatai"
				},
				"chi" : {
					"name" : "Chinese"
				},
				"zho" : {
					"name" : "Chinese",
					"flag" : "cn"
				},
				"chk" : {
					"name" : "Chuukese"
				},
				"chm" : {
					"name" : "Mari"
				},
				"chn" : {
					"name" : "Chinook jargon"
				},
				"cho" : {
					"name" : "Choctaw"
				},
				"chp" : {
					"name" : "Chipewyan; Dene Suline"
				},
				"chr" : {
					"name" : "Cherokee"
				},
				"chu" : {
					"name" : "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"
				},
				"chv" : {
					"name" : "Chuvash"
				},
				"chy" : {
					"name" : "Cheyenne"
				},
				"cmc" : {
					"name" : "Chamic languages"
				},
				"cop" : {
					"name" : "Coptic"
				},
				"cor" : {
					"name" : "Cornish"
				},
				"cos" : {
					"name" : "Corsican"
				},
				"cpe" : {
					"name" : "Creoles and pidgins, English based"
				},
				"cpf" : {
					"name" : "Creoles and pidgins, French-based "
				},
				"cpp" : {
					"name" : "Creoles and pidgins, Portuguese-based "
				},
				"cre" : {
					"name" : "Cree"
				},
				"crh" : {
					"name" : "Crimean Tatar; Crimean Turkish"
				},
				"crp" : {
					"name" : "Creoles and pidgins "
				},
				"csb" : {
					"name" : "Kashubian"
				},
				"cus" : {
					"name" : "Cushitic languages"
				},
				"cze" : {
					"name" : "Czech"
				},
				"ces" : {
					"name" : "Czech",
					"flag" : "cz"
				},
				"dak" : {
					"name" : "Dakota"
				},
				"dan" : {
					"name" : "Danish",
					"flag" : "dk"
				},
				"dar" : {
					"name" : "Dargwa"
				},
				"day" : {
					"name" : "Land Dayak languages"
				},
				"del" : {
					"name" : "Delaware"
				},
				"den" : {
					"name" : "Slave (Athapascan)"
				},
				"dgr" : {
					"name" : "Dogrib"
				},
				"din" : {
					"name" : "Dinka"
				},
				"div" : {
					"name" : "Divehi; Dhivehi; Maldivian"
				},
				"doi" : {
					"name" : "Dogri"
				},
				"dra" : {
					"name" : "Dravidian languages"
				},
				"dsb" : {
					"name" : "Lower Sorbian"
				},
				"dua" : {
					"name" : "Duala"
				},
				"dum" : {
					"name" : "Dutch, Middle (ca.1050-1350)"
				},
				"dut" : {
					"name" : "Dutch; Flemish"
				},
				"nld" : {
					"name" : "Dutch; Flemish",
					"flag" : "nl"
				},
				"dyu" : {
					"name" : "Dyula"
				},
				"dzo" : {
					"name" : "Dzongkha"
				},
				"efi" : {
					"name" : "Efik"
				},
				"egy" : {
					"name" : "Egyptian (Ancient)"
				},
				"eka" : {
					"name" : "Ekajuk"
				},
				"elx" : {
					"name" : "Elamite"
				},
				"eng" : {
					"name" : "English",
					"flag" : "gb"
				},
				"enm" : {
					"name" : "English, Middle (1100-1500)"
				},
				"epo" : {
					"name" : "Esperanto"
				},
				"est" : {
					"name" : "Estonian",
					"flag" : "ee"
				},
				"ewe" : {
					"name" : "Ewe"
				},
				"ewo" : {
					"name" : "Ewondo"
				},
				"fan" : {
					"name" : "Fang"
				},
				"fao" : {
					"name" : "Faroese"
				},
				"fat" : {
					"name" : "Fanti"
				},
				"fij" : {
					"name" : "Fijian"
				},
				"fil" : {
					"name" : "Filipino; Pilipino"
				},
				"fin" : {
					"name" : "Finnish",
					"flag" : "fi"
				},
				"fiu" : {
					"name" : "Finno-Ugrian languages"
				},
				"fon" : {
					"name" : "Fon"
				},
				"fre" : {
					"name" : "French"
				},
				"fra" : {
					"name" : "French",
					"flag" : "fr"
				},
				"frm" : {
					"name" : "French, Middle (ca.1400-1600)"
				},
				"fro" : {
					"name" : "French, Old (842-ca.1400)"
				},
				"frr" : {
					"name" : "Northern Frisian"
				},
				"frs" : {
					"name" : "Eastern Frisian"
				},
				"fry" : {
					"name" : "Western Frisian"
				},
				"ful" : {
					"name" : "Fulah"
				},
				"fur" : {
					"name" : "Friulian"
				},
				"gaa" : {
					"name" : "Ga"
				},
				"gay" : {
					"name" : "Gayo"
				},
				"gba" : {
					"name" : "Gbaya"
				},
				"gem" : {
					"name" : "Germanic languages"
				},
				"geo" : {
					"name" : "Georgian"
				},
				"kat" : {
					"name" : "Georgian"
				},
				"ger" : {
					"name" : "German",
					"flag" : "de"
				},
				"deu" : {
					"name" : "German",
					"flag" : "de"
				},
				"gez" : {
					"name" : "Geez"
				},
				"gil" : {
					"name" : "Gilbertese"
				},
				"gla" : {
					"name" : "Gaelic; Scottish Gaelic"
				},
				"gle" : {
					"name" : "Irish",
					"flag" : "ie"
				},
				"glg" : {
					"name" : "Galician"
				},
				"glv" : {
					"name" : "Manx"
				},
				"gmh" : {
					"name" : "German, Middle High (ca.1050-1500)"
				},
				"goh" : {
					"name" : "German, Old High (ca.750-1050)"
				},
				"gon" : {
					"name" : "Gondi"
				},
				"gor" : {
					"name" : "Gorontalo"
				},
				"got" : {
					"name" : "Gothic"
				},
				"grb" : {
					"name" : "Grebo"
				},
				"grc" : {
					"name" : "Greek, Ancient (to 1453)"
				},
				"gre" : {
					"name" : "Greek, Modern (1453-)"
				},
				"ell" : {
					"name" : "Greek, Modern (1453-)",
					"flag" : "gr"
				},
				"grn" : {
					"name" : "Guarani"
				},
				"gsw" : {
					"name" : "Swiss German; Alemannic; Alsatian"
				},
				"guj" : {
					"name" : "Gujarati"
				},
				"gwi" : {
					"name" : "Gwich'in"
				},
				"hai" : {
					"name" : "Haida"
				},
				"hat" : {
					"name" : "Haitian; Haitian Creole"
				},
				"hau" : {
					"name" : "Hausa"
				},
				"haw" : {
					"name" : "Hawaiian"
				},
				"heb" : {
					"name" : "Hebrew",
					"flag" : "il"
				},
				"her" : {
					"name" : "Herero"
				},
				"hil" : {
					"name" : "Hiligaynon"
				},
				"him" : {
					"name" : "Himachali languages; Western Pahari languages"
				},
				"hin" : {
					"name" : "Hindi",
					"flag" : "in"
				},
				"hit" : {
					"name" : "Hittite"
				},
				"hmn" : {
					"name" : "Hmong; Mong"
				},
				"hmo" : {
					"name" : "Hiri Motu"
				},
				"hrv" : {
					"name" : "Croatian",
					"flag" : "hr"
				},
				"hsb" : {
					"name" : "Upper Sorbian"
				},
				"hun" : {
					"name" : "Hungarian",
					"flag" : "hu"
				},
				"hup" : {
					"name" : "Hupa"
				},
				"iba" : {
					"name" : "Iban"
				},
				"ibo" : {
					"name" : "Igbo"
				},
				"ice" : {
					"name" : "Icelandic"
				},
				"isl" : {
					"name" : "Icelandic",
					"flag" : "is"
				},
				"ido" : {
					"name" : "Ido"
				},
				"iii" : {
					"name" : "Sichuan Yi; Nuosu"
				},
				"ijo" : {
					"name" : "Ijo languages"
				},
				"iku" : {
					"name" : "Inuktitut"
				},
				"ile" : {
					"name" : "Interlingue; Occidental"
				},
				"ilo" : {
					"name" : "Iloko"
				},
				"ina" : {
					"name" : "Interlingua (International Auxiliary Language Association)"
				},
				"inc" : {
					"name" : "Indic languages"
				},
				"ind" : {
					"name" : "Indonesian",
					"flag" : "id"
				},
				"ine" : {
					"name" : "Indo-European languages"
				},
				"inh" : {
					"name" : "Ingush"
				},
				"ipk" : {
					"name" : "Inupiaq"
				},
				"ira" : {
					"name" : "Iranian languages"
				},
				"iro" : {
					"name" : "Iroquoian languages"
				},
				"ita" : {
					"name" : "Italian",
					"flag" : "it"
				},
				"jav" : {
					"name" : "Javanese"
				},
				"jbo" : {
					"name" : "Lojban"
				},
				"jpn" : {
					"name" : "Japanese",
					"flag" : "jp"
				},
				"jpr" : {
					"name" : "Judeo-Persian"
				},
				"jrb" : {
					"name" : "Judeo-Arabic"
				},
				"kaa" : {
					"name" : "Kara-Kalpak"
				},
				"kab" : {
					"name" : "Kabyle"
				},
				"kac" : {
					"name" : "Kachin; Jingpho"
				},
				"kal" : {
					"name" : "Kalaallisut; Greenlandic"
				},
				"kam" : {
					"name" : "Kamba"
				},
				"kan" : {
					"name" : "Kannada"
				},
				"kar" : {
					"name" : "Karen languages"
				},
				"kas" : {
					"name" : "Kashmiri"
				},
				"kau" : {
					"name" : "Kanuri"
				},
				"kaw" : {
					"name" : "Kawi"
				},
				"kaz" : {
					"name" : "Kazakh"
				},
				"kbd" : {
					"name" : "Kabardian"
				},
				"kha" : {
					"name" : "Khasi"
				},
				"khi" : {
					"name" : "Khoisan languages"
				},
				"khm" : {
					"name" : "Central Khmer"
				},
				"kho" : {
					"name" : "Khotanese; Sakan"
				},
				"kik" : {
					"name" : "Kikuyu; Gikuyu"
				},
				"kin" : {
					"name" : "Kinyarwanda"
				},
				"kir" : {
					"name" : "Kirghiz; Kyrgyz"
				},
				"kmb" : {
					"name" : "Kimbundu"
				},
				"kok" : {
					"name" : "Konkani"
				},
				"kom" : {
					"name" : "Komi"
				},
				"kon" : {
					"name" : "Kongo"
				},
				"kor" : {
					"name" : "Korean",
					"flag" : "kr"
				},
				"kos" : {
					"name" : "Kosraean"
				},
				"kpe" : {
					"name" : "Kpelle"
				},
				"krc" : {
					"name" : "Karachay-Balkar"
				},
				"krl" : {
					"name" : "Karelian"
				},
				"kro" : {
					"name" : "Kru languages"
				},
				"kru" : {
					"name" : "Kurukh"
				},
				"kua" : {
					"name" : "Kuanyama; Kwanyama"
				},
				"kum" : {
					"name" : "Kumyk"
				},
				"kur" : {
					"name" : "Kurdish"
				},
				"kut" : {
					"name" : "Kutenai"
				},
				"lad" : {
					"name" : "Ladino"
				},
				"lah" : {
					"name" : "Lahnda"
				},
				"lam" : {
					"name" : "Lamba"
				},
				"lao" : {
					"name" : "Lao"
				},
				"lat" : {
					"name" : "Latin"
				},
				"lav" : {
					"name" : "Latvian",
					"flag" : "lv"
				},
				"lez" : {
					"name" : "Lezghian"
				},
				"lim" : {
					"name" : "Limburgan; Limburger; Limburgish"
				},
				"lin" : {
					"name" : "Lingala"
				},
				"lit" : {
					"name" : "Lithuanian",
					"flag" : "lt"
				},
				"lol" : {
					"name" : "Mongo"
				},
				"loz" : {
					"name" : "Lozi"
				},
				"ltz" : {
					"name" : "Luxembourgish; Letzeburgesch"
				},
				"lua" : {
					"name" : "Luba-Lulua"
				},
				"lub" : {
					"name" : "Luba-Katanga"
				},
				"lug" : {
					"name" : "Ganda"
				},
				"lui" : {
					"name" : "Luiseno"
				},
				"lun" : {
					"name" : "Lunda"
				},
				"luo" : {
					"name" : "Luo (Kenya and Tanzania)"
				},
				"lus" : {
					"name" : "Lushai"
				},
				"mac" : {
					"name" : "Macedonian"
				},
				"mkd" : {
					"name" : "Macedonian",
					"flag" : "mk"
				},
				"mad" : {
					"name" : "Madurese"
				},
				"mag" : {
					"name" : "Magahi"
				},
				"mah" : {
					"name" : "Marshallese"
				},
				"mai" : {
					"name" : "Maithili"
				},
				"mak" : {
					"name" : "Makasar"
				},
				"mal" : {
					"name" : "Malayalam"
				},
				"man" : {
					"name" : "Mandingo"
				},
				"mao" : {
					"name" : "Maori"
				},
				"mri" : {
					"name" : "Maori"
				},
				"map" : {
					"name" : "Austronesian languages"
				},
				"mar" : {
					"name" : "Marathi"
				},
				"mas" : {
					"name" : "Masai"
				},
				"may" : {
					"name" : "Malay"
				},
				"msa" : {
					"name" : "Malay",
					"flag" : "my"
				},
				"mdf" : {
					"name" : "Moksha"
				},
				"mdr" : {
					"name" : "Mandar"
				},
				"men" : {
					"name" : "Mende"
				},
				"mga" : {
					"name" : "Irish, Middle (900-1200)"
				},
				"mic" : {
					"name" : "Mi'kmaq; Micmac"
				},
				"min" : {
					"name" : "Minangkabau"
				},
				"mis" : {
					"name" : "Uncoded languages"
				},
				"mkh" : {
					"name" : "Mon-Khmer languages"
				},
				"mlg" : {
					"name" : "Malagasy"
				},
				"mlt" : {
					"name" : "Maltese",
					"flag" : "mt"
				},
				"mnc" : {
					"name" : "Manchu"
				},
				"mni" : {
					"name" : "Manipuri"
				},
				"mno" : {
					"name" : "Manobo languages"
				},
				"moh" : {
					"name" : "Mohawk"
				},
				"mon" : {
					"name" : "Mongolian"
				},
				"mos" : {
					"name" : "Mossi"
				},
				"mul" : {
					"name" : "Multiple languages"
				},
				"mun" : {
					"name" : "Munda languages"
				},
				"mus" : {
					"name" : "Creek"
				},
				"mwl" : {
					"name" : "Mirandese"
				},
				"mwr" : {
					"name" : "Marwari"
				},
				"myn" : {
					"name" : "Mayan languages"
				},
				"myv" : {
					"name" : "Erzya"
				},
				"nah" : {
					"name" : "Nahuatl languages"
				},
				"nai" : {
					"name" : "North American Indian languages"
				},
				"nap" : {
					"name" : "Neapolitan"
				},
				"nau" : {
					"name" : "Nauru"
				},
				"nav" : {
					"name" : "Navajo; Navaho"
				},
				"nbl" : {
					"name" : "Ndebele, South; South Ndebele"
				},
				"nde" : {
					"name" : "Ndebele, North; North Ndebele"
				},
				"ndo" : {
					"name" : "Ndonga"
				},
				"nds" : {
					"name" : "Low German; Low Saxon; German, Low; Saxon, Low"
				},
				"nep" : {
					"name" : "Nepali"
				},
				"new" : {
					"name" : "Nepal Bhasa; Newari"
				},
				"nia" : {
					"name" : "Nias"
				},
				"nic" : {
					"name" : "Niger-Kordofanian languages"
				},
				"niu" : {
					"name" : "Niuean"
				},
				"nno" : {
					"name" : "Norwegian Nynorsk; Nynorsk, Norwegian"
				},
				"nob" : {
					"name" : "Bokmål, Norwegian; Norwegian Bokmål"
				},
				"nog" : {
					"name" : "Nogai"
				},
				"non" : {
					"name" : "Norse, Old"
				},
				"nor" : {
					"name" : "Norwegian",
					"flag" : "no"
				},
				"nqo" : {
					"name" : "N'Ko"
				},
				"nso" : {
					"name" : "Pedi; Sepedi; Northern Sotho"
				},
				"nub" : {
					"name" : "Nubian languages"
				},
				"nwc" : {
					"name" : "Classical Newari; Old Newari; Classical Nepal Bhasa"
				},
				"nya" : {
					"name" : "Chichewa; Chewa; Nyanja"
				},
				"nym" : {
					"name" : "Nyamwezi"
				},
				"nyn" : {
					"name" : "Nyankole"
				},
				"nyo" : {
					"name" : "Nyoro"
				},
				"nzi" : {
					"name" : "Nzima"
				},
				"oci" : {
					"name" : "Occitan (post 1500); Provençal"
				},
				"oji" : {
					"name" : "Ojibwa"
				},
				"ori" : {
					"name" : "Oriya"
				},
				"orm" : {
					"name" : "Oromo"
				},
				"osa" : {
					"name" : "Osage"
				},
				"oss" : {
					"name" : "Ossetian; Ossetic"
				},
				"ota" : {
					"name" : "Turkish, Ottoman (1500-1928)"
				},
				"oto" : {
					"name" : "Otomian languages"
				},
				"paa" : {
					"name" : "Papuan languages"
				},
				"pag" : {
					"name" : "Pangasinan"
				},
				"pal" : {
					"name" : "Pahlavi"
				},
				"pam" : {
					"name" : "Pampanga; Kapampangan"
				},
				"pan" : {
					"name" : "Panjabi; Punjabi"
				},
				"pap" : {
					"name" : "Papiamento"
				},
				"pau" : {
					"name" : "Palauan"
				},
				"peo" : {
					"name" : "Persian, Old (ca.600-400 B.C.)"
				},
				"per" : {
					"name" : "Persian"
				},
				"fas" : {
					"name" : "Persian"
				},
				"phi" : {
					"name" : "Philippine languages"
				},
				"phn" : {
					"name" : "Phoenician"
				},
				"pli" : {
					"name" : "Pali"
				},
				"pol" : {
					"name" : "Polish",
					"flag" : "pl"
				},
				"pon" : {
					"name" : "Pohnpeian"
				},
				"por" : {
					"name" : "Portuguese",
					"flag" : "pt"
				},
				"pra" : {
					"name" : "Prakrit languages"
				},
				"pro" : {
					"name" : "Provençal, Old (to 1500)"
				},
				"pus" : {
					"name" : "Pushto; Pashto"
				},
				"qaa-qtz" : {
					"name" : "Reserved for local use"
				},
				"que" : {
					"name" : "Quechua"
				},
				"raj" : {
					"name" : "Rajasthani"
				},
				"rap" : {
					"name" : "Rapanui"
				},
				"rar" : {
					"name" : "Rarotongan; Cook Islands Maori"
				},
				"roa" : {
					"name" : "Romance languages"
				},
				"roh" : {
					"name" : "Romansh"
				},
				"rom" : {
					"name" : "Romany"
				},
				"rum" : {
					"name" : "Romanian; Moldavian; Moldovan"
				},
				"ron" : {
					"name" : "Romanian; Moldavian; Moldovan",
					"flag" : "ro"
				},
				"run" : {
					"name" : "Rundi"
				},
				"rup" : {
					"name" : "Aromanian; Arumanian; Macedo-Romanian"
				},
				"rus" : {
					"name" : "Russian",
					"flag" : "ru"
				},
				"sad" : {
					"name" : "Sandawe"
				},
				"sag" : {
					"name" : "Sango"
				},
				"sah" : {
					"name" : "Yakut"
				},
				"sai" : {
					"name" : "South American Indian (Other)"
				},
				"sal" : {
					"name" : "Salishan languages"
				},
				"sam" : {
					"name" : "Samaritan Aramaic"
				},
				"san" : {
					"name" : "Sanskrit"
				},
				"sas" : {
					"name" : "Sasak"
				},
				"sat" : {
					"name" : "Santali"
				},
				"scn" : {
					"name" : "Sicilian"
				},
				"sco" : {
					"name" : "Scots"
				},
				"sel" : {
					"name" : "Selkup"
				},
				"sem" : {
					"name" : "Semitic languages"
				},
				"sga" : {
					"name" : "Irish, Old (to 900)"
				},
				"sgn" : {
					"name" : "Sign Languages"
				},
				"shn" : {
					"name" : "Shan"
				},
				"sid" : {
					"name" : "Sidamo"
				},
				"sin" : {
					"name" : "Sinhala; Sinhalese"
				},
				"sio" : {
					"name" : "Siouan languages"
				},
				"sit" : {
					"name" : "Sino-Tibetan languages"
				},
				"sla" : {
					"name" : "Slavic languages"
				},
				"slo" : {
					"name" : "Slovak"
				},
				"slk" : {
					"name" : "Slovak",
					"flag" : "sk"
				},
				"slv" : {
					"name" : "Slovenian",
					"flag" : "si"
				},
				"sma" : {
					"name" : "Southern Sami"
				},
				"sme" : {
					"name" : "Northern Sami"
				},
				"smi" : {
					"name" : "Sami languages"
				},
				"smj" : {
					"name" : "Lule Sami"
				},
				"smn" : {
					"name" : "Inari Sami"
				},
				"smo" : {
					"name" : "Samoan"
				},
				"sms" : {
					"name" : "Skolt Sami"
				},
				"sna" : {
					"name" : "Shona"
				},
				"snd" : {
					"name" : "Sindhi"
				},
				"snk" : {
					"name" : "Soninke"
				},
				"sog" : {
					"name" : "Sogdian"
				},
				"som" : {
					"name" : "Somali"
				},
				"son" : {
					"name" : "Songhai languages"
				},
				"sot" : {
					"name" : "Sotho, Southern"
				},
				"spa" : {
					"name" : "Spanish; Castilian",
					"flag" : "es"
				},
				"srd" : {
					"name" : "Sardinian"
				},
				"srn" : {
					"name" : "Sranan Tongo"
				},
				"srp" : {
					"name" : "Serbian",
					"flag" : "rs"
				},
				"srr" : {
					"name" : "Serer"
				},
				"ssa" : {
					"name" : "Nilo-Saharan languages"
				},
				"ssw" : {
					"name" : "Swati"
				},
				"suk" : {
					"name" : "Sukuma"
				},
				"sun" : {
					"name" : "Sundanese"
				},
				"sus" : {
					"name" : "Susu"
				},
				"sux" : {
					"name" : "Sumerian"
				},
				"swa" : {
					"name" : "Swahili"
				},
				"swe" : {
					"name" : "Swedish",
					"flag" : "se"
				},
				"syc" : {
					"name" : "Classical Syriac"
				},
				"syr" : {
					"name" : "Syriac"
				},
				"tah" : {
					"name" : "Tahitian"
				},
				"tai" : {
					"name" : "Tai languages"
				},
				"tam" : {
					"name" : "Tamil"
				},
				"tat" : {
					"name" : "Tatar"
				},
				"tel" : {
					"name" : "Telugu"
				},
				"tem" : {
					"name" : "Timne"
				},
				"ter" : {
					"name" : "Tereno"
				},
				"tet" : {
					"name" : "Tetum"
				},
				"tgk" : {
					"name" : "Tajik"
				},
				"tgl" : {
					"name" : "Tagalog"
				},
				"tha" : {
					"name" : "Thai",
					"flag" : "th"
				},
				"tib" : {
					"name" : "Tibetan"
				},
				"bod" : {
					"name" : "Tibetan"
				},
				"tig" : {
					"name" : "Tigre"
				},
				"tir" : {
					"name" : "Tigrinya"
				},
				"tiv" : {
					"name" : "Tiv"
				},
				"tkl" : {
					"name" : "Tokelau"
				},
				"tlh" : {
					"name" : "Klingon; tlhIngan-Hol"
				},
				"tli" : {
					"name" : "Tlingit"
				},
				"tmh" : {
					"name" : "Tamashek"
				},
				"tog" : {
					"name" : "Tonga (Nyasa)"
				},
				"ton" : {
					"name" : "Tonga (Tonga Islands)"
				},
				"tpi" : {
					"name" : "Tok Pisin"
				},
				"tsi" : {
					"name" : "Tsimshian"
				},
				"tsn" : {
					"name" : "Tswana"
				},
				"tso" : {
					"name" : "Tsonga"
				},
				"tuk" : {
					"name" : "Turkmen"
				},
				"tum" : {
					"name" : "Tumbuka"
				},
				"tup" : {
					"name" : "Tupi languages"
				},
				"tur" : {
					"name" : "Turkish",
					"flag" : "tr"
				},
				"tut" : {
					"name" : "Altaic languages"
				},
				"tvl" : {
					"name" : "Tuvalu"
				},
				"twi" : {
					"name" : "Twi"
				},
				"tyv" : {
					"name" : "Tuvinian"
				},
				"udm" : {
					"name" : "Udmurt"
				},
				"uga" : {
					"name" : "Ugaritic"
				},
				"uig" : {
					"name" : "Uighur; Uyghur"
				},
				"ukr" : {
					"name" : "Ukrainian",
					"flag" : "ua"
				},
				"umb" : {
					"name" : "Umbundu"
				},
				"und" : {
					"name" : "Undetermined"
				},
				"urd" : {
					"name" : "Urdu"
				},
				"uzb" : {
					"name" : "Uzbek"
				},
				"vai" : {
					"name" : "Vai"
				},
				"ven" : {
					"name" : "Venda"
				},
				"vie" : {
					"name" : "Vietnamese",
					"flag" : "vn"
				},
				"vol" : {
					"name" : "Volapük"
				},
				"vot" : {
					"name" : "Votic"
				},
				"wak" : {
					"name" : "Wakashan languages"
				},
				"wal" : {
					"name" : "Walamo"
				},
				"war" : {
					"name" : "Waray"
				},
				"was" : {
					"name" : "Washo"
				},
				"wel" : {
					"name" : "Welsh"
				},
				"cym" : {
					"name" : "Welsh"
				},
				"wen" : {
					"name" : "Sorbian languages"
				},
				"wln" : {
					"name" : "Walloon"
				},
				"wol" : {
					"name" : "Wolof"
				},
				"xal" : {
					"name" : "Kalmyk; Oirat"
				},
				"xho" : {
					"name" : "Xhosa"
				},
				"yao" : {
					"name" : "Yao"
				},
				"yap" : {
					"name" : "Yapese"
				},
				"yid" : {
					"name" : "Yiddish"
				},
				"yor" : {
					"name" : "Yoruba"
				},
				"ypk" : {
					"name" : "Yupik languages"
				},
				"zap" : {
					"name" : "Zapotec"
				},
				"zbl" : {
					"name" : "Blissymbols; Blissymbolics; Bliss"
				},
				"zen" : {
					"name" : "Zenaga"
				},
				"zha" : {
					"name" : "Zhuang; Chuang"
				},
				"znd" : {
					"name" : "Zande languages"
				},
				"zul" : {
					"name" : "Zulu"
				},
				"zun" : {
					"name" : "Zuni"
				},
				"zxx" : {
					"name" : "No linguistic content; Not applicable"
				},
				"zza" : {
					"name" : "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki"
				}
			};

			return iso;
		});
/*global define: true, require: true */
/*!
 * Aloha Editor
 * Author & Copyright (c) 2011 Gentics Software GmbH
 * aloha-sales@gentics.com
 * Licensed unter the terms of http://www.aloha-editor.com/license.html
 *
 * Language Repository
 * -------------------
 * Provides a set of language codes and images
 */
define('link/../../../shared/languages/languages',[
	'aloha',
	'jquery',
	'./iso639-1-de',
	'./iso639-2-de',
	'./iso639-1-en',
	'./iso639-2-en'
], function(
	Aloha,
	jQuery,
	iso1de,
	iso2de,
	iso1en,
	iso2en
) {
	
	
	/**
	 * Keeps reference to the language codes and names.
	 */
	var ISO_MAP = {
		'iso639-1-de': iso1de,
		'iso639-2-de': iso2de,
		'iso639-1-en': iso1en,
		'iso639-2-en': iso2en
	};
	
	/**
	 * Path to the languages files.
	 */
	var PATH = Aloha.getAlohaUrl() + '/../plugins/shared/languages/';

	return Aloha.AbstractRepository.extend({
		
		/**
		 * Set of language codes
		 */
		languageCodes: [],
		
		/**
		 * Set default locale
		 */
		locale: 'de',
		
		/**
		 * Set default iso
		 */
		iso: 'iso639-1',
		
		/**
		 * Object type of the values for this repository.
		 */
		objectType: 'language',

		/**
		 * Whether to show flags or not
		 */
		flags: false,

		_constructor: function (name, flags, iso, locale, objectType) {
			this._super(name);

			if (typeof flags !== 'undefined') {
				this.flags = flags;
			}

			if (typeof iso !== 'undefined') {
				this.iso = ('iso639-1' === iso) ? 'iso639-1' : 'iso639-2';
			}

			if (typeof locale !== 'undefined') {
				this.locale = locale;
			}
			
			if (typeof objectType !== 'undefined') {
				this.objectType = objectType;
			}
			
			
			var data = ISO_MAP[this.iso + '-' + this.locale];
			
			this.storeLanguageCodes(data);
			this.languageData = data;
		},

		/**
		 * Initializes the repository: loads the language files and prepares the data.
		 */
		init: function () {
			
		},

		markObject: function (obj, item) {
			// Copied from wai-lang-plugin makeVisible to avoid a circular dependency
			// We do not need to add this class here since it already being
			// done in the wai-lang plugin
			// jQuery( obj ).addClass( 'aloha-wai-lang' );
		},

		/**
		 * This method will invoked if a error occurres while loading data via ajax
		 */
		errorHandler: function (text, error) {
			console.log("error", this, "Error while loading languages. " + text);
		},

		/**
		 * Stores the retrieved language code data in this object
		 */
		storeLanguageCodes: function (data) {
			var that = this;
			var path = PATH + 'img/';
			// Transform loaded json into a set of repository documents
			jQuery.each(data, function (key, value) {
				var el = value;
				el.id = key;
				el.repositoryId = that.repositoryId;
				el.type = that.objectType;
				if (that.flags) {
					if (el.flag) {
						el.url = path + el.flag + '.png';
					} else {
						el.url = path + 'default.png';
					}
				}
				// el.renditions.url = "img/flags/" + e.id + ".png";
				// el.renditions.kind.thumbnail = true;
				that.languageCodes.push(new Aloha.RepositoryDocument(el));
			});
		},
		
		/**
		 * Searches a repository for object items matching query if objectTypeFilter.
		 * If none found it returns null.
		 * Not supported: filter, orderBy, maxItems, skipcount, renditionFilter
		 */
		_searchInLanguageCodes: function (p, callback) {
			var query = new RegExp('^' + p.queryString, 'i'),
		    i,
		    d = [],
		    matchesName,
		    matchesType,
		    currentElement;

			for (i = 0; i < this.languageCodes.length; ++i) {
				currentElement = this.languageCodes[i];
				matchesName = (!p.queryString || currentElement.name.match(query));
				matchesType = (!p.objectTypeFilter || (!p.objectTypeFilter.length) || jQuery.inArray(currentElement.type, p.objectTypeFilter) > -1);
	
				if (matchesName && matchesType) {
					d.push(currentElement);
				}
			}
	
			callback.call(this, d);
		},

		/**
		 * Fetches the languageCodes if they are not already loaded and
		 * searches the collection with the given query.
		 */
		query: function (p, callback) {
			this._searchInLanguageCodes(p, callback);
		},

		/**
		 * Get the repositoryItem with given id
		 * @param itemId {String} id of the repository item to fetch
		 * @param callback {function} callback function
		 * @return {Aloha.Repository.Object} item with given id
		 */
		getObjectById: function (itemId, callback) {
			var i, currentElement;

			for (i = 0; i < this.languageCodes.length; ++i) {
				currentElement = this.languageCodes[i];
				if (currentElement.id === itemId) {
					callback.call(this, [currentElement]);
					break;
				}
			}

		}
	});
});

/* link-plugin.js is part of the Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2014 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * License http://aloha-editor.org/license.php
 */
/* Aloha Link Plugin
 * -----------------
 * This plugin provides an interface to allow the user to insert, edit and
 * remove links within an active editable.
 * It presents its user interface in the Toolbar, in a Sidebar panel.
 * Clicking on any links inside the editable activates the this plugin's
 * floating menu scope.
 */
define('link/link-plugin',[
	'jquery',
	'aloha',
	'aloha/plugin',
	'aloha/ephemera',
	'aloha/content-rules',
	'util/dom',
	'ui/port-helper-attribute-field',
	'ui/ui',
	'ui/scopes',
	'ui/button',
	'ui/toggleButton',
	'i18n!link/nls/i18n',
	'PubSub',
	'util/keys',
	'../../../shared/languages/languages'
], function (
	$,
	Aloha,
	Plugin,
	Ephemera,
	ContentRules,
	Dom,
	AttributeField,
	Ui,
	Scopes,
	Button,
	ToggleButton,
	i18n,
	PubSub,
	Keys,
	LanguageRepository
) {
	

	var configurations = {};
	var jQuery = $;
	var pluginNamespace = 'aloha-link';
	var oldValue = '';
	var newValue;

	/**
	 * Regular expression that matches if an URL is an external link.
	 */
	var EXTERNAL_LINK_REG_EXP = /^([a-z]){3,10}:\/\/.+/i;
	
	/**
	 * Field for hrefLang value in the link sidebar.
	 */
	var hrefLangField;
	
	/**
	 * Language repository
	 */
	var LANG_REPOSITORY;
	
	/**
	 * Initializes href lang input text.
	 */
	function initHrefLang(plugin, sidebar) {
		hrefLangField = AttributeField({
			name: 'hreflangfield',
			valueField: 'id',
			minChars: 1,
			scope: 'Aloha.continuoustext',
			open: function (elm, ui) {
				// known issue http://bugs.jquery.com/ticket/10079
				// $.css('z-index') return 1e+9, and when call partseInt, then 
				// parseInt($.css('z-index'), 10) returns 1.
				// Only firefox issue
				// Everytime is open the autocomple the z-index must be set,
				// because is automatically changed. 
				if (Aloha.browser.mozilla) {
					hrefLangField.getInputJQuery().autocomplete('widget').css('z-index', '9999999999');
				}
			}
		});
		
		if (plugin.flags) {
			hrefLangField.setTemplate(
				 '<div class="aloha-wai-lang-img-item">' +
				  '<img class="aloha-wai-lang-img" src="{url}" />' +
				  '<div class="aloha-wai-lang-label-item">{name} ({id})</div>' +
				  '</div>');
		} else {
			hrefLangField.setTemplate('<div class="aloha-wai-lang-img-item">' +
				  '<div class="aloha-wai-lang-label-item">{name} ({id})</div>' +
				  '</div>'
			);
		}
		
		hrefLangField.setObjectTypeFilter(['language/link']);
		
		hrefLangField.addListener('item-change', function() {
			if (this.getItem()) {
				jQuery(sidebar.effective ).attr( 'hreflang', this.getItem().id);
			}
		});
		
		hrefLangField.addListener('keyup', function() {
			if (jQuery.trim(this.getValue()).length === 0) {
				this.setValue('');
				jQuery(sidebar.effective ).attr( 'hreflang', '');
			}
		});
	}
	
	/**
	 * Gets the language name for laguage code 'langCode'.
	 * @param {string} langCode Language code
	 */
	function getLanguageName(langCode) {
		return LANG_REPOSITORY.languageData ? LANG_REPOSITORY.languageData[langCode].name : langCode;
	}

	/**
	 * Properties for cleaning up markup immediately after inserting new link
	 * markup.
	 *
	 * Successive anchor elements are generally not merged, but an exception
	 * needs to be made in the process of creating links: adjacent fragments of
	 * new links are coalesced whenever possible.
	 *
	 * @type {object}
	 */
	var insertLinkPostCleanup = {
		merge: true,
		mergeable: function (node) {
			return ('aloha-new-link' === node.className && node.nextSibling &&
				'aloha-new-link' === node.nextSibling.className);
		}
	};
	
	Ephemera.classes('aloha-link-pointer', 'aloha-link-text');

	function setupMousePointerFix() {
		jQuery(document).bind('keydown.aloha-link.pointer-fix', function (e) {
				// metaKey for OSX, 17 for PC (we can't check
				// e.ctrlKey because it's only set on keyup or
				// keypress, not on keydown).
				if (e.metaKey || Keys.getToken(e.keyCode) === 'control') {
					jQuery('body').addClass('aloha-link-pointer');
				}
			})
			.bind('keyup.aloha-link.pointer-fix', function (e) {
				if (e.metaKey || Keys.getToken(e.keyCode) === 'control') {
					jQuery('body').removeClass('aloha-link-pointer');
				}
			});
	}

	function teardownMousePointerFix() {
		jQuery(document).unbind('.aloha-link.pointer-fix');
	}

	function setupMetaClickLink(editable) {
		editable.obj.delegate('a', 'click.aloha-link.meta-click-link', function (e) {
			// Use metaKey for OSX and ctrlKey for PC
			if (e.metaKey || e.ctrlKey) {
				// blur current editable. user is waiting for the link to load
				Aloha.activeEditable.blur();
				// hack to guarantee a browser history entry
				window.setTimeout(function () {
					location.href = e.target;
				}, 0);
				e.stopPropagation();
				return false;
			}
		});
	}

	function teardownMetaClickLink(editable) {
		editable.obj.unbind('.aloha-link.meta-click-link');
	}

	/**
	 * Get the translation from the given i18n object.
	 * The object should be composed like:
	 * {
	 *   "en": "Path",
	 *   "de": "Pfad"
	 * }
	 * 
	 * If the translation in the current language is not found,
	 * the first translation will be returned
	 * @param i18nObject {Object} i18n Object
	 * @return translation {String}
	 */
	function _i18n(i18nObject) {
		if (!i18nObject) {
			return '';
		}
		if (i18nObject.hasOwnProperty(Aloha.settings.locale)) {
			return i18nObject[Aloha.settings.locale];
		}

		for (var lang in i18nObject) {
			if (i18nObject.hasOwnProperty(lang)) {
				return i18nObject[lang];
			}
		}

		return '';
	}

	return Plugin.create('link', {
		/**
		 * Default configuration allows links everywhere
		 */
		config: [ 'a' ],

		/**
		 * The value that will automatically be set to an anchor tag's title
		 * attribute if its href field matches the titleregex, and the editor
		 * has not manually defined the title attribute.
		 *
		 * @type {string}
		 */
		title: null,

		/**
		 * Regular Expression string which the field's href value will be tested
		 * against in order to determine whether or not to set the configured
		 * title attribute value.
		 *
		 * @type {string}
		 */
		titleregex: null,

		/**
		 * all links that match the targetregex will get set the target
		 * e.g. ^(?!.*aloha-editor.com).* matches all href except aloha-editor.com
		 */
		targetregex: '',
		
		/**
		  * this target is set when either targetregex matches or not set
		  * e.g. _blank opens all links in new window
		  */
		target: '',
		
		/**
		 * all links that match the cssclassregex will get set the css class
		 * e.g. ^(?!.*aloha-editor.com).* matches all href except aloha-editor.com
		 */
		cssclassregex: null,
		
		/**
		  * this target is set when either cssclassregex matches or not set
		  */
		cssclass: '',
		
		/**
		 * the defined object types to be used for this instance
		 */
		objectTypeFilter: [],
		
		/**
		 * handle change on href change
		 * called function ( obj, href, item );
		 */
		onHrefChange: null,
		
		/**
		 * This variable is used to ignore one selection changed event. We need
		 * to ignore one selectionchanged event when we set our own selection.
		 */
		ignoreNextSelectionChangedEvent: false,

		/**
		 * Internal update interval reference to work around an ExtJS bug
		 */
		hrefUpdateInt: null,
		
		/**
		 * HotKeys used for special actions
		*/
		hotKey: {
			insertLink: i18n.t('insertLink', 'ctrl+k')
		},
		
		/**
		 * Default input value for a new link
		*/
		hrefValue: 'http://',
		
		/**
		 * Shows the flags when setting language ('hreflang' attribute).
		 */
		flags: true,
		
		/**
		 * Initializes the plugin.
		 */
		init: function () {
			var plugin = this;

			if ('undefined' !== typeof this.settings.title) {
				this.title = this.settings.title;
			}
			if ('undefined' !== typeof this.settings.titleregex) {
				this.titleregex = this.settings.titleregex;
			}
			if (typeof this.settings.targetregex != 'undefined') {
				this.targetregex = this.settings.targetregex;
			}
			if (typeof this.settings.target != 'undefined') {
				this.target = this.settings.target;
			}
			if (typeof this.settings.cssclassregex != 'undefined') {
				this.cssclassregex = this.settings.cssclassregex;
			}
			if (typeof this.settings.cssclass != 'undefined') {
				this.cssclass = this.settings.cssclass;
			}
			if (typeof this.settings.objectTypeFilter != 'undefined') {
				this.objectTypeFilter = this.settings.objectTypeFilter;
			}
			if (typeof this.settings.onHrefChange != 'undefined') {
				this.onHrefChange = this.settings.onHrefChange;
			}
			if (typeof this.settings.hotKey != 'undefined') {
				jQuery.extend(true, this.hotKey, this.settings.hotKey);
			}
			if (typeof this.settings.hrefValue != 'undefined') {
				this.hrefValue = this.settings.hrefValue;
			}
			
			this.createButtons();
			this.subscribeEvents();
			this.bindInteractions();

			Aloha.bind('aloha-plugins-loaded', function () {
				plugin.initSidebar(Aloha.Sidebar.right);
				PubSub.pub('aloha.link.ready', {
					plugin: plugin
				});
			});
		},

		nsSel: function () {
			var stringBuilder = [], prefix = pluginNamespace;
			jQuery.each(arguments, function () {
				stringBuilder.push('.' + (this == '' ? prefix : prefix + '-' + this));
			});
			return jQuery.trim(stringBuilder.join(' '));
		},

		//Creates string with this component's namepsace prefixed the each classname
		nsClass: function () {
			var stringBuilder = [], prefix = pluginNamespace;
			jQuery.each(arguments, function () {
				stringBuilder.push(this == '' ? prefix : prefix + '-' + this);
			});
			return jQuery.trim(stringBuilder.join(' '));
		},

		initSidebar: function (sidebar) {
			var pl = this;
			sidebar.addPanel( {
				id       : pl.nsClass( 'sidebar-panel-target' ),
				title    : i18n.t( 'floatingmenu.tab.link' ),
				content  : '',
				expanded : true,
				activeOn : 'a, link',
				
				onInit: function () {
					initHrefLang(pl, this);

					var infoFields = '';
					if (jQuery.isArray(pl.settings.sidebar)) {
						jQuery.each(pl.settings.sidebar, function () {
							infoFields += '<div class="' + pl.nsClass('title-container') + '"><fieldset><legend>' + _i18n(this.title) + '</legend><span class="' + pl.nsClass( this.attr ) + '"></span></fieldset></div>';
						});
					}

					 var that = this,
						 content = this.setContent(
							'<div class="' + pl.nsClass( 'target-container' ) + '"><fieldset><legend>' + i18n.t( 'link.target.legend' ) + '</legend><ul><li><input type="radio" name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '" value="_self" /><span>' + i18n.t( 'link.target.self' ) + '</span></li>' + 
							'<li><input type="radio" name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '" value="_blank" /><span>' + i18n.t( 'link.target.blank' ) + '</span></li>' + 
							'<li><input type="radio" name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '" value="_parent" /><span>' + i18n.t( 'link.target.parent' ) + '</span></li>' + 
							'<li><input type="radio" name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '" value="_top" /><span>' + i18n.t( 'link.target.top' ) + '</span></li>' + 
							'<li><input type="radio" name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '" value="framename" /><span>' + i18n.t( 'link.target.framename' ) + '</span></li>' + 
							'<li><input type="text" class="' + pl.nsClass( 'framename' ) + '" /></li></ul></fieldset></div>' + 
							'<div class="' + pl.nsClass( 'title-container' ) + '" ><fieldset><legend>' + i18n.t( 'link.title.legend' ) + '</legend><input type="text" class="' + pl.nsClass( 'linkTitle' ) + '" /></fieldset></div>' +
							'<div class="' + pl.nsClass( 'href-lang-container' ) + '" ><fieldset><legend>' + i18n.t( 'href.lang.legend' ) + '</legend></fieldset></div>' +
							infoFields
						).content; 
					 
					 jQuery(hrefLangField.getInputElem()).addClass(pl.nsClass( 'hrefLang' ));
					 jQuery(content).find("." + pl.nsClass( 'href-lang-container' ) + " fieldset").append(hrefLangField.getInputElem());
					 
					 jQuery( pl.nsSel( 'framename' ) ).live( 'keyup', function () {
						jQuery( that.effective ).attr( 'target', jQuery( this ).val().replace( '\"', '&quot;' ).replace( "'", "&#39;" ) );
					 } );
					 
					 jQuery( pl.nsSel( 'radioTarget' ) ).live( 'change', function () {
						if ( jQuery( this ).val() == 'framename' ) {
							jQuery( pl.nsSel( 'framename' ) ).slideDown();
						} else {
							jQuery(pl.nsSel('framename')).slideUp().val( '' );
							jQuery(that.effective).attr('target', jQuery( this ).val());
						}
					 } );
					 
					 jQuery( pl.nsSel( 'linkTitle' ) ).live( 'keyup', function () {
						jQuery( that.effective ).attr( 'title', jQuery( this ).val().replace( '\"', '&quot;' ).replace( "'", "&#39;" ) );
					 } );
				},
				
				onActivate: function ( effective ) {
					var that = this;
					that.effective = effective;
					if ( jQuery( that.effective ).attr( 'target' ) != null ) {
						var isFramename = true;
						jQuery( pl.nsSel( 'framename' ) ).hide().val( '' );
						jQuery( pl.nsSel( 'radioTarget' ) ).each( function () {
							jQuery( this ).removeAttr('checked');
							if ( jQuery( this ).val() === jQuery( that.effective ).attr( 'target' ) ) {
								isFramename = false;
								jQuery( this ).attr( 'checked', 'checked' );
							}
						} );
						if ( isFramename ) {
							jQuery( pl.nsSel( 'radioTarget[value="framename"]' ) ).attr( 'checked', 'checked' );
							jQuery( pl.nsSel( 'framename' ) )
								.val( jQuery( that.effective ).attr( 'target' ) )
								.show();
						}
					} else {
						jQuery( pl.nsSel( 'radioTarget' ) ).first().attr( 'checked', 'checked' );
						jQuery( that.effective ).attr( 'target', jQuery( pl.nsSel( 'radioTarget' ) ).first().val() );
					}
					
					var that = this;
					that.effective = effective;
					jQuery( pl.nsSel( 'linkTitle' ) ).val( jQuery( that.effective ).attr( 'title' ) );
					
					var hrefLangAttr = jQuery(effective).attr('hreflang');
					
					if (hrefLangAttr && hrefLangAttr.length > 0) {
						var languageName = getLanguageName(hrefLangAttr);
						hrefLangField.setValue(languageName);
					} else {
						hrefLangField.setValue('');
					}
					
					if (EXTERNAL_LINK_REG_EXP.test(jQuery(effective).attr('href'))) {
						hrefLangField.enableInput();
					} else {
						hrefLangField.disableInput();
					}
				}
				
			} );
			
			sidebar.show();
		},
		
		/**
		 * Subscribe for events
		 */
		subscribeEvents: function () {
			var plugin = this;
			var editablesCreated = 0;

			PubSub.sub('aloha.editable.created', function (message) {
				var editable = message.editable;
				var config = plugin.getEditableConfig(editable.obj);
				var enabled = config
				           && (jQuery.inArray('a', config) > -1)
				           && ContentRules.isAllowed(editable.obj[0], 'a');

				configurations[editable.getId()] = !!enabled;

				if (!enabled) {
					return;
				}

				// enable hotkey for inserting links
				editable.obj.bind('keydown.aloha-link', plugin.hotKey.insertLink, function () {
					if (plugin.findLinkMarkup()) {
						plugin.hrefField.foreground();
						plugin.hrefField.focus();
					} else {
						plugin.insertLink(true);
					}
					return false;
				});

				editable.obj.find('a').each(function() {
					plugin.addLinkEventHandlers(this);
				});

				if (0 === editablesCreated++) {
					setupMousePointerFix();
				}
			});

			PubSub.sub('aloha.editable.destroyed', function (message) {
				message.editable.obj.unbind('.aloha-link');
				if (0 === --editablesCreated) {
					teardownMousePointerFix();
				}
			});

			PubSub.sub('aloha.editable.activated', function (message) {
				if (configurations[message.editable.getId()]) {
					plugin._formatLinkButton.show();
					plugin._insertLinkButton.show();
				} else {
					plugin._formatLinkButton.hide();
					plugin._insertLinkButton.hide();
				}
				setupMetaClickLink(message.editable);
			});

			PubSub.sub('aloha.selection.context-change', function (message) {
				if (!Aloha.activeEditable) {
					plugin.lastActiveLink = false;
					return;
				}
				var activeLink = false;
				if (configurations[Aloha.activeEditable.getId()]) {
					activeLink = selectionChangeHandler(plugin, message.range);
					// Only foreground the tab containing the href field the
					// first time the user enters the link scope to avoid
					// intefering with the user's manual tab selection
					if (activeLink !== false && !activeLink.is(plugin.lastActiveLink)) {
						// put the field into foreground with a timeout, so that this
						// overrules other plugins that change the active toolbar tab
						// by setting the scope
						setTimeout(function () {
							plugin.hrefField.foreground();
						}, 10);
					}
				}
				plugin.lastActiveLink = activeLink;
			});

			// Fixes problem: if one clicks from inside an aloha link outside
			// the editable and thereby deactivates the editable, the link scope
			// will remain active
			PubSub.sub('aloha.editable.deactivated', function (message) {
				if (plugin.lastActiveLink !== false) {
					// Leave the link scope lazily to avoid flickering when
					// switching between anchor element editables
					setTimeout(function () {
						if (!plugin.lastActiveLink) {
							plugin.toggleLinkScope(false);
						}
					}, 100);
					plugin.lastActiveLink = false;
				}
				teardownMetaClickLink(message.editable);
			});
		},

		/**
		 * lets you toggle the link scope to true or false
		 * @param show bool
		 */
		toggleLinkScope: function ( show ) {
			// Check before doing anything as a performance improvement.
			// The _isScopeActive_editableId check ensures that when
			// changing from a normal link in an editable to an editable
			// that is a link itself, the removeLinkButton will be
			// hidden.
			if (this._isScopeActive === show && Aloha.activeEditable && this._isScopeActive_editableId === Aloha.activeEditable.getId()) {
				return;
			}
			this._isScopeActive = show;
			this._isScopeActive_editableId = Aloha.activeEditable && Aloha.activeEditable.getId();
			if (!configurations[this._isScopeActive_editableId]) {
				this.hrefField.hide();
				this._insertLinkButton.hide();
				this._removeLinkButton.hide();
				this._formatLinkButton.setState(false);
				// The calls to enterScope and leaveScope by the link
				// plugin are not balanced.
				// When the selection is changed from one link to
				// another, the link scope is incremented more than
				// decremented, which necessitates the force=true
				// argument to leaveScope.
				Scopes.leaveScope(this.name, 'link', true);
			} else if ( show ) {
				this.hrefField.show();
				this._insertLinkButton.hide();
				// Never show the removeLinkButton when the link itself
				// is the editable.
				if (Aloha.activeEditable && Aloha.activeEditable.obj[0].nodeName === 'A') {
					this._removeLinkButton.hide();
				} else {
					this._removeLinkButton.show();
				}
				this._formatLinkButton.setState(true);
				Scopes.enterScope(this.name, 'link');
			} else {
				this.hrefField.hide();
				this._insertLinkButton.show();
				this._removeLinkButton.hide();
				this._formatLinkButton.setState(false);
				// The calls to enterScope and leaveScope by the link
				// plugin are not balanced.
				// When the selection is changed from one link to
				// another, the link scope is incremented more than
				// decremented, which necessitates the force=true
				// argument to leaveScope.
				Scopes.leaveScope(this.name, 'link', true);
			}
		},

		/**
		 * Add event handlers to the given link object
		 * @param link object
		 */
		addLinkEventHandlers: function ( link ) {
			var that = this;

			// show pointer on mouse over
			jQuery( link ).mouseenter( function ( e ) {
				Aloha.Log.debug( that, 'mouse over link.' );
				that.mouseOverLink = link;
				that.updateMousePointer();
			} );

			// in any case on leave show text cursor
			jQuery( link ).mouseleave( function ( e ) {
				Aloha.Log.debug( that, 'mouse left link.' );
				that.mouseOverLink = null;
				that.updateMousePointer();
			} );

			// follow link on ctrl or meta + click
			jQuery( link ).click( function ( e ) {
				if ( e.metaKey ) {
					// blur current editable. user is waiting for the link to load
					Aloha.activeEditable.blur();
					// hack to guarantee a browser history entry
					window.setTimeout( function () {
						location.href = e.target;
					}, 0 );
					e.stopPropagation();

					return false;
				}
			} );
		},

		/**
		 * Initialize the buttons
		 */
		createButtons: function () {
			var that = this;

			this._formatLinkButton = Ui.adopt("formatLink", ToggleButton, {
				tooltip: i18n.t("button.addlink.tooltip"),
				icon: "aloha-icon aloha-icon-link",
				scope: 'Aloha.continuoustext',
				click: function() {
					that.formatLink();
				}
			});

			this._insertLinkButton = Ui.adopt("insertLink", Button, {
				tooltip: i18n.t("button.addlink.tooltip"),
				icon: "aloha-icon aloha-icon-link",
				scope: 'Aloha.continuoustext',
				click: function() {
					that.insertLink(false);
				}
			});
			
			this.hrefField = AttributeField({
				name: 'editLink',
				width: 320,
				valueField: 'url',
				cls: 'aloha-link-href-field',
				scope: 'Aloha.continuoustext',
				noTargetHighlight: false,
				targetHighlightClass: 'aloha-focus'
			});
			this.hrefField.setTemplate('<span><b>{name}</b><br/>{url}</span>');
			this.hrefField.setObjectTypeFilter( this.objectTypeFilter );

			this._removeLinkButton = Ui.adopt("removeLink", Button, {
				tooltip: i18n.t("button.removelink.tooltip"),
				icon: "aloha-icon aloha-icon-unlink",
				scope: 'Aloha.continuoustext',
				click: function() {
					that.removeLink();
				}
			});
		},

		/**
		 * Parse a all editables for links and bind an onclick event
		 * Add the link short cut to all edtiables
		 */
		bindInteractions: function () {
			var that = this;

			this.hrefField.addListener('item-change', function(){
				// because 'hrefChange()' references 'this' object.
				that.hrefChange();
			});
			
			// update link object when src changes
			this.hrefField.addListener( 'keyup', function ( event ) {
				if (Keys.getToken(event.keyCode) === 'escape') {
					var curval = that.hrefField.getValue();
					if ( curval[ 0 ] == '/' || // local link
						 curval[ 0 ] == '#' || // inner document link
						 curval.match( /^.*\.([a-z]){2,4}$/i ) || // local file with extension
						 curval.match( EXTERNAL_LINK_REG_EXP ) || // external link (http(s), ftp(s), ssh, file, skype, ... )
						 curval.match( /^(mailto|tel):.+/i ) // mailto / tel link
					) {
						// could be a link better leave it as it is
					} else {
						// the user searched for something and aborted
						var hrefValue = that.hrefField.getValue();
						
						// restore original value and hide combo list
						that.hrefField.setValue( hrefValue );
						
						if ( hrefValue == that.hrefValue || hrefValue == '' ) {
							that.removeLink( false );
						}
						
					}
				}
				
				that.hrefChange();
				
				// Terminate the link scope and show the final link.
				if (Keys.getToken(event.keyCode) === 'enter') {
					// Update the selection and place the cursor at the end of the link.
					var	range = Aloha.Selection.getRangeObject();
					
					// workaround to keep the found markup otherwise removelink won't work
//					var foundMarkup = that.findLinkMarkup( range );
//					console.dir(foundMarkup);
//					that.hrefField.setTargetObject(foundMarkup, 'href');
					
					// We have to ignore the next 2 onselectionchange events.
					// The first one we need to ignore is the one trigger when
					// we reposition the selection to right at the end of the
					// link.

					that.ignoreNextSelectionChangedEvent = true;
					range.startContainer = range.endContainer;
					range.startOffset = range.endOffset;
					range.select();

					// At least Mozilla still has the focus on the href input field.
					// TODO: Should Range.select() perhaps ensure that the editable
					// actually has the focus?
					var editable = $(range.startContainer).closest('.aloha-editable,.aloha-table-cell-editable');

					if (editable.length > 0 && Aloha.browser.mozilla && document.activeElement !== editable[0]) {
						editable.focus();
					}

					var hrefValue = jQuery( that.hrefField.getInputElem() ).attr( 'value' );
					
					if ( hrefValue == that.hrefValue || hrefValue == '' ) {
						that.removeLink( false );
					}
					
					window.setTimeout( function () {
						Scopes.setScope('Aloha.continuoustext');
					}, 100 );
				} else {
					// Check whether the value in the input field has changed
					// because if it has, then the ui-attribute object's store
					// needs to be cleared. The reason we need to do this
					// clearing is because once the auto-suggeset combo box is
					// shown and/or populated, the next enter keypress event
					// would be handled as if the user is selecting one of the
					// elements in the down down list.
					newValue = jQuery( that.hrefField.getInputElem() ).attr( 'value' );
					if ( oldValue != newValue ) {
						oldValue = newValue;
					}
				}
			});

			jQuery( document )
				.keydown( function ( e ) {
					Aloha.Log.debug( that, 'Meta key down.' );
					that.metaKey = e.metaKey;
					that.updateMousePointer();
				} ).keyup( function ( e ) {
					Aloha.Log.debug( that, 'Meta key up.' );
					that.metaKey = e.metaKey;
					that.updateMousePointer();
				} );
		},
		
		/**
		 * Updates the mouse pointer
		 */
		updateMousePointer: function () {
			if ( this.metaKey && this.mouseOverLink ) {
				Aloha.Log.debug( this, 'set pointer' );
				jQuery( this.mouseOverLink ).removeClass( 'aloha-link-text' );
				jQuery( this.mouseOverLink ).addClass( 'aloha-link-pointer' );
			} else {
				jQuery( this.mouseOverLink ).removeClass( 'aloha-link-pointer' );
				jQuery( this.mouseOverLink ).addClass( 'aloha-link-text' );
			}
		},

		/**
		 * Check whether inside a link tag
		 * @param {RangeObject} range range where to insert the
		 *			object (at start or end)
		 * @return markup
		 * @hide
		 */
		findLinkMarkup: function ( range ) {
			if ( typeof range == 'undefined' ) {
				range = Aloha.Selection.getRangeObject();
			}
			if ( Aloha.activeEditable ) {
				// If the anchor element itself is the editable, we
				// still want to show the link tab.
				var limit = Aloha.activeEditable.obj;
				if (limit[0] && limit[0].nodeName === 'A') {
					limit = limit.parent();
				}
				return range.findMarkup(function () {
					return this.nodeName == 'A';
				}, limit);
			} else {
				return null;
			}
		},

		/**
		 * Format the current selection or if collapsed the current word as
		 * link. If inside a link tag the link is removed.
		 */
		formatLink: function () {
			if ( Aloha.activeEditable ) {
				if ( this.findLinkMarkup( Aloha.Selection.getRangeObject() ) ) {
					this.removeLink();
				} else {
					this.insertLink();
				}
			}
		},

		/**
		 * Insert a new link at the current selection. When the selection is
		 * collapsed, the link will have a default link text, otherwise the
		 * selected text will be the link text.
		 */
		insertLink: function ( extendToWord ) {
			var that = this,
			    range = Aloha.Selection.getRangeObject(),
			    linkText,
			    newLink;
			
			// There are occasions where we do not get a valid range, in such
			// cases we should not try and add a link
			if ( !( range.startContainer && range.endContainer ) ) {
				return;
			}
			
			// do not nest a link inside a link
			if ( this.findLinkMarkup( range ) ) {
				return;
			}
			
			// activate floating menu tab
			this.hrefField.foreground();
			
			// if selection is collapsed then extend to the word.
			if ( range.isCollapsed() && extendToWord !== false ) {
				Dom.extendToWord( range );
			}
			if ( range.isCollapsed() ) {
				// insert a link with text here
				linkText = i18n.t( 'newlink.defaulttext' );
				newLink = jQuery( '<a href="' + that.hrefValue + '" class="aloha-new-link">' + linkText + '</a>' );
				Dom.insertIntoDOM( newLink, range, jQuery( Aloha.activeEditable.obj ) );
				range.startContainer = range.endContainer = newLink.contents().get( 0 );
				range.startOffset = 0;
				range.endOffset = linkText.length;
			} else {
				newLink = jQuery( '<a href="' + that.hrefValue + '" class="aloha-new-link"></a>' );
				Dom.addMarkup( range, newLink, false );
				Dom.doCleanup(insertLinkPostCleanup, range);
			}

			Aloha.activeEditable.obj.find( 'a.aloha-new-link' ).each( function ( i ) {
				that.addLinkEventHandlers( this );
				jQuery(this).removeClass( 'aloha-new-link' );
			} );

			range.select();


			// focus has to become before prefilling the attribute, otherwise
			// Chrome and Firefox will not focus the element correctly.
			this.hrefField.focus();
			
			// prefill and select the new href
			// We need this guard because sometimes the element has not yet been initialized
			if ( this.hrefField.hasInputElem() ) {
				jQuery( this.hrefField.getInputElem() ).attr( 'value', that.hrefValue ).select();
			}
			
			// because the Aloha Selection is deprecated I need to convert it to a ragne
			var apiRange = Aloha.createRange();
			apiRange.setStart(range.startContainer, range.startOffset);
			apiRange.setEnd(range.endContainer, range.endOffset);

			PubSub.pub('aloha.link.insert', {range: apiRange});
			this.hrefChange();
		},

		/**
		 * Remove an a tag and clear the current item from the hrefField
		 */
		removeLink: function ( terminateLinkScope ) {
			var	range = Aloha.Selection.getRangeObject(),
				foundMarkup = this.findLinkMarkup();
			var linkText;

			// clear the current item from the href field
			this.hrefField.setItem(null);
			if ( foundMarkup ) {
				linkText = jQuery(foundMarkup).text();
				// remove the link
				Dom.removeFromDOM( foundMarkup, range, true );

				range.startContainer = range.endContainer;
				range.startOffset = range.endOffset;

				// select the (possibly modified) range
				range.select();
				
				if ( typeof terminateLinkScope == 'undefined' ||
						terminateLinkScope === true ) {
					Scopes.setScope('Aloha.continuoustext');
				}

				// trigger an event for removing the link
				var apiRange = Aloha.createRange();
				apiRange.setStart(range.startContainer, range.startOffset);
				apiRange.setEnd(range.endContainer, range.endOffset);

				PubSub.pub('aloha.link.remove', {
					range: apiRange,
					text: linkText
				});
			}
		},

		/**
		 * Automatically sets (or unsets) the title attribute value of the given
		 * AttributeField's target anchor element based on the link's href
		 * value.
		 *
		 * @param {AttributeField} field The AttributeField that is to be used.
		 * @param {string} value The value to which the title attribute is to be
		 *                       set to.
		 * @param {string} regex A string representing a regular expression
		 *                       against which to test the href value of the
		 *                       AttributeField `field`, to predicate whether
		 *                       the title field should be update or not.
		 */
		automaticallySetTitle: function (field, value, regex) {
			var currentValue = jQuery(field.getTargetObject()).attr('title');
			var canOverwriteTitle = !currentValue || value === currentValue;
			if (value && canOverwriteTitle) {
				field.setAttribute('title', value, regex, field.getValue());
			}
		},

		/**
		 * Updates the link object depending on the src field
		 */
		hrefChange: function () {
			var hrefTargetObject = jQuery(this.hrefField.getTargetObject());
			var setAutoValues = hrefTargetObject && !hrefTargetObject.attr('data-ignore-auto-values');
			var that = this;

			// No need to check setAutoValues here, since the title will not be
			// changed anyway once a custom title has been set.
			this.automaticallySetTitle(
				this.hrefField,
				this.title,
				this.titleregex
			);

			// For now hard coded attribute handling with regex.
			// Avoid creating the target attribute, if it's unnecessary, so
			// that XSS scanners (AntiSamy) don't complain.
			if (setAutoValues && this.target) {
				this.hrefField.setAttribute(
					'target',
					this.target,
					this.targetregex,
					this.hrefField.getValue()
				);
			}

			if (setAutoValues && this.cssclassregex) {
				this.hrefField.setAttribute(
					'class',
					this.cssclass,
					this.cssclassregex,
					this.hrefField.getValue()
				);
			}

			var href = that.hrefField.getValue();
			var element = that.hrefField.getTargetObject();
			
			Aloha.trigger('aloha-link-href-change', {
				 href: href,
				 obj: element,
				 item: that.hrefField.getItem()
			});

			PubSub.pub('aloha.link.changed', {
				href: href,
				element: element,
				input: that.hrefField.getInputElem()
			});
			
			if ( typeof this.onHrefChange == 'function' ) {
				this.onHrefChange.call(
					this,
					this.hrefField.getTargetObject(),
					this.hrefField.getValue(),
					this.hrefField.getItem()
				);
			}
			
			var hrefFieldItem = this.hrefField.getItem();
			// If href has been set to an item (Page)
			if (hrefFieldItem && hrefFieldItem.language) {
				var languageName = getLanguageName(hrefFieldItem.language);
				
				this.hrefField.setAttribute('hreflang', hrefFieldItem.language);
				hrefLangField.setValue(languageName);
				hrefLangField.disableInput();
			}
			// href is an external link
			else if (EXTERNAL_LINK_REG_EXP.test(href)){
				hrefLangField.enableInput();
			}
			// href is being defined
			else {
				hrefLangField.setValue('');
				hrefLangField.disableInput();
			}

			Aloha.Sidebar.right.checkActivePanels(Aloha.Selection.getRangeObject());

			// fill all info fields
			if (jQuery.isArray(that.settings.sidebar)) {
				jQuery.each(that.settings.sidebar, function () {
					if (hrefFieldItem && hrefFieldItem.hasOwnProperty(this.attr)) {
						jQuery(that.nsSel(this.attr)).text(hrefFieldItem[this.attr]);
					} else {
						jQuery(that.nsSel(this.attr)).text("");
					}
				});
			}
		}
	});

	/**
	 * Add additional target objects, in case the selection includes
	 * several links tag
	 *
	 * @param {RangeObject} rangeObject Selection Range
	 * @param {LinkPlugin} that Link Plugin object
	 */
	function addAdditionalTargetObject(rangeObject, field) {
		var links = rangeObject.findAllMarkupByTagName('A', rangeObject);
		for (var i = 0, len = links.length; i < len; i++) {
			field.addAdditionalTargetObject(links[i]);
		}
	}

	/**
	 * Selection change handler.
	 *
	 * @param {LinkPlugin} that This Link Plugin object
	 * @param {RangeObject} rangeObject Selection Range
	 * @returns {boolean|DomObject} The Dom Object if a link was selected,
	 *                    False otherwise
	 */
	function selectionChangeHandler(that, rangeObject) {
		var foundMarkup,
		    enteredLinkScope = false;

		// Check if we need to ignore this selection changed event for
		// now and check whether the selection was placed within a
		// editable area.
		if (!that.ignoreNextSelectionChangedEvent &&
			Aloha.Selection.isSelectionEditable() &&
			Aloha.activeEditable != null ) {
			
			foundMarkup = jQuery(that.findLinkMarkup(rangeObject));

			if (foundMarkup.length > 0) {
				that.toggleLinkScope(true);

				// now we are ready to set the target object
				foundMarkup.attr('data-ignore-auto-values', 'true');
				that.hrefField.setTargetObject(foundMarkup, 'href');
				foundMarkup.removeAttr('data-ignore-auto-values');
				addAdditionalTargetObject(rangeObject, that.hrefField);
				// if the selection-changed event was raised by the first click interaction on this page
				// the hrefField component might not be initialized. When the user switches to the link
				// tab to edit the link the field would be empty. We check for that situation and add a
				// special interval check to set the value once again
				if (jQuery('#' + that.hrefField.getInputId()).length == 0) {
					// there must only be one update interval running at the same time
					if (that.hrefUpdateInt !== null) {
						clearInterval(that.hrefUpdateInt);
					}
					
					// register a timeout that will set the value as soon as the href field was initialized
					that.hrefUpdateInt = setInterval( function () {
						if (jQuery( '#' + that.hrefField.getInputId()).length > 0) { // the object was finally created
							that.hrefField.setTargetObject(foundMarkup, 'href');
							clearInterval(that.hrefUpdateInt);
						}
					}, 200);
				}
				Aloha.trigger('aloha-link-selected');
				enteredLinkScope = true;

				PubSub.pub('aloha.link.selected', {
					input: that.hrefField.getInputElem(),
					href: that.hrefField.getValue(),
					element: that.hrefField.getTargetObject()
				});
			} else {
				that.toggleLinkScope(false);
				that.hrefField.setTargetObject(null);
				Aloha.trigger('aloha-link-unselected');
			}
		} else {
			that.toggleLinkScope(false);
		}
		
		that.ignoreNextSelectionChangedEvent = false;
		return enteredLinkScope ? foundMarkup : false;
	}
} );

define('ui/dialog',[
	'jquery',
	'ui/component',
	'i18n!ui/nls/i18n'
], function (
	$,
	Component,
	i18n
) {
	

	function makeDialogDiv(props) {
		var textOrHtml = {};
		if (props.text) {
			textOrHtml.text = props.text;
		}
		if (props.html) {
			textOrHtml.html = props.html;
		}
		return $('<div>', textOrHtml);
	}

	/**
	 * Wraps the callback function so to destory the dialog when the callback is
	 * invoked.
	 *
	 * @param {function} callback
	 * @return {function} Wrapped callback.
	 */
	function callbackAndDestroy(callback) {
		return function () {
			callback.apply(this);
			$(this).dialog('destroy').remove();
		};
	}

	function wrapDialogButtons(buttons) {
		// Buttons automatically close the dialog for convenience
		var title;
		for (title in buttons) {
			if (buttons.hasOwnProperty(title)) {
				buttons[title] = callbackAndDestroy(buttons[title]);
			}
		}
		return buttons;
	}

	function makeDialogProps(props, defaultTitle) {
		// All root elements of widgets added to the page by aloha should have the class 'aloha'.
		// All ui elements should have the class aloha-ui.
		// aloha-dialog is used for a hack to prevent a click in the
		// dialog from bluggin the editable search for aloha-dialog in
		// the aloha core for more information.
		var cls = 'aloha aloha-dialog aloha-ui';
		if (props.cls) {
			cls += ' ' + props.cls;
		}
		return {
			'resizable': false,
			'modal': true,
			'title': props.title || defaultTitle,
			'dialogClass': cls,
			'zIndex': 10200
		};
	}

	return {
		/**
		 * Shows a confirm dialog.
		 *
		 * A confirm dialog has a confirm icon and style and yes and no buttons.
		 *
		 * @param props is an object with the following properties (all optional):
		 *          title - the title of the dialog
		 *           text - either the text inside the dialog
		 *           html - or the html inside the dialog
		 *            yes - makes a "Yes" button in the dialog and invokes the given callback if it is pressed.
		 *             no - makes a "No" button in the dialog and invokes the given callback if it is pressed.
		 *         answer - makes a "Yes" and "No" button in the dialog and pressing either will invoke the
		 *                  callback with the answer as a boolean argument. Does not interfere with yes and
		 *                  no properties.
		 *            cls - the root element of the dialog will receive this class
		 *        buttons - an object where the properties are button titles and the values are callbacks
		 *        Button callbacks will receive the dialog element as context.
		 *        Pressing any buttons in the dialog will automatically close the dialog.
		 * @return
		 *        A function that can be called to close the dialog.
		 */
		'confirm': function (props) {
			var buttons = props.buttons || {};

			var yesLabel = i18n.t('button.yes.label');
			var noLabel = i18n.t('button.no.label');

			// block adds backwards compatibility to still be able to use
			// 'buttons.Yes/No' for setting functionality of basic buttons
			if (buttons.Yes !== null && yesLabel !== 'Yes') {
				buttons[yesLabel] = buttons.Yes;
				delete buttons.Yes;
			}
			if (buttons.No !== null && noLabel !== 'No') {
				buttons[noLabel] = buttons.No;
				delete buttons.No;
			}

			buttons[yesLabel] = buttons[yesLabel] || props.yes || $.noop;
			buttons[noLabel]  = buttons[noLabel]  || props.no  || $.noop;

			if (props.answer) {
				var yes = buttons[yesLabel];
				var no  = buttons[noLabel];
				buttons[yesLabel] = function () {
					yes();
					props.answer(true);
				};
				buttons[noLabel] = function () {
					no();
					props.answer(false);
				};
			}
			var dialog = makeDialogDiv(props).dialog(
				$.extend(makeDialogProps(props, 'Confirm'), {
					'buttons': wrapDialogButtons(buttons)
				})
			);
			return function () {
				dialog.dialog('destroy').remove();
			};
		},

		/**
		 * Shows an alert dialog.
		 *
		 * An alert dialog has an alert icon and style and a dismiss button.
		 *
		 * @param props is an object with the following properties (all optional)
		 *        title - the title of the dialog
		 *        text - either the text inside the dialog
		 *        html - or the html inside the dialog
		 *        cls - the root element of the dialog will receive this class
		 * @return
		 *        A function that can be called to close the dialog.
		 */
		'alert': function (props) {
			var propsExtended = {};
			propsExtended[i18n.t('button.dismiss.label')] = $.noop;
			var dialog = makeDialogDiv(props).dialog(
				$.extend(makeDialogProps(props, 'Alert'), {
					'buttons': wrapDialogButtons(propsExtended)
				})
			);
			return function () {
				dialog.dialog('destroy').remove();
			};
		},

		/**
		 * Shows a stripped down modal dialog that can be customized.
		 *
		 * @param {object} props Properties with which to customize the
		 *                       modal.  All properties of
		 *                       api.jqueryui.com/dialog apply, in addition to
		 *                       the following optional properties:
		 *                       html - HTML contents to be placed inside the
		 *                              modal.
		 *                        cls - Custom class to be given to the modal's
		 *                              root element.
		 * @return {jQuery.<HTMLElement>} jQuery object containing the dialog
		 *                                DOM element.
		 */
		'modal': function (props) {
			var $dialog = makeDialogDiv(props).dialog(
				$.extend(makeDialogProps(props, ''), props)
			);
			$dialog.parent().find('.ui-dialog-titlebar').remove();
			return $dialog;
		},

		/**
		 * Shows a progress dialog.
		 *
		 * A progress dialog shows a progressbar and a message to
		 * indicate that some process is in progress.
		 *
		 * @param props is an object with the following properties (all optional)
		 *        title - the title of the dialog
		 *         text - either the text inside the dialog
		 *         html - or the html inside the dialog
		 *          cls - the root element of the dialog will receive this class
		 *        value - the intial value of the progressbar from 0 to 100
		 * @return
		 *        A function that can be called to update the progress bar with a value from 0 to 100.
		 *        If null or undefined is passed, the dialog will be closed.
		 */
		'progress': function (props) {
			var progressbar = $("<div>").progressbar({
				// TODO if no initial value is specific, show a full but an animated progress bar instead
				value: null != props.value ? props.value : 100
			});
			var dialog = makeDialogDiv(props).dialog(
				$.extend(makeDialogProps(props, 'Progress'), {
					open: function () {
						$(this).append(progressbar);
					}
				})
			);
			return function (value) {
				if (null != value) {
					progressbar.progressbar({ value: value });
				} else {
					dialog.dialog('destroy').remove();
				}
			};
		}
	};
});

/*global define: true */
/**
 * The multiSplit component groups multiple buttons and other
 * button-like items into an expandable menu.
 */
define('ui/multiSplit',[
	'jquery',
	'ui/component',
	'ui/button',
	'ui/utils'
], function (
	$,
	Component,
	Button,
	Utils
) {
	

	/**
	 * MultiSplit component type.
	 * @class
	 * @api
	 * @extends {Component}
	 */
	var MultiSplit = Component.extend({

		_activeButton: null,
		_isOpen: false,

		/**
		 * @override
		 */
		init: function () {
			this._super();
			var multiSplit = this;
			var element = this.element = $('<div>', {
				'class': 'aloha-multisplit'
			});
			var content = this.contentElement = $('<div>', {
				'class': 'aloha-multisplit-content'
			}).appendTo(element);
			var toggle = this.toggleButton = Utils.makeButtonElement({
				'class': 'aloha-multisplit-toggle',
				click: function () {
					multiSplit.toggle();
				}
			}).uibutton().appendTo(element);

			this.buttons = [];

			var buttons = this.getButtons();

			if (0 === buttons.length) {
				element.hide();
				this.visible = false;
			}

			for (var i = 0; i < buttons.length; i++) {
				this.addButton(buttons[i]);
			}

			$('body').click(function (event) {
				if (multiSplit._isOpen &&
			        !multiSplit.element.is(event.target) &&
			        0 === multiSplit.element.find(event.target).length) {
					multiSplit.close();
				}
			});
		},

		/**
		 * Adds a button to this multisplit component.
		 *
		 * @param button
		 *        - name 
		 *          The name of the button.
		 *          Used by setActiveButton(name), show(name),
		 *          hide(name) to identify the correct button.
		 *
		 *        - tooltip
		 *        - icon
		 *          The tooltip and icon. 
		 *
		 *        - wide
		 *          A boolean indicating whether the button should
		 *          extend the entire width of the multi split button.
		 *
		 *        - init
		 *          An optional init function. Will be invoked with the
		 *          button component instance as context.
		 *
		 *        - click
		 *          A click handler for the button.
		 * @api
		 */
		addButton: function (props) {
			var multiSplit = this;

			var component = new (Button.extend({
				tooltip: props.tooltip,
				icon: props.wide ? props.icon : 'aloha-large-icon ' + props.icon,
				iconOnly: props.wide ? false : true,
				init: function () {
					this._super();
					if (props.init) {
						props.init.call(this);
					}
				},
				click: function () {
					props.click.apply(multiSplit, arguments);
					multiSplit.close();
				}
			}))();
			if (!props.wide) {
				component.element.addClass('aloha-large-button');
			}

			multiSplit.buttons[props.name] = {
				settings: props,
				component: component,
				element: component.element,
				visible: true
			};

			// Ensure non-wide buttons always come before wide buttons
			var fullwidthButtons = this.contentElement.children('.aloha-ui-multisplit-fullwidth');
			if (!props.wide && fullwidthButtons.length) {
				fullwidthButtons.before(component.element[0]);
			} else {
				this.contentElement.append(component.element[0]);
			}
		},

		/**
		 * @api
		 */
		setActiveButton: function (name) {
			if (!name) {
				name = null;
			}
			if (null !== this._activeButton) {
				this.buttons[this._activeButton]
				    .element.removeClass('aloha-multisplit-active');
			}
			this._activeButton = name;
			if (null !== name) {
				this.buttons[name]
				    .element.addClass('aloha-multisplit-active');
			}
		},

		/**
		 * Toggles the multisplit menu
		 * @api
		 */
		toggle: function () {
			this.element.toggleClass('aloha-multisplit-open');
			this._isOpen = !this._isOpen;
		},

		/**
		 * Opens the multisplit menu
		 * @api
		 */
		open: function () {
			this.element.addClass('aloha-multisplit-open');
			this._isOpen = true;
		},

		/**
		 * Closes the multisplit menu
		 * @api
		 */
		close: function () {
			this.element.removeClass('aloha-multisplit-open');
			this._isOpen = false;
		},

		/**
		 * Show the button with given index
		 * @api
		 * @param {Number} index button index
		 */
		show: function (name) {
			if (!name) {
				name = null;
			}
			if (null !== name && this.buttons[name] !== undefined) {
				this.buttons[name].element.show();
				this.buttons[name].visible = true;
				// since we show at least one button now, we need to show the multisplit button
				this.element.show();
				this.visible = true;
			}
		},

		/**
		 * Hide the button with given index
		 * @api
		 * @param {Number} index button index
		 */
		hide: function (name) {
			var button, visible = false;

			if (!name) {
				name = null;
			}
			if (null !== name && this.buttons[name] !== undefined) {
				this.buttons[name].element.hide();
				this.buttons[name].visible = false;

				// now check, if there is a visible button
				for (button in this.buttons) {
					if (this.buttons.hasOwnProperty(button)) {
						if (this.buttons[button].visible) {
							this.element.show();
							this.visible = true;
							visible = true;
							break;
						}
					}
				}

				if (!visible) {
					this.element.hide();
					this.visible = false;
				}
			}
		}
	});

	/**
	 * This module is part of the Aloha API.
	 * It is valid to override this module via requirejs to provide a
	 * custom behaviour. An overriding module must implement all API
	 * methods. Every member must have an api annotation. No non-api
	 * members are allowed.
	 * @api
	 */
	return MultiSplit;
});

/*global define: true */
/**
 * This is a helper module for porting plugins from the old
 * ui-attributefield.js in the aloha core to the new ui-plugin.
 * This interface is obsolete and must not be used for new implementations.
 */
define('ui/port-helper-multi-split',[
	'aloha/core',
	'jquery',
	'ui/ui',
	'ui/multiSplit'
], function (
	Aloha,
	jQuery,
	Ui,
	MultiSplit
) {
	

	function MultiSplitButton(props) {
		var multiSplit;

		multiSplit = Ui.adopt(props.name, MultiSplit, {
			scope: props.scope,
			getButtons: function () {
				var buttons = [];
				jQuery.each(props.items, function (i, item) {
					buttons.push({
						tooltip: item.tooltip,
						text: item.text,
						name: item.name,
						icon: item.iconClass,
						click: item.click,
						init: function () {
							if (item.cls) {
								this.element.addClass(item.cls);
							}
							if (item.init) {
								item.init.call(this);
							}
						},
						wide: item.wide
					});
				});
				return buttons;
			}
		});

		return {
			// Expose this function so the cite-plugin can push its own
			// button to the format plugin's multi-split-button (which
			// is a disastrous hack I know).
			// TODO make it possible to combine the items of multiple
			// plugins into a single multi split button.
			pushItem: function (item) {
				multiSplit.addButton(item);
			},
			showItem: function (name) {
				multiSplit.show(name);
			},
			hideItem: function (name) {
				multiSplit.hide(name);
			},
			setActiveItem: function (name) {
				multiSplit.setActiveButton(name);
			}
		};
	}

	return MultiSplitButton;
});

define('table/nls/i18n',{
	"root":  {
		"floatingmenu.tab.table": "Table",
		"floatingmenu.tab.tablelayout": "Table Layout",
		"deleterows.confirm": "Do you really want to delete the selected rows?",
		"deletecolumns.confirm": "Do you really want to delete the selected columns?",
		"deletetable.confirm": "Do you really want to delete the table?",
		"Table": "Table",
		"button.createtable.tooltip": "Insert Table",
		"button.addcolleft.tooltip": "Add Column left",
		"button.addcolright.tooltip": "Add Column right",
		"button.delcols.tooltip": "Delete Columns",
		"button.addrowbefore.tooltip": "Add Row before",
		"button.addrowafter.tooltip": "Add Row after",
		"button.delrows.tooltip": "Delete Rows",
		"button.caption.tooltip": "Table caption",
		"button.naturalfit.tooltip": "Reset table sizing",
		"empty.caption": "Table caption",
		"button.removeFormat.tooltip": "Remove formatting",
		"button.removeFormat.text": "Remove formatting",
		"button.rowheader.tooltip": "Format row as table header",
		"button.columnheader.tooltip": "Format column as table header",
		"button.mergecells.tooltip": "Merge Cells",
		"button.splitcells.tooltip": "Split Cells",
		"table.label.target": "Summary",
		"table.sidebar.title": "Table",
		"table.mergeCells.notRectangular": "Only a rectangular selection can be merged",
		"table.addColumns.nonConsecutive": "Please select a single column or a consecutive range of columns",
		"table.createTable.nestedTablesNoSupported": "Sorry, nested tables are not supported"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

define('aloha/nls/i18n',{
	"root":  {
		"plugin.abbr.floatingmenu.tab.abbr": "Abbreviation",
		"floatingmenu.tab.format": "Format",
		"floatingmenu.tab.insert": "Insert",
		"yes": "Yes",
		"no": "No",
		"cancel": "Cancel",
		"repository.no_item_found": "No item found.",
		"repository.loading": "Loading",
		"repository.no_items_found_yet": "No items found yet..."
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

define('table/table-create-layer',
['jquery'],
function (jQuery) {
	/**
	 * Initialize of the CreateLayer object
	 */
	CreateLayer = function(TablePlugin){
		this.TablePlugin = TablePlugin;
	};

	/**
	 * Internal configuration of the create-table panel
	 */
	CreateLayer.prototype.parameters = {
		elemId: 'aloha-table-createLayer', // id of the create-table panel
		className: 'aloha-table-createdialog',   // class-name of the create-table panel
		numX: 10,	         // Number of cols in the create-layer
		numY: 10,            // Number of rows in the create-layer vertically
		layer: undefined,    // Attribute holding the create-layer
		target: undefined    // the clicktarget which was clicked on (mostly the button of the floatingmenu)
	};

	/**
	 * The configuration-object for the implementer of the plugin. All keys of
	 * the "parameters" object could be overwritten within this object and will
	 * simply be used instead.
	 */
	CreateLayer.prototype.config = new Object();

	/**
	 * Flag wether the CreateLayer is currently visble or not
	 */
	CreateLayer.prototype.visible = false;

	/**
	 * This function checks if there is an create-table-layer. If no layer exists, it creates one and puts it into the configuration.
	 * If the layer was already created it sets the position of the panel and shows it.
	 *
	 * @return void
	 */
	CreateLayer.prototype.show = function(){
		var layer = this.get('layer');

		// create the panel if the layer doesn't exist
		if (layer == null) {
			this.create();
		}else {
			// or reposition, cleanup and show the layer
			this.setPosition(layer);
			layer.find('td').removeClass('hover');
			layer.show();
		}
		this.visible = true;
	};

	/**
	 * Creates the div-layer which holds a table with the given number of rows and cols.
	 * It sets click and mouseover-events to the table data fields
	 *
	 * @return void
	 */
	CreateLayer.prototype.create = function () {
		var that = this;
		var layer = jQuery('<div></div>');
		layer.id = this.get('elemId');
		layer.addClass(this.get('className'));

		var table = jQuery('<table></table>');
		table.css('width', (this.get('numX') + 6) * 15);
		var tr;
		var td;

		for (var i = 0; i < this.get('numY'); i++) {
			tr = jQuery('<tr></tr>');

			for (var j = 0; j < this.get('numX'); j++) {
				td = jQuery('<td>\u00a0</td>');

				if (i == 0 && j == 0) {
					td.addClass('hover');
				}

				td.bind('mouseover', {rowId: i, colId: j}, function(e) {
					that.handleMouseOver(e, table);
				});

				td.bind('click', {rowId: i, colId: j}, function(e){
					var rows = e.data.rowId + 1;
					var cols = e.data.colId + 1;

					that.TablePlugin.createTable(cols, rows);
					that.hide();
				});

				tr.append(td);
			}
			table.append(tr);
		}
		layer.append(table);

		// set attributes
		this.set('layer', layer);
		this.setPosition();

		// stop bubbling the click on the create-dialog up to the body event
		layer.bind('click', function(e) {
			e.stopPropagation();
		}).mousedown(function(e) {
			e.stopPropagation();
		});

		// append layer to body and
		// hide the create layer if user clicks anywhere in the body
		jQuery('#neos-application').append(layer).bind('click', function(e) {
			// If the layer is visible and the event target is not the
			// button itself or a descendant of the button, hide the
			// layer.
			if (that.visible && !(e.target === that.get('target')[0] || jQuery.contains(that.get('target')[0], e.target))) {
				that.hide();
			}
		});
	};

	/**
	 * handles the mose over state for a cell
	 * @param e event object
	 * @param table the aeffected table
	 * @return void
	 */
	CreateLayer.prototype.handleMouseOver = function(e, table) {
		var rowId = e.data.rowId;
		var colId = e.data.colId;
		var innerRows = table.find('tr');

		for (var n = 0; n <= innerRows.length; n++) {
			var innerCells = jQuery(innerRows[n]).find('td');

			for (var k = 0; k <= innerCells.length; k++) {
				if (n <= rowId && k <= colId) {
					jQuery(innerCells[k]).addClass('hover');
				} else {
					jQuery(innerCells[k]).removeClass('hover');
				}
			}
		}
	};

	/**
	 * Sets the "left" and "top" style-attributes according to the clicked target-button
	 *
	 *  @return void
	 */
	CreateLayer.prototype.setPosition = function() {
		var targetObj = jQuery(this.get('target'));
		var pos = targetObj.offset();
		this.get('layer').css('left', pos.left + 'px');
		this.get('layer').css('top', (pos.top + targetObj.height()) + 'px');
	};

	/**
	 * Hides the create-table panel width the jQuery-method hide()
	 *
	 * @see jQuery().hide()
	 * @return void
	 */
	CreateLayer.prototype.hide = function() {
		this.get('layer').hide();
		this.visible = false;
	};

	/**
	 * The "get"-method returns the value of the given key. First it searches in the
	 * config for the property. If there is no property with the given name in the
	 * "config"-object it returns the entry associated with in the parameters-object
	 *
	 * @param property
	 * @return void
	 */
	CreateLayer.prototype.get = function(property) {
		// return param from the config
		if (this.config[property]) {
			return this.config[property];
		}
		// if config-param was not found return param from the parameters-object
		if (this.parameters[property]) {
			return this.parameters[property];
		}
		return undefined;
	};

	/**
	 * The "set"-method takes a key and a value. It checks if there is a key-value
	 * pair in the config-object. If so it saves the data in the config-object. If
	 * not it saves the data in the parameters-object.
	 *
	 * @param key
	 *            the key which should be set
	 * @param value
	 *            the value which should be set for the associated key
	 */
	CreateLayer.prototype.set = function (key, value) {
		// if the key already exists in the config-object, set it to the config-object
		if (this.config[key]) {
			this.config[key] = value;

			// otherwise "add" it to the parameters-object
		}else{
			this.parameters[key] = value;
		}
	};

	return CreateLayer;
});

define('table/table-plugin-utils',[
	'jquery',
	'aloha/copypaste',
	'util/browser',
	'aloha/console'
], function (
	$,
	CopyPaste,
	Browser,
	Console
) {
	

	function getAnchorCell(cells) {
		if (0 === cells.length) {
			return null;
		}

		var i;
		var editable;
		var range = CopyPaste.getRange();

		if (range) {
			editable = $(
				range.commonAncestorContainer
			).closest('.aloha-table-cell-editable')[0];
		}

		if (editable) {
			for (i = 0; i < cells.length; i++) {
				if ($(cells[i]).find(editable).length) {
					return cells[i];
				}
			}
		}

		return cells[0];
	}

	var Utils = {
		/**
		 * Translates the DOM-Element column offset of a table-cell to the
		 * column offset of a grid-cell, which is the column index adjusted
		 * by other cells' rowspan and colspan values.
		 *
		 * @param rows the rows of a table as an array or jQuery object
		 * @param rowIdx the index in rows of the cell to get the grid column index of
		 * @param colIdx the index in rows[row].cells of the cell to get the grid column index of
		 * @return the grid column index of the cell at the given rowIdx and colIdx, or null
		 *   if the given rowIdx and colIdx coordinates point to a cell outside of the table.
		 */
		'cellIndexToGridColumn': function (rows, rowIdx, colIdx) {
			var gridColumn = null;
			Utils.walkCells(rows, function(ri, ci, walkedGridColumn, rowspan, colspan) {
				if (ri === rowIdx && ci === colIdx) {
					gridColumn = walkedGridColumn;
					return false;
				}
			});
			return gridColumn;
		},
		/**
		 * Walks the table-cells of the table represented by the given rows,
		 * invoking the given callback for each.
		 * @param callback will receive the following arguments
		 *   o ri the row index of the table-cell
		 *   o ci the column index of the table-cell as the offset of the DOM-Element
		 *   o gridCi the column index of the table-cell in a virtual grid that overlays the table (see makeGrid())
		 *   o colspan the colspan attribute of the table-cell (as a number)
		 *   o rowspan the rowspan attribute of the table-cell (as a number)
		 *   returning false from the callback will terminate the walk early.
		 * @return void
		 */
		'walkCells': function (rows, callback) {
			var adjust = [];
			for (var ri = 0; ri < rows.length; ri++) {
				var cells = rows[ri].cells;
				var skip = 0;
				for (var ci = 0; ci < cells.length; ci++) {
					var cell = cells[ci];
					var colspan = Utils.colspan(cell);
					var rowspan = Utils.rowspan(cell);

					while (adjust[ci + skip]) {
						adjust[ci + skip] -= 1;
						skip += 1;
					}

					if (false === callback(ri, ci, ci + skip, colspan, rowspan)) {
						return;
					}

					for (var i = 0; i < colspan; i++) {
						if (adjust[ci + skip + i] ) {
							throw "an impossible case has occurred";
						}
						adjust[ci + skip + i] = rowspan - 1;
					}
					skip += colspan - 1;
				}
				for (; ci + skip < adjust.length; skip++) {
					if (adjust[ci + skip]) {
						adjust[ci + skip] -= 1;
					}
				}
			}
		},
		/**
		 * Makes a grid out of the table represented by the given rows.  A
		 * grid will contain one or multiple grid-cells for each table-cell.
		 * A table-cell that has a colspan or rowspan greater than one will
		 * be represented by multiple cells (colspan*rowspan) in the
		 * grid.
		 * @parm rows either an array or an jQuery object holding the DOM
		 *   rows of the table.
		 * @return the table translated to a grid of the form
		 *   [ [cell11, cell12, cell13, ...],
		 *     [cell21, cell22, cell23, ...],
		 *     ... ]
		 *  where each grid-cell is an object containing the following members:
		 *  cell: the DOM object in the table that is rendered into the grid-cell
		 *  colspan: the colspan attribute of the DOM object (as a number)
		 *  rowspan: the rowspan attribute of the DOM object (as a number)
		 *  spannedX: the row offset of the grid-cell in the table-cell (0 based)
		 *  spannedY: the column offset of the grid-cll in the table-cell (0 based)
		 */
		'makeGrid': function (rows) {
			var grid = [];
			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				var cell = rows[ri].cells[ci];
				for (var spannedY = 0; spannedY < rowspan; spannedY++) {
					grid[ri + spannedY] = grid[ri + spannedY] || [];
					for (var spannedX = 0; spannedX < colspan; spannedX++) {
						grid[ri + spannedY][gridCi + spannedX] = {
							'cell'    : cell,
							'colspan' : colspan,
							'rowspan' : rowspan,
							'spannedX': spannedX,
							'spannedY': spannedY
						};
					}
				}
			});
			return grid;
		},
		/**
		 * A grid-cell is said to contain a dom-cell if it is located in the
		 * upper-left corner of a table-cell. A table-cell may have a
		 * rowspan and colspan, and as such may be comprised of several
		 * grid-cells, of which only one (the upper-left corner one)
		 * contains a dom-cell.
		 * @param cellInfo a cell in the grid returned by makeGrid()
		 * @return whether the given grid-cell maps to a dom-cell
		 */
		'containsDomCell': function (cellInfo) {
			return 0 === cellInfo.spannedX && 0 === cellInfo.spannedY;
		},
		/**
		 * A grid-cell may not contain a dom-cell (due to rowspan and
		 * colspan). If this function is given the coordinates of such a
		 * grid-cell, it will look to the left of the grid-cell, until it
		 * finds a grid-cell that contains a dom-cell and returns the
		 * DOM-Element of it.
		 *
		 * This function is useful to insert something into the DOM next to
		 * or in place of the grid-cell.
		 *
		 * @param grid the grid of the table (see makeGrid())
		 * @param ri the row index into the grid
		 * @param ci the column index into the grid
		 * @return the DOM-Element either at or to the left of the grid-cell
		 *   a the given coordinates.
		 */
		'leftDomCell': function (grid, ri, gridCi) {
			do {
				var cellInfo = grid[ri][gridCi];
				if ( 0 === cellInfo.spannedY ) {
					return cellInfo.cell;
				}
				gridCi -= cellInfo.spannedX + 1;
			} while (gridCi >= 0);
			return null;
		},
		/**
		 * Given a cell of a table (td/th) with a colspan or rowspan
		 * greater than one, will set the rowspan and colspan of the
		 * cell to one and insert empty cells where the original cell
		 * spanned (the number of cells allocated with createCell will
		 * be rowspan * colspan - 1).
		 *
		 * @param cell
		 *        the cell to split
		 * @param createCell
 		 *        a callback that will be invoked rowspan * colspan - 1
		 *        times, and which must return a table cell (td/th) that
		 *        will be inserted into the table
		 */
		'splitCell': function (cell, createCell) {
			var $cell = $(cell);
			var colspan = Utils.colspan( cell );
			var rowspan = Utils.rowspan( cell );

			//catch the most common case early
			if (1 === colspan && 1 === rowspan) {
				return;
			}

			var $row  = $cell.parent();
			var $rows = $row.parent().children();
			var rowIdx = $row.index();
			var colIdx = $cell.index();
			var grid = Utils.makeGrid($rows);
			var gridColumn = Utils.cellIndexToGridColumn($rows, rowIdx, colIdx);
			for (var i = 0; i < rowspan; i++) {
				for (var j = (0 === i ? 1 : 0); j < colspan; j++) {
					var leftCell = Utils.leftDomCell(grid, rowIdx + i, gridColumn);
					if (null == leftCell) {
						$rows.eq(rowIdx + i).prepend(createCell());
					} else {
						$( leftCell ).after(createCell());
					}
				}
			}
			// Note that attribute case (colSpan instead of colspan) matters on IE7.
			$cell.removeAttr('colSpan');
			$cell.removeAttr('rowSpan');
		},
		/**
		 * @param cell
		 *        the DOM node for a table cell (td/th)
		 * @return
		 *        a numeric value indicating the number of rows the cell spans
		 */
		'rowspan': function (cell) {
			return parseInt( $( cell ).attr('rowspan') ) || 1;
		},
		/**
		 * @param cell
		 *        the DOM node for a table cell (td/th)
		 * @return
		 *        a numeric value indicating the number of columns the cell spans
		 */
		'colspan': function (cell) {
			return parseInt( $( cell ).attr('colspan') ) || 1;
		},
		/**
		 * Calls the given callback with each object in the given
		 * two-dimensional array.
		 *
		 * @param grid
		 *        A two-dimensional array.
		 * @param callback
		 *        Invoked with each item in the given two-dimensional array.
		 *        Accepts the following parameters:
		 *        o item an item in the given two-dimensional array
		 *        o x the offset in the nested array (horizontal axis)
		 *        o y the offset in the outer array (veritcal axis)
		 *        If the callback returns a value identical to false,
		 *        the walk will be aborted early.
		 */
		'walkGrid': function(grid, callback) {
			var	row;
			for (var i = 0, gridLength = grid.length; i < gridLength; i++ ) {
				row = grid[i];
				for (var j = 0, rowLength = row.length; j < rowLength; j++ ) {
					if ( false === callback( row[ j ], j, i ) ) {
						return;
					}
				}
			}
		},
		/**
		 * Walks the cells of the given grid inside the given
		 * coordinates.
		 *
		 * @param {array} grid
		 *        A two-dimensional array.
		 * @param {object} rect
		 *        Must have the properties top, left, bottom, right
		 *        each of which referring to a coordinate in the given grid.
		 * @param {function} callback
		 *        A callback to invoke for each item in the given
		 *        two-dimensional array. See walkGrid() for the
		 *        specification of this parameter.
		 */
		'walkGridInsideRect': function ( grid, rect, callback ) {
			Utils.walkGrid( grid, function ( cellInfo, x, y ) {
				if ( y >= rect.top && y < rect.bottom && x >= rect.left && x < rect.right ) {
					return callback( cellInfo, x, y );
				}
			});
		},
		/**
		 * Slices leading null or undefined items off of an array
		 *
		 * @param array
		 *        the array to slice null or undefined items off from
		 * @return
		 *        a new array with the remaining items
		 */
		'leftTrimArray': function ( array ) {
			for (var i = 0; i < array.length; i++) {
				if ( null != array[i] ) {
					return array.slice( i, array.length );
				}
			}
			return [];
		},
		/**
		 * Given a two-dimensional array, will determine the smallest
		 * possible contour that contains all items for which
		 * hasCountour returns true.
		 *
		 * @param grid
		 *        A two-dimensional array
		 * @param hasContour
		 *        Invoked with each item in the given two dimensional array.
		 *        Accepts the following parameters:
		 *        o item an item in the given two-dimensional array
		 *        o x the offset in the nested array (horizontal axis)
		 *        o y the offset in the outer array (veritcal axis)
		 *        Returns a boolean value indicating whether the item is
		 *        considered to have a contour.
		 * @return
		 *        A set of arrays that indicate a contour
		 *        top:    an array of the smallest vertical offsets
		 *        right:  an array of the greatest horizontal offsets
		 *        bottom: an array of the greatest vertical offsets
		 *        left:   an array of the smallest horizontal offsets
		 */
		'makeContour': function ( grid, hasContour ) {
			var left = [];
			var right = [];
			var top = [];
			var bottom = [];
			Utils.walkGrid( grid, function ( item, x, y ) {
				if ( hasContour( item, x, y ) ) {
					if ( null == left[ y ] || x < left[ y ] ) {
						left[ y ] = x;
					}
					if ( null == right[ y ] || x > right[ y ] ) {
						right[ y ] = x;
					}
					if ( null == top[ x ] || y < top[ x ] ) {
						top[ x ] = y;
					}
					if ( null == bottom[ x ] || y > bottom[ x ] ) {
						bottom[ x ] = y;
					}
				}
			});
			left   = Utils.leftTrimArray(left);
			right  = Utils.leftTrimArray(right);
			top    = Utils.leftTrimArray(top);
			bottom = Utils.leftTrimArray(bottom);
			return {'left': left, 'right': right, 'top': top, 'bottom': bottom};
		},
		/**
		 * Returns the index of the first item that doesn't match the given value
		 *
		 * @param array
		 *        An array that contains arbitrary items
		 * @param but
		 *        A value to ignore while searching in the given array
		 * @return
		 *        The offset of the first item in the given array that doesn't match the given value.
		 *        If no such item was found, -1 is returned.
		 */
		'indexOfAnyBut': function ( array, but ) {
			for ( var i = 0; i < array.length; i++ ) {
				if ( but !== array[ i ] ) {
					return i;
				}
			}
			return -1;
		},
		/**
		 * @param array
		 *        an array of integers
		 * @return
		 *        true if each item in the given array has a
		 *        difference to its neighbor of exactly 1
		 */
		'isConsecutive': function ( array ) {
			for ( var i = 1; i < array.length; i++ ) {
				if ( 1 !== Math.abs( array[ i ] - array[ i - 1 ] ) ) {
					return false;
				}
			}
			return true;
		},

		/**
		 * resizes the width of the given cell
		 *
		 * @param {DOM element} cell
		 *        the DOM node for a table cell (td/th)
		 * @param {number | string} width
		 *        an integer value indicating the desired width
		 */

		'resizeCellWidth': function(cell, width) {
			$( cell ).css('width', width);
			$( cell ).find('.aloha-table-cell-editable').eq(0).css({
				'width': width,
				'word-wrap': 'break-word'
			});
		},

		/**
		 * Get the minimum column width based on the word size
		 *
		 * @param cell
		 *        the DOM node for a table cell (td/th)
		 *
		 * @return
		 * 				the minimum width as an integer value
		 */
		'getMinColWidth': function(cell) {
			var rows = cell.closest( 'tbody' ).children( 'tr' );
			var cellRow = cell.closest( 'tr' );
			var gridId = Utils.cellIndexToGridColumn( rows,
																							  rows.index( cellRow ),
																							  cellRow.children().index( cell )
																						  );

			var largestWord = "";

			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				if (gridCi === gridId) {
					var curCell = $( $( rows[ri] ).children()[ ci ] );

					// skip the cells with a colspan
					if (colspan > 1) {
						return;
					}

					var cellWords = curCell.text().split(" ");

					// pick the largest word in the cell
					for ( var j = 0; j < cellWords.length; j++ ) {
						if ( cellWords[j].length > largestWord.length ) {
							largestWord = cellWords[j];
						}
					}
				}
			});

			var fakeCell = $("<span></span>");
			fakeCell.css( 'text-indent', -9999 );
			fakeCell.css( 'display', 'inline' );
			fakeCell.text( largestWord );

			$( cell ).append( fakeCell );

			var width = fakeCell.width();

			$( fakeCell ).remove();

			return width;
		},

		/**
		 * Get the maximum and minimum width a cell can be resized.
		 *
		 * @param gridId
		 * 			id of the cell based on the virtual grid
		 * @param rows
		 * 			Collection of rows as DOM objects
		 * @param callback
		 * 			a callback function
		 *
		 * @return void
		 */
		'getCellResizeBoundaries': function(gridId, rows, callback) {
			var maxPageX, minPageX;

			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				var currentCell = $( $( rows[ri] ).children()[ ci ] );

				// skip the select cells
				if ( currentCell.hasClass( 'aloha-table-selectrow' ) || currentCell.closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ) ) {
					return true;
				};

				if (gridCi === gridId && colspan === 1) {
					maxPageX = currentCell.offset().left + Utils.getCellBorder(currentCell) + currentCell.width() - Utils.getMinColWidth( currentCell );
				}

				if (gridCi === gridId - 1 && colspan === 1) {
					minPageX = currentCell.offset().left + Utils.getCellBorder(currentCell) + Utils.getCellPadding(currentCell) + Utils.getMinColWidth( currentCell );
				}

				// if both max page x and min page x is set,
				// stop walking over the cells.
				if ( maxPageX && minPageX ) {
					callback(maxPageX, minPageX);
					return false;
				}
			});
		},

		/**
		 * Get the border width of the cell
		 *
		 * @param cell
		 *        the DOM node for a table cell (td/th)
		 *
		 * @return
		 * 				the border width as an integer value
		 */
		'getCellBorder': function(cell) {
			return ( (cell.outerWidth() - cell.innerWidth()) / 2 );
		},

		/**
		 * Get the padding of the cell
		 *
		 * @param cell
		 *        the DOM node for a table cell (td/th)
		 *
		 * @return
		 * 				the padding as an integer value
		 */
		'getCellPadding': function(cell) {
			return ( cell.innerWidth() - cell.width() );
		},

		selectAnchorContents: function(selection) {
			var anchor = getAnchorCell(selection);
			if (anchor) {
				var element = $('>.aloha-table-cell-editable', anchor)[0];
				if (Browser.ie && anchor.ownerDocument.documentMode <= 8) {
					try {
						CopyPaste.selectAllOf(element);
					} catch (e) {
						Console.warn('Table Plugin', e.message);
					}
				} else {
					CopyPaste.selectAllOf(element);
				}
			}
		}

	};

	return Utils;
});

define('table/table-cell',[
	'aloha',
	'aloha/jquery',
	'aloha/ephemera',
	'table/table-plugin-utils',
	'util/browser',
	'util/misc',
	'util/dom'
], function (
	Aloha,
	jQuery,
	Ephemera,
	Utils,
	Browser,
	Misc,
	Dom
) {
	/**
	 * Constructs a TableCell.
	 *
	 * @param {DomNode} cell
	 *        A td/th which will be represente by this TableCell.
	 * @param {Table} tableObj
	 *        The Table which contains the cell. The cell will be
	 *        activated/dactivated with the table.
	 */
	var TableCell = function (originalTd, tableObj) {
		if (null == originalTd) {
			originalTd = '<td></td>';
		}

		//original Td must be a DOM node so that the this.obj.context property is available
		//this transformation will properly handle jQuery objects as well as DOM nodes
		originalTd = jQuery(originalTd).get(0);

		this.obj = jQuery(originalTd);
		this.tableObj = tableObj;

		tableObj.cells.push(this);
	};

	/**
	 * Reference to the jQuery-representation of the wrapping table
	 *
	 * @see TableCell.table
	 */
	TableCell.prototype.tableObj = undefined;

	/**
	 * Reference to the jQuery td-Object of the cell
	 */
	TableCell.prototype.obj = undefined;

	/**
	 * The jQuery wrapper of the cell
	 */
	TableCell.prototype.wrapper = undefined;

	/**
	 * Flag if the cell has focus
	 */
	TableCell.prototype.hasFocus = false;

	TableCell.prototype.activate = function () {
		var cell = this;
		var $elem = cell.obj;

		// wrap the created div into the contents of the cell
		$elem.wrapInner('<div/>');

		// create the editable wrapper for the cells
		var $wrapper = $elem.children('div').eq(0);
		$wrapper.contentEditable(true);
		$wrapper.addClass('aloha-table-cell-editable');

		// mark the editable wrapper as ephemeral
		Ephemera.markWrapper($wrapper);

		// attach events to the editable div-object
		$wrapper.on('focus', function ($event) {
			// activate the button for splitting cells if the clicked cell has an active row- or colspan
			if (Utils.colspan(cell.obj) > 1 || Utils.rowspan(cell.obj) > 1) {
				cell.tableObj.tablePlugin._splitcellsButton.enable(true);
			} else {
				cell.tableObj.tablePlugin._splitcellsButton.enable(false);
			}

			// ugly workaround for ext-js-adapter problem in
			// ext-jquery-adapter-debug.js:1020
			if ($event.currentTarget) {
				$event.currentTarget.indexOf = function () {
					return -1;
				};
			}
			cell._editableFocus($event);
		});

		$wrapper.on('mousedown', function ($event) {
			// ugly workaround for ext-js-adapter problem in ext-jquery-adapter-debug.js:1020
			if ($event.currentTarget) {
				$event.currentTarget.indexOf = function () {
					return -1;
				};
			}

			// prevent cell selection, if mousedown was on a block handle
			if (jQuery($event.target).hasClass('aloha-block-draghandle')) {
				return;
			}

			cell._editableMouseDown($event);

			cell.tableObj.selection.baseCellPosition = [cell._virtualY(), cell._virtualX()];

			if ($event.shiftKey) {
				// shift-click to select a coherent cell range
				//
				// in IE it's not possible to select multiple cells when you "select+drag" over other cells
				// click into the first cell and then "shift-click" into the last cell of the coherent cell range you want to select
				var right = cell.tableObj.selection.lastBaseCellPosition[1];
				var bottom = cell.tableObj.selection.lastBaseCellPosition[0];
				var topLeft = cell.tableObj.selection.baseCellPosition;
				var left = topLeft[1];
				if (left > right) {
					left = right;
					right = topLeft[1];
				}
				var top = topLeft[0];
				if (top > bottom) {
					top = bottom;
					bottom = topLeft[0];
				}
				var rect = {
					"top": top,
					"right": right,
					"bottom": bottom,
					"left": left
				};

				var table = cell.tableObj;
				var $rows = table.obj.children().children('tr');
				var grid = Utils.makeGrid($rows);

				table.selection.selectedCells = [];
				table.selection.currentRectangle = rect;
				var selectClass = table.get('classCellSelected');
				Utils.walkGrid(grid, function (cellInfo, j, i) {
					if (Utils.containsDomCell(cellInfo)) {
						if (i >= rect.top && i <= rect.bottom && j >= rect.left && j <= rect.right) {
							jQuery(cellInfo.cell).addClass(selectClass);
							table.selection.selectedCells.push(cellInfo.cell);
						} else {
							jQuery(cellInfo.cell).removeClass(selectClass);
						}
					}
				});

				table.selection.notifyCellsSelected();
			} else {
				cell.tableObj.selection.lastBaseCellPosition = cell.tableObj.selection.baseCellPosition;
				cell._editableMouseDown($event);
				cell._startCellSelection();
			}
		});

		$wrapper.on('blur', function ($event) {
			cell._editableBlur($event);
		});
		$wrapper.on('keyup', function ($event) {
			cell._editableKeyUp($event);
		});
		$wrapper.on('keydown', function ($event) {
			cell._editableKeyDown($event);
		});
		$wrapper.on('mouseover', function ($event) {
			cell._selectCellRange();
		});
		$elem.on('mouseover', function ($event) {
			cell._selectCellRange();
		});

		// we will treat the wrapper just like an editable
		$wrapper.contentEditableSelectionChange(function ($event) {
			Aloha.Selection.onChange($wrapper, $event);
			return $wrapper;
		});

		$elem.on('mousedown', function ($event) {
			// prevent cell selection, if mousedown was on a block handle
			if (jQuery($event.target).hasClass('aloha-block-draghandle')) {
				return;
			}

			// when clicked on something nested, prevent selection of whole cell
			if ($event.target != $elem[0]) {
				$event.stopPropagation();
				return;
			}

			window.setTimeout(function () {
				// Select the entire cell's content.
				cell.wrapper.trigger('focus');
				cell._selectAll($wrapper);
			}, 1);
			cell.tableObj.selection.baseCellPosition = [cell._virtualY(), cell._virtualX()];

			if (!cell.tableObj.selection.lastBaseCellPosition) {
				cell.tableObj.selection.lastBaseCellPosition = cell.tableObj.selection.baseCellPosition;
			}

			if ($event.shiftKey) {
				// shift-click to select a coherent cell range
				//
				// in IE it's not possible to select multiple cells when you "select+drag" over other cells
				// click into the first cell and then "shift-click" into the last cell of the coherent cell range you want to select
				var right = cell.tableObj.selection.lastBaseCellPosition[1];
				var bottom = cell.tableObj.selection.lastBaseCellPosition[0];
				var topLeft = cell.tableObj.selection.baseCellPosition;
				var left = topLeft[1];
				if (left > right) {
					left = right;
					right = topLeft[1];
				}
				var top = topLeft[0];
				if (top > bottom) {
					top = bottom;
					bottom = topLeft[0];
				}
				var rect = {
					"top": top,
					"right": right,
					"bottom": bottom,
					"left": left
				};

				var table = cell.tableObj;
				var $rows = table.obj.children().children('tr');
				var grid = Utils.makeGrid($rows);

				table.selection.selectedCells = [];
				var selectClass = table.get('classCellSelected');
				table.selection.currentRectangle = rect;
				Utils.walkGrid(grid, function (cellInfo, j, i) {
					if (Utils.containsDomCell(cellInfo)) {
						if (i >= rect.top && i <= rect.bottom && j >= rect.left && j <= rect.right) {
							jQuery(cellInfo.cell).addClass(selectClass);
							table.selection.selectedCells.push(cellInfo.cell);
						} else {
							jQuery(cellInfo.cell).removeClass(selectClass);
						}
					}
				});

				table.selection.notifyCellsSelected();
			} else {
				cell.tableObj.selection.lastBaseCellPosition = cell.tableObj.selection.baseCellPosition;
				cell._startCellSelection();
			}
			$event.stopPropagation();
		});

		if ($elem[0]) {
			$elem[0].onselectstart = function () {
				return false;
			};
		}

		$elem.on('mouseenter', function (evt) {
			Misc.addEditingHelpers($wrapper);
		});
		$elem.on('mouseleave', function (evt) {
			Misc.removeEditingHelpers($wrapper);
		});

		Aloha.bind('aloha-smart-content-changed', function (event, data) {
			if (data.editable.isActive && data.triggerType === 'block-change') {
				Misc.addEditingHelpers($wrapper);
			}
		});

		// set contenteditable wrapper div
		this.wrapper = $wrapper;
		if ($wrapper[0]) {
			var wrapper = $wrapper[0];

			wrapper.onselectstart = function () {
				window.event.cancelBubble = true;
			};
			// Disabled the dragging of content, since it makes cell selection
			// difficult.
			wrapper.ondragstart = function () {
				return false
			};
		}

		return this;
	};

	/**
	 * The deactivate method removes the contenteditable helper div within the
	 * table-data field and wraps the innerHtml to the outerHTML
	 *
	 * @return void
	 */
	TableCell.prototype.deactivate = function () {
		var wrapper = jQuery(this.obj.children('.aloha-table-cell-editable'));

		if (wrapper.length) {
			Misc.removeEditingHelpers(wrapper);

			// unwrap cell contents without re-creating dom nodes
			wrapper.parent().append(
				wrapper.contents()
			);

			// remove the contenteditable div and its attached events
			wrapper.remove();


			// remove the click event of the
			this.obj.unbind('click');
			this.obj.unbind('mousedown');
			this.obj.unbind('mouseenter');
			this.obj.unbind('mouseleave');
			this.obj.get(0).onselectstart = null;

			if (jQuery.trim(this.obj.attr('class')) == '') {
				this.obj.removeAttr('class');
			}
		}
	}

	/**
	 * Native toString-method
	 *
	 * @return string name of the namespace
	 */
	TableCell.prototype.toString = function () {
		return 'TableCell';
	};

	/**
	 * Focus method for the contentediable div within a table data-field. The method
	 * requires the event-property Cell as a Cell object. If the
	 * Cell wasn't activated yet it does all relevant actions to activate the cell.
	 *
	 * @param e
	 *            the jquery event object
	 * @return void
	 */
	TableCell.prototype._editableFocus = function (e) {
		// only do activation stuff if the cell don't has the focus
		if (!this.hasFocus) {
			// set an internal flag to focus the table
			this.tableObj.focus();

			// add an active-class
			this.obj.addClass('aloha-table-cell_active');

			// set the focus flag
			this.hasFocus = true;

			// unset the selection type
			this.tableObj.selection.selectionType = 'cell';

		}
	};

	/**
	 * Blur event for the contenteditable div within a table-data field. The method
	 * requires the event-property TableCell as a TableCell object. It
	 * sets the hasFocus flag of the cell to false and removes the "active"
	 * css-class.
	 *
	 * @param jqEvent
	 *            the jquery event object
	 * @return void
	 */
	TableCell.prototype._editableBlur = function (jqEvent) {

		// reset the focus of the cell
		this.hasFocus = false;

		// remove "active class"
		this.obj.removeClass('aloha-table-cell_active');

		// if the editable wrapper in the table cell only contains a single, empty
		// paragraph, we remove that paragraph
		if (this.wrapper.children().length === 1) {
			this.wrapper.find('p').filter(function (index) {
				var clone = jQuery(this).clone();
				// the last br in the paragraph does not count, so we remove
				// it before checking the paragraph for emptiness
				clone.find('br:last-child').remove();
				return Dom.isEmpty(clone[0]);
			}).remove();
		}
	};

	/**
	 * Gives the X (column no) for a cell, after adding colspans
	 */
	TableCell.prototype._virtualX = function () {
		var $rows = this.tableObj.obj.children().children('tr');
		var rowIdx = this.obj.parent().index();
		var colIdx = this.obj.index();
		return Utils.cellIndexToGridColumn($rows, rowIdx, colIdx);
	};

	/**
	 * Gives the Y (row no) for a cell, after adding colspans
	 */
	TableCell.prototype._virtualY = function () {
		return this.obj.parent('tr').index();
	};

	/**
	 * Starts the cell selection mode
	 */
	TableCell.prototype._startCellSelection = function () {
		if(!this.tableObj.selection.cellSelectionMode) {

			//unselect currently selected cells
			this.tableObj.selection.unselectCells();

			// activate cell selection mode
			this.tableObj.selection.cellSelectionMode = true;

			//bind a global mouseup event handler to stop cell selection
			var that = this;
			jQuery('body').bind('mouseup.cellselection', function(event) {
				that._endCellSelection();
			});

			this.tableObj.selection.baseCellPosition = [this._virtualY(), this._virtualX()];
		}
	};

	/**
	 * Ends the cell selection mode
	 */
	TableCell.prototype._endCellSelection = function() {
		if (this.tableObj.selection.cellSelectionMode) {
			Utils.selectAnchorContents(this.tableObj.selection.selectedCells);

			this.tableObj.selection.cellSelectionMode = false;
			this.tableObj.selection.baseCellPosition = null;
			this.tableObj.selection.lastSelectionRange = null;

			this.tableObj.selection.selectionType = 'cell';

			//unbind the global cell selection event
			jQuery('body').unbind('mouseup.cellselection');
		}
	};

	TableCell.prototype._getSelectedRect = function () {
		var right = this._virtualX();
		var bottom = this._virtualY();
		var topLeft = this.tableObj.selection.baseCellPosition;
		var left = topLeft[1];
		if (left > right) {
			left = right;
			right = topLeft[1];
		}
		var top = topLeft[0];
		if (top > bottom) {
			top = bottom;
			bottom = topLeft[0];
		}
		return {
			"top": top,
			"right": right,
			"bottom": bottom,
			"left": left
		};
	};
	
	/**
	 * Toggles selection of cell.
	 * This works only when cell selection mode is active.
	 */
	TableCell.prototype._selectCellRange = function() {
		if(this.tableObj.selection.resizeMode || !this.tableObj.selection.cellSelectionMode) {
			return;
		}

		var rect = this._getSelectedRect();
		var table = this.tableObj;
		// if the range contains a single cell only, and no cells were selected before,
		// we do not select the whole cell. This enables selecting text in a single cell
		// without selecting the whole cell, even if - while selecting the text - the user
		// moves the mouse out of the text wrapper into the cell itself
		if (rect.top === rect.bottom && rect.left === rect.right && table.selection.selectedCells.length === 0) {
			return;
		}

		var $rows = table.obj.children().children('tr');
		var grid = Utils.makeGrid($rows);

		table.selection.selectedCells = [];
		table.selection.currentRectangle = rect;
		var selectClass = table.get('classCellSelected');
		Utils.walkGrid(grid, function (cellInfo, j, i) {
			if (Utils.containsDomCell(cellInfo)) {
				if (i >= rect.top && i <= rect.bottom && j >= rect.left && j <= rect.right) {
					jQuery(cellInfo.cell).addClass(selectClass);
					table.selection.selectedCells.push(cellInfo.cell);

				} else {
					jQuery(cellInfo.cell).removeClass(selectClass);
				}
			}
		});

		table.selection.notifyCellsSelected();
	};

	/**
	 * Selects all inner-contens of an contentEditable-object
	 *
	 * @param editableNode dom-representation of the editable node (div-element)
	 * @return void
	 */
	TableCell.prototype._selectAll = function (editableNode) {
		var e = (editableNode.jquery) ? editableNode.get(0) : editableNode;

		// Not IE
		if (!jQuery.browser.msie) {
			var s = window.getSelection();
			// WebKit
			if (s.setBaseAndExtent /*&& e> 0 */ ) {
				s.setBaseAndExtent(e, 0, e, Math.max(0, e.innerText.length - 1));
			}
			// Firefox and Opera
			else {
				// workaround for bug # 42885
				if (window.opera && e.innerHTML.substring(e.innerHTML.length - 4) == '<BR>') {
					e.innerHTML = e.innerHTML + '&#160;';
				}

				var r = document.createRange();
				r.selectNodeContents(e);
				s.removeAllRanges();
				s.addRange(r);
			}
		}
		// Some older browsers
		else if (document.getSelection) {
			var s = document.getSelection();
			var r = document.createRange();
			r.selectNodeContents(e);
			s.removeAllRanges();
			s.addRange(r);
		}
		// IE
		else if (document.selection) {
			var r = document.body.createTextRange();
			r.moveToElementText(e);
			r.select();
		}
	};

	/**
	 * The mouse-down event for the editable-div in the thd-field. Unselect all
	 * cells when clicking on the editable-div.
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	TableCell.prototype._editableMouseDown = function (jqEvent) {
		// deselect all highlighted cells registered in the this.tableObj.selection object
		this.tableObj.selection.unselectCells();

		if (this.tableObj.hasFocus) {
			if (typeof jqEvent.stopPropagation === 'function') {
				jqEvent.stopPropagation();
			} else if (typeof jqEvent.cancelBubble !== 'undefined') {
				jqEvent.cancelBubble = true;
			}
		}
	};

	/**
	 * The key-up event for the editable-div in the td-field. Just check if the div
	 * is empty and insert an &nbsp;
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	TableCell.prototype._editableKeyUp = function (jqEvent) {
		//TODO do we need to check for empty cells and insert a space?
		//this._checkForEmptyEvent(jqEvent);
	};

	/**
	 * The key-down event for the ediable-div in the td-field. Check if the the div
	 * is empty and insert an &nbsp. Furthermore if cells are selected, unselect
	 * them.
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	TableCell.prototype._editableKeyDown = function (jqEvent) {
		var KEYCODE_TAB = 9;
		this._checkForEmptyEvent(jqEvent);
		if (this.obj[0] === this.tableObj.obj.find('tr:last td:last')[0]) {
			// only add a row on a single key-press of tab (so check that alt-,
			// shift- or ctrl-key are NOT pressed)
			if (KEYCODE_TAB == jqEvent.keyCode && !jqEvent.altKey && !jqEvent.shiftKey && !jqEvent.ctrlKey) {
				var lastInsertedRow = this.tableObj.addRow(this.obj.parent().index() + 1);

				if (Browser.mozilla) {
					// After the row is inserted, mozilla sets the cursor outside
					// the Table in weird places.
					jqEvent.preventDefault();

					// Place focus into first editable cell of new row
					$(lastInsertedRow).find('td:nth-child(2) .aloha-table-cell-editable').focus();
				}
			}
		}
	};

	/**
	 * The custom keyup event for a table-cell Checks if the cell is empty and
	 * inserts a space (\u00a0)
	 *
	 * @param e
	 *            the event object which is given by jquery
	 * @return void
	 */
	TableCell.prototype._checkForEmptyEvent = function (jqEvent) {
		var $wrapper = jQuery(this.wrapper),
			text = $wrapper.text();

		if ($wrapper.children().length > 0) {
			return;
		}

		// if empty insert a blank space and blur and focus the wrapper
		if (text === '') {
			this.wrapper.text('');
			this.wrapper.get(0).blur();
			this.wrapper.get(0).focus();
		}
	};

	/**
	 * Given a cell, will return the container element of the contents
	 * of the cell. The container element may be the given cell itself,
	 * or a wrapper element, in the case of activated cells.
	 *
	 * @param {DomNode} cell
	 *        the TH/TD of a TableCell that may or may not be actived.
	 * @return {DomNode}
	 *        the element that contains the contents of the given cell.
	 */
	TableCell.getContainer = function (cell) {
		if (jQuery(cell.firstChild).hasClass("aloha-table-cell-editable")) {
			return cell.firstChild;
		} else {
			return cell;
		}
	};

	return TableCell;
});

define('table/table-selection',[
	'aloha',
	'aloha/jquery',
	'table/table-plugin-utils',
	'table/table-cell',
	'ui/dialog',
	'i18n!table/nls/i18n'
], function (
	Aloha,
	$,
	Utils,
	TableCell,
	Dialog,
	i18n
) {
	/**
	 * The TableSelection object is a helper-object
	 */
	var TableSelection = function (table) {
		this.table = table;
	};

	/**
	 * Returns if a content parameter is a content that be added in a 
	 * merge cell from the cells selecteds.
	 * 
	 * @param {String|TextNodeElement|HTMLElement} content - A content 
	 * from the cell, might be a TextNode, HTMLElement or a String
	 * 
	 * @return {Boolean}
	 */
	function isMergeableContent(content) {
		return ((
				'string' === typeof(content) && '' !== $.trim(content)
			) || (
				content.nodeType
				&& (
					3 === content.nodeType
					&&
					'' !== $.trim(content.data)
				) || (
					1 === content.nodeType
				)
			));
	}

	var POSSIBLE_SELECTION_TYPES = [ 'cell', 'column', 'row' ];

	TableSelection.getPossibleSelectionTypes = function () {
		return POSSIBLE_SELECTION_TYPES;
	};

	/**
	 * Gives the type of the cell-selection
	 * possible values are "cell", "row", "column" or "all".
	 * If the value is 'cell' means custom cell selections
	 */
	TableSelection.prototype.selectionType = undefined;

	/**
	 * Stores the currently selected rectangle
	 * @type {Object} the rectangle object
	 */
	TableSelection.prototype.currentRectangle = {};
	/**
	 * Holds all currently selected table cells as an array of DOM "td" representations
	 */
	TableSelection.prototype.selectedCells = [];

	/**
	 * Holds all table columnIdx if selectiontype is column
	 */
	TableSelection.prototype.selectedColumnIdxs = [];

	/**
	 * Holds all table rowIds if selectiontype is column
	 */
	TableSelection.prototype.selectedRowIdxs = [];

	/**
	 * Holds the active/disabled state of cell selection mode 
	 */
	TableSelection.prototype.cellSelectionMode = false;

	/**
	 * Gives the position of the base cell of a selection - [row, column]
	 */
	TableSelection.prototype.baseCellPosition = null;

	/**
	 * Gives the range of last cell selection - [row, column]
	 */
	TableSelection.prototype.lastSelectionRange = null;

	/**
	 * Marks all cells of the specified column or columns as selected
	 *
	 * @return void
	 */
	TableSelection.prototype.selectColumns = function ( columnsToSelect ) {
		this.unselectCells();

		var rows = this.table.getRows();
		// first row is the selection row (dump it, it's not needed)
		rows.shift();
		
		var grid = Utils.makeGrid(rows);
		for (var j = 0; j < columnsToSelect.length; j++) {
			// check if this column is already selected.
			if ( -1 !== $.inArray(columnsToSelect[j], this.selectedColumnIdxs) ) {
				continue;
			}
			this.selectedColumnIdxs.push( columnsToSelect[j] );
			for (var i = 0; i < grid.length; i++) {
				var cellInfo = grid[i][columnsToSelect[j]];
				if ( Utils.containsDomCell(cellInfo) ) {
					$(cellInfo.cell).addClass(this.table.get('classCellSelected'));
					this.selectedCells.push( cellInfo.cell );
				}
			}
		}
		this.currentRectangle.columns = this.selectedColumnIdxs;
		this.selectionType = 'column';
	};
	
	/**
	 * Marks all cells of the specified row or rows as selected
	 *
	 * @return void
	 */
	TableSelection.prototype.selectRows = function( rowsToSelect ) {
		this.unselectCells();

		var rows = this.table.getRows();
		
 	    rowsToSelect.sort( function ( a, b ) { return a - b; } );
		for (var i = 0; i < rowsToSelect.length; i++) {
			if ( rows[ rowsToSelect[i] ] ) {
				// check if this row is already selected.
	        	for ( var z = 0; z < this.selectedRowIdxs.length; z++ ) {
	        		if ( rowsToSelect[i] == this.selectedRowIdxs[z] ) {
	        			return;
	        		}
	        	}
				this.selectedRowIdxs.push( rowsToSelect[i] );
				// to not select first cell, which is a control cell
			    for ( var j = 1; j < rows[ rowsToSelect[i] ].cells.length; j++ ) {  
					this.selectedCells.push( rows[ rowsToSelect[i] ].cells[j] );
					// TODO make proper cell selection method
					$( rows[ rowsToSelect[i] ].cells[j] ).addClass( this.table.get('classCellSelected') );
			    }
			}
		}
		this.currentRectangle.rows = this.selectedRowIdxs;

	    this.selectionType = 'row';
	};

	TableSelection.prototype.selectAll = function () {
		var rowIndices = $.map( this.table.getRows(), function ( item, i ) {
			return i;
		});

		//getRows() returns all rows, even the header row which we must not select
		rowIndices.shift();

		this.selectRows( rowIndices );

		this.selectionType = 'all';
	};
	
	/**
	 * To be called when cells of the table were selected
	 * @see selectRows, selectColumns, selectCellRange
	 * TODO this should be private
	 */
	TableSelection.prototype.notifyCellsSelected = function () {
		Aloha.trigger( 'aloha-table-selection-changed' );
		
		// the UI feels more consisten when we remove the non-table
		// selection when cells are selected
		// TODO this code doesn't work right in IE as it causes the table
		//  scope of the floating menu to be lost. Maybe this can be
		//  handled by testing for an empty selection in the
		//  aloha-selection-changed event.
		//Aloha.getSelection().removeAllRanges();
	};

	/**
	 * To be called when a cell-selection is entirely removed
	 * @see unselectCells
	 */
	TableSelection.prototype._notifyCellsUnselected = function () {
		Aloha.trigger( 'aloha-table-selection-changed' );
	};

	/**
	 * This method return true if all sellected cells are TH cells.
	 *
	 * @return boolean
	 */
	TableSelection.prototype.isHeader = function ( ) {
		
        if ( this.selectedCells.length == 0 ) {
        	return false;
        }
        
        // take 1 column to detect if the header button is pressd
		for (var i = 0; i < this.selectedCells.length; i++) {
			if ( !this.selectedCells[i] || this.selectedCells[i].nodeName.toLowerCase() != 'th' ) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * This method removes the "selected" class from all selected cells
	 *
	 * @return void
	 */
	TableSelection.prototype.unselectCells = function(){
		//don't unselect cells if cellSelectionMode is active
		if ( this.cellSelectionMode ) {
    		return;
		}

		if (this.selectedCells.length > 0) {
			var
				cells = this.selectedCells,
				classCellSelected = this.table.get('classCellSelected');

			for (var i = 0, len = cells.length; i < len; i++) {
				$(cells[i]).removeClass(classCellSelected);
			}

			this.selectedCells = [];
			this.selectedColumnIdxs = [];
			this.selectedRowIdxs = [];
			this.currentRectangle = {};

			//we keep 'cell' as the default selection type instead of
			//unsetting the selectionType to avoid an edge-case where a
			//click into a cell doesn't trigger a call to
			//TableCell.editableFocs (which would set the 'cell'
			//selection type) which would result in the FloatingMenu
			//losing the table scope.
			this.selectionType = 'cell';

			this._notifyCellsUnselected();
		}
	};

	/**
	 * Returns the index of a given cell, in selectedCells
	 * returns -1 if the given cell is not in selectedCells 
	 * @params cell
	 *          DOMElement
	 *
	 * @return integer 
	 */
	TableSelection.prototype.selectionIndex = function(cell){
		for(var i = 0; i < this.selectedCells.length; i++){
			if(this.selectedCells[i] === cell){
				return i; 
			} 
		}
		return -1;
	};


	/**
	 * Given a contour creates a object representing a rectangle.
	 * This function only gives a useful return value if the given
	 * contour rectangular.
	 *
	 * @param {object} contour
	 *        a rectangular contour
	 * @return {object}
	 *        an object with the properties top, right, bottom, left, 
	 *        representing the rectangular contour.
	 */
	function getRectFromContour( contour ) {
		return {
			'top'   : contour.top[0],
			'right' : contour.right[0] + 1,
			'bottom': contour.bottom[0] + 1,
			'left'  : contour.left[0]
		};
	}

	/**
	 * Given a grid and contour, determines whether the contour is
	 * rectangular, and each cell in the rectangle is selected.
	 *
	 * @param {array} grid
	 *        a two-dimensional array representing a grid see Utils.makeGrid
	 * @param {object} contour
	 *        an object reprensenting a contour see Utils.makeContour
	 * @param {function} isSelected
	 *        a function that determines whether a cell in the given grid
	 *        is selected for merging.
	 * @return {boolean}
	 *        true if all cells inside the contour are selected and can
	 *        be merged.
	 */
	function isMergeable(grid, contour, isSelected) {
		var mergeable = true;
		if (   -1 !== Utils.indexOfAnyBut( contour.top   , contour.top[0]    )
			|| -1 !== Utils.indexOfAnyBut( contour.right , contour.right[0]  )
			|| -1 !== Utils.indexOfAnyBut( contour.bottom, contour.bottom[0] )
			|| -1 !== Utils.indexOfAnyBut( contour.left  , contour.left[0]   ) ) {
			// the outside of the selected area is jagged (not a rectangle)
			mergeable = false;
		} else {
			// the outside of the selected area is a rectangle, but we
			// must also ensore that there are no holes in the selection
			var rect = getRectFromContour( contour )
			Utils.walkGridInsideRect( grid, rect, function ( cellInfo ) {
				if ( ! isSelected( cellInfo ) ) {
					mergeable = false;
					return false;
				}
			});
		}
		return mergeable;
	}

	/**
	 * This method merges all selected cells
	 *
	 * @return void
	 */
	TableSelection.prototype.mergeCells = function(){

		var selectedCells = this.selectedCells;
		if ( 0 === selectedCells.length ) {
			return;
		}

		var isSelected = function ( cellInfo ) {
			return -1 != $.inArray( cellInfo.cell, selectedCells );
		};

		var grid = Utils.makeGrid( this.table.getRows() );
		var contour = Utils.makeContour( grid, isSelected );

		if ( ! isMergeable( grid, contour, isSelected ) ) {
			Dialog.alert({
				title : i18n.t('Table'),
				text : i18n.t('table.mergeCells.notRectangular')
			});
			return;
		}

		var selectedRect = getRectFromContour( contour );
		var $firstCell = $( grid[ selectedRect.top ][ selectedRect.left ].cell );
		var $firstContainer = $( TableCell.getContainer( $firstCell.get( 0 ) ) );

		Utils.walkGridInsideRect( grid, selectedRect, function ( cellInfo, x, y ) {
			if (   x - cellInfo.spannedX === selectedRect.left
				&& y - cellInfo.spannedY === selectedRect.top ) {
				return;
			}
			var cell = cellInfo.cell;
			var contents = $( TableCell.getContainer( cell ) ).contents();
			// only append the delimiting space if there is some non-whitespace
			for ( var i = 0; i < contents.length; i++ ) {
				if(isMergeableContent(contents[i])){
					$firstContainer.append( " " );
					$firstContainer.append( contents );
					break;
				}
//
//				if (   "string" !== typeof contents[i]
//					|| "" !== $.trim( contents[i].data ) ) {
//					$firstContainer.append( " " );
//					$firstContainer.append( contents );
//					break;
//				}
			}
			$( cell ).remove();
		});

		$firstCell.attr({ 'rowspan': selectedRect.bottom - selectedRect.top,
						  'colspan': selectedRect.right  - selectedRect.left });

		//select the merged cell
		this.selectedCells = [ $firstCell.get( 0 ) ];

		//reset flags
		this.cellSelectionMode = false; 
		this.baseCellPosition = null;
		this.lastSelectionRange = null; 
		this.selectionType = 'cell';

		Aloha.trigger( 'aloha-table-selection-changed' );
	};

	/**
	 * This method splits all selected cells (if they are already have row or column spans)
	 *
	 * @return void
	 */
	TableSelection.prototype.splitCells = function(){
		var selection = this;

		// split the selected cells or currently active cell
		var cells_to_split = this.selectedCells;
		if (cells_to_split.length > 0) {

			$(cells_to_split).each(function(){
				Utils.splitCell(this, function () {
					return selection.table.newActiveCell().obj;
				});
			});

			//reset flags
			this.cellSelectionMode = false; 
			this.baseCellPosition = null;
			this.lastSelectionRange = null; 
			this.selectionType = 'cell';

			Aloha.trigger( 'aloha-table-selection-changed' );
		}
	};

	/**
	 * This method checks if the current selection of cells is merge able
	 *
	 * @return {boolean}
	 *        true if more than one cell is selected.
	 */
	TableSelection.prototype.cellsAreMergeable = function() {
		var selectedCells = this.selectedCells;

		if ( selectedCells.length < 2 ) {
			return false;
		}

		var isSelected = function ( cellInfo ) {
			return -1 != $.inArray( cellInfo.cell, selectedCells );
		};

		var grid = Utils.makeGrid( this.table.getRows() );
		var contour = Utils.makeContour( grid, isSelected );

		if ( ! isMergeable( grid, contour, isSelected ) ) {
			return false;
		} else {
			return true;
		}
	};

	/**
	 * This method checks if the current selection of cells is split able
	 *
	 * @return {boolean}
	 *        true if more than one cell is selected.
	 */
	TableSelection.prototype.cellsAreSplitable = function() {
		var splitable = 0;
		if ( this.selectedCells.length > 0 ) {
			$(this.selectedCells).each(function(){
				var cell = this;
				var colspan = Utils.colspan( cell );
				var rowspan = Utils.rowspan( cell );

				if ( colspan > 1 || rowspan > 1 ) {
					splitable++;
				}
			});

			if ( splitable > 0 ) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	};

	/**
	 * Gets the currenty active selection type from the selected
	 * rectangle or the selected rows/columns.
	 * The first element in the array is the selection type which is
	 * most likely to be the most important one to the user.
	 * (e.g. "table" if a whole table was selected or "row" when the whole row was selected)
	 *
	 * @return {array} an array of currently active selection types
	 */
	TableSelection.prototype.getCurrentSelectionTypes = function () {
		var rect = this.currentRectangle;
		var table = this.table;
		var selectionTypes = [];

		// check if a whole row was selected with row or cell selection
		if (rect.rows || (rect.left === 1 && rect.right >= table.numCols)) {
			selectionTypes.push('row');
		}
		// check if a whole column was selected with column or cell selection
		if (rect.columns || (rect.top === 1 && rect.bottom >= table.numRows)) {
			selectionTypes.push('column');
		}
		// if all rows and columns are selected using row, column or cell selection mark the whole table as selected
		// put the "cell" selection type on the first position in the array to make sure it is selected
		// as active tab in the toolbar
		if (
			// check if the whole table is selected with row selection
			(rect.rows && rect.rows.length === table.numRows) ||
			// check if the whole table is selected with column selection
			(rect.columns && rect.columns.length === table.numCols) ||
			// check if the whole table is selected with cell selection
			($.inArray('row', selectionTypes) !== -1 && $.inArray('column', selectionTypes) !== -1)
		) {
			selectionTypes.unshift('cell');
		// if row and column selection was not used, we know the cell selection was used and need to add this type in the array
		} else if (!rect.rows && !rect.columns) {
			selectionTypes.push('cell');
		}
		// as fallback use the selection type set with the old logic
		if (selectionTypes.length === 0) {
			selectionTypes.push(this.selectionType);
		}
		return selectionTypes;
	};

	return TableSelection;
});

/**
 * Aloha Table Plugin
 * ------------------
 * This plugin provides advanced support for manipulating tables in Aloha
 * Editables.
 * Nested tables are not support. If nested tables are pasted into the
 * editable, they will simply be left alone.
 * Each (non-nested) table in the editable will have a corresponding Aloha
 * Table instance created for it, which will maintain internal state, and
 * information related to its DOM element.
 *
 * @todo: - selectRow/selectColumn should take into account the helper row/column.
 *			ie: selectRow(0) and selectColumn(0), should be zero indexed
 */
define('table/table',[
	'aloha',
	'jquery',
	'ui/scopes',
	'ui/dialog',
	'i18n!table/nls/i18n',
	'table/table-cell',
	'table/table-selection',
	'table/table-plugin-utils',
	'aloha/ephemera',
	'util/html',
	'util/dom',
	'aloha/console'
], function (
	Aloha,
	jQuery,
	Scopes,
	Dialog,
	i18n,
	TableCell,
	TableSelection,
	Utils,
	Ephemera,
	Html,
	Dom,
    Console
) {
	var undefined = void 0;
	var GENTICS = window.GENTICS;

	/**
	 * Returns an Array with all elements and textnodes included in the 
	 * hierarchy of the element received. Is Similar to do 
	 * jQuery('*', element).contents(), the diference it's this function returns the 
	 * array in the correct order of apparition
	 * @example
	 * <pre>
	 *		&gt;p&lt;
	 *			textnode
	 *			<b>b textnode</b>
	 *			another text node
	 *		&gt;/p&lt;
	 *		
	 *		jQuery('*', lt).contents();
	 *			// returns ["textnode", "<b>", "another textnode", "b textnode"]
	 *		getPlainHierarchy(lt)
	 *			// returns ["textnode", "<b>", "b textnode", "another textnode"]
	 * </pre>
	 * 
	 * @return {Array.<HTMLElement|TextNode>}
	 */
	function getPlainHierarchy(element) {
		if (element.jquery) {
			element = element[0];
		}
		var i, result = [], child;
		for (i = 0; i < element.childNodes.length; i++) {
			child = element.childNodes[i];
			result.push(child);
			if (child.nodeType === 1) {
				result = result.concat(getPlainHierarchy(child));
			}
		}

		return result;
	}

	/**
	 * Find the first or the last element inside a table, even if in a td
	 * 
	 * @param {String} type Accepts two values: 'first' or 'last'
	 * @param {HTMLElement|jQuery} parent the parent element to search
	 * 
	 * @return {jQuery}
	 */
	function getNewSelectedElement(type, parent) {
		var toSelectElement;
		if ('first' === type) {
			toSelectElement = jQuery('[contenteditable]', parent).first()[0]
				.firstChild;
			if (undefined === toSelectElement) {
				toSelectElement = jQuery('*', parent).first()[0].firstChild;
			}
		} else if ('last' === type) {
			toSelectElement = getPlainHierarchy(jQuery('td:last', parent))
				.reverse()[0];
		}

		return toSelectElement;
	}

	/**
	 * Constructor of the table object
	 *
	 * @param table
	 *            the dom-representation of the held table
	 * @return void
	 */
	var Table = function ( table, tablePlugin ) {
		// set the table attribut "obj" as a jquery represenation of the dom-table
		this.obj = jQuery( table );

		correctTableStructure( this );

		if ( !this.obj.attr( 'id' ) ) {
			this.obj.attr( 'id', GENTICS.Utils.guid() );
		}

		// mark table id as ephemeral
		Ephemera.markAttr(this.obj, 'id');

		this.tablePlugin = tablePlugin;
		this.selection = new TableSelection( this );
		this.refresh();
	};

	jQuery.extend( Table.prototype, {
		/**
		 * Attribute holding the jQuery-table-represenation
		 */
		obj: undefined,

		/**
		 * The DOM-element of the outest div-container wrapped around the cell
		 */
		tableWrapper: undefined,

		/**
		 * An array of all Cells contained in the Table
		 *
		 * @see TableCell
		 */
		cells: undefined,

		/**
		 * Number of rows of the table
		 */
		numRows: undefined,

		/**
		 * Number of rows of the table
		 */
		numCols: undefined,

		/**
		 * Flag wether the table is active or not
		 */
		isActive: false,

		/**
		 * Flag wether the table is focused or not
		 */
		hasFocus: false,

		/**
		 * The editable which contains the table
		 */
		parentEditable: undefined,

		/**
		 * Flag to check if the mouse was pressed. For row- and column-selection.
		 */
		mousedown: false,

		/**
		 * ID of the column which was pressed when selecting columns
		 */
		clickedColumnId: -1,

		/**
		 * ID of the row which was pressed when selecting rows
		 */
		clickedRowId: -1,

		/**
		 * collection of columnindexes of the columns which should be selected
		 */
		columnsToSelect: [],

		/**
		 * collection of rowindexes of the rows which should be selected
		 */
		rowsToSelect: [],

		/**
		 * contains the plugin id used for interaction with the floating menu
		 */
		fmPluginId: undefined
	} );

	/**
	 * @hide
	 */
	Table.prototype.refresh = function () {
		// find the dimensions of the table
		this.numCols = this.countVirtualCols();

		var rows = this.getRows();
		this.numRows = rows.length;

		// init the cell-attribute with an empty array
		this.cells = [];

		// iterate over table cells and create Cell-objects
		for ( var i = 0; i < rows.length; i++ ) {
			var row = jQuery(rows[i]);
			var cols = row.children();
			for ( var j = 0; j < cols.length; j++ ) {
				var col = cols[j];
				var Cell = this.newCell( col );
			}
		}
	};

	Table.prototype.countVirtualCols = function () {
		var $firstRow = this.obj.children().children( 'tr:first-child' ).children();
		return $firstRow.length - $firstRow.filter( '.' + this.get( 'classLeftUpperCorner' ) ).length;
	};

	/**
	 * Wrapper-Mehotd to return a property of TablePlugin.get
	 *
	 * @see TablePlugin.get
	 * @param property
	 *            the property whichs value should be return
	 * @return the value associated with the property
	 */
	Table.prototype.get = function(property) {
		return this.tablePlugin.get(property);
	};

	/**
	 * Wrapper-Method for TablePlugin.set
	 *
	 * @see TablePlugin.set
	 * @param key
	 *            the key whichs value should be set
	 * @param value
	 *            the value for the key
	 * @return void
	 */
	Table.prototype.set = function(key, value) {
		this.tablePlugin.set(key, value);
	};

	/**
	 * Given an unbalanced table structure, pad it with the necessary cells to
	 * make it perfectly rectangular
	 *
	 * @param {Aloha.Table} tableObj
	 */
	function correctTableStructure ( tableObj ) {
		var table = tableObj.obj,

			i,
			j,
		    row,
		    rows = tableObj.getRows(),
		    rowsNum = rows.length,

			cols,
			colsNum,

		    colsCount,
		    maxColsCount = 0,
		    cachedColsCounts = [rowsNum],
		    colsCountDiff,
		    colSpan;

		for ( i = 0; i < rowsNum; i++ ) {
			row = jQuery( rows[ i ] );
			cols = row.children( 'td, th' );
			colsNum = cols.length;
			colsCount = Utils.cellIndexToGridColumn( rows, i, colsNum - 1 ) + 1;

			// Check if the last cell in this row has a col span, to account
			// for it in the total number of colums in this row

			colSpan = parseInt( cols.last().attr( 'colspan' ), 10 );

			if ( colSpan == 0 ) {
				// TODO: support colspan=0
				// http://dev.w3.org/html5/markup/td.html#td.attrs.colspan
				// http://www.w3.org/TR/html401/struct/tables.html#adef-colspan
				// The value zero ("0") means that the cell spans all columns
				// from the current column to the last column of the column
				// group (COLGROUP) in which the cel
			} else if ( !isNaN( colSpan ) ) {
				// The default value of this attribute is one ("1"), so where this
				// is the case, we will remove such superfluous colspan attributes
				if ( colSpan == 1 ) {
					cols.last().removeAttr( 'colspan' );
				}

				colsCount += ( colSpan - 1 );
			}

			// if a rowspan is set in the last element of the row, the row(s) below
			// are supposed to have one less column for every colspan the element has
			rowSpan = parseInt(cols.last().attr('rowspan'), 10);
			if (rowSpan > 1) {
				for (j = 1; j < rowSpan-1; j++) {
					if (colSpan > 1) {
						cachedColsCounts[i+j] += colSpan;
					} else {
						cachedColsCounts[i+j] += 1;
					}
				}
			}

			cachedColsCounts[i] += colsCount;

			if (cachedColsCounts[i] > maxColsCount) {
				maxColsCount = cachedColsCounts[i];
			}
		}

		for ( i = 0; i < rowsNum; i++ ) {
			colsCountDiff = maxColsCount - cachedColsCounts[ i ];
			if ( colsCountDiff > 0 ) {
				// Create as many td's as we need to complete the row
				jQuery( rows[ i ] ).append(
					( new Array( colsCountDiff + 1 ) ).join( '<td></td>' )
				);
			}
		}
	};

	/**
	 * If all of the selected cells have been set to the same predefined style,
	 * then its style-button is toggled on. Otherwise, all style-buttons are toggled off.
	 *
	 * @param selectedCells the cells to be checked
	 * @param config the list of styles as defined in the aloha-configuration
	 * @param items the multisplit-toggle-items
	 * @param button a multisplit-button
	 *
	 * @return void
	 */
	function setActiveStyle(selectedCells, config, items, button) {
		var className;
		var allSelected = false;

		// activate all formatting buttons
		for (var i = 0; i < items.length; i++) {
			button.showItem(items[i].name);
		}

		// clear active style block
		button.setActiveItem();

		// select class of first element as reference
		for (var i = 0; i < config.length; i++) {
			if (jQuery(selectedCells[0]).hasClass(config[i].cssClass)) {
				allSelected = true;
				className = config[i].name;
				break;
			}
		}

		// if all selected cells have the same class, set it as active
		jQuery(selectedCells).each(function(index) {
			if (!jQuery(this).hasClass(className)) {
				allSelected = false;
			}
		});
		if (allSelected) {
			button.setActiveItem(className);
		}
	}

	/**
	 * Transforms the existing dom-table into an editable aloha-table. In fact it
	 * replaces the td-elements with equivalent TableCell-elements
	 * with attached events.
	 * Furthermore it creates wrapping divs to realize a click-area for row- and
	 * column selection and also attaches events.
	 *
	 * @return void
	 */
	Table.prototype.activate = function () {
		if ( this.isActive ) {
			return;
		}

		var that = this,
		    htmlTableWrapper,
		    tableWrapper, eventContainer, range = new Aloha.Selection.SelectionRange(true);

		// check whether the current selection is in this table
		if (jQuery(range.startContainer).closest('table').is(this.obj)
				|| jQuery(range.endContainer).closest('table').is(
						this.obj)) {
			// if the startContainer or endContainer are a tr, we move into the next td
			if (range.startContainer
					&& range.startContainer.nodeType === 1
					&& range.startContainer.nodeName.toLowerCase() === 'tr') {
				if (range.startOffset < range.startContainer.childNodes.length) {
					range.startContainer = range.startContainer.childNodes[range.startOffset];
					range.startOffset = 0;
				}
			}
			if (range.endContainer
					&& range.endContainer.nodeType === 1
					&& range.endContainer.nodeName.toLowerCase() === 'tr') {
				if (range.endOffset < range.endContainer.childNodes.length) {
					range.endContainer = range.endContainer.childNodes[range.endOffset];
					range.endOffset = 0;
				}
			}
		} else {
			range = null;
		}

		// alter the table attributes
		this.obj.addClass( this.get( 'className' ) );
		this.obj.contentEditable( false );

		// set an id to the table if not already set
		if ( this.obj.attr( 'id' ) == '' ) {
			this.obj.attr( 'id', GENTICS.Utils.guid() );
		}

		// unset the selection type
		this.selection.selectionType = undefined;

		// the eventContainer will be the tbody (if there is one), or the table (if no tbody exists)
		eventContainer = this.obj.children('tbody');
		if (eventContainer.length === 0) {
			eventContainer = this.obj;
		}

		eventContainer.bind( 'keydown', function ( jqEvent ) {
			if ( !jqEvent.ctrlKey && !jqEvent.shiftKey ) {
				if ( that.selection.selectedCells.length > 0 &&
						that.selection.selectedCells[ 0 ].length > 0 ) {
					that.selection.selectedCells[ 0 ][ 0 ].firstChild.focus();
				}
			}
		} );

		this.obj.on('keydown', function (jqEvent) {
			// Delete button
			if (jqEvent.keyCode === 46) {
				if (that.selection.selectionType === 'row') {
					that.deleteRows();
				} else if (that.selection.selectionType === 'column') {
					that.deleteColumns();
				} else {
					return;
				}

				// jqEvent.stopPropagation doesn't support cancelBubble
				// in the last jQuery versions. (query/jquery@97fa97f#diff-031bb62d959e7e4949d1847c82507f33L676)
				if (typeof jqEvent.stopPropagation === 'function') {
					jqEvent.stopPropagation();
				} else {
					// Workaround for IE
					jqEvent.cancelBubble = true;
				}
			}
		});

		/*
		We need to make sure that when the user has selected text inside a
		table cell we do not delete the entire row, before we activate this

		this.obj.bind( 'keyup', function ( $event ) {
			if ( $event.keyCode == 46 ) {
				if ( that.selection.selectedColumnIdxs.length ) {
					that.deleteColumns();
					$event.stopPropagation();
				} else if ( that.selection.selectedRowIdxs.length ) {
					that.deleteRows();
					$event.stopPropagation();
				} else {
					// Nothing to delete
				}
			}
		} );
		*/

		// handle click event of the table
	//	this.obj.bind('click', function(e){
	//		// stop bubbling the event to the outer divs, a click in the table
	//		// should only be handled in the table
	//		e.stopPropagation();
	//		return false;
	//	});
	//

	 // handle column/row resize
			eventContainer.delegate( 'th, td', 'mousemove', function( e ) {

				var jqObj = jQuery( this );
				// offset to be used for activating the resize cursor near a table border
				var mouseOffset = 3;

				// filter out the control cells
				if ( jQuery( this ).hasClass( 'aloha-table-selectrow' ) || jQuery( this ).closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ))
					return;

				var closeToLeftBorder = function(cell) {
					return ( ( e.pageX - cell.offset().left ) < mouseOffset );
				};

				var closeToTopBorder = function(cell) {
					return ( ( e.pageY - cell.offset().top ) < mouseOffset );
				};

				var closeToTableBottom = function(cell) {
					var row = cell.closest( 'tr');
					// check if it's the last row
					if ( row.next( 'tr').length > 0 ) {
						return false
					}

					var cursorOffset = e.pageY - ( row.offset().top + row.outerHeight() );
					return cursorOffset > (mouseOffset * -1) && cursorOffset < mouseOffset;
				}

				var colResize = that.tablePlugin.colResize;
				var rowResize = that.tablePlugin.rowResize;

				if ( colResize && closeToLeftBorder( jqObj ) ) {
					jqObj.css( 'cursor', 'col-resize' );
					return that.attachColumnResize( jqObj );
				} else if ( rowResize && closeToTopBorder( jqObj ) ) {
					jqObj.css( 'cursor', 'row-resize' );
					return that.attachRowResize( jqObj );
				} else if ( rowResize && closeToTableBottom( jqObj ) ) {
					jqObj.css( 'cursor', 'row-resize' );
					return that.attachRowResize( jqObj, true );
				} else {
					jqObj.css( 'cursor', 'default' );
					return that.detachRowColResize( jqObj );
				}
			});

			eventContainer.bind( 'mousemove', function( e ) {

				var jqObj = jQuery( this ).closest( 'table' );

				var isTableRightBorder = function( table ) {
					var cursorOffset = e.pageX - ( table.offset().left + table.outerWidth() );
					return cursorOffset > -5 && cursorOffset < 5;
				};

				var tableResize = that.tablePlugin.tableResize;

				if ( tableResize && isTableRightBorder( jqObj ) ) {
					return that.attachTableResizeWidth( jqObj );
				}

			});

		eventContainer.bind( 'mousedown', function ( jqEvent ) {
			// focus the table if not already done
			if ( !that.hasFocus ) {
				that.focus();
			}

			// DEACTIVATED by Haymo prevents selecting rows
			//		// if a mousedown is done on the table, just focus the first cell of the table
			//		setTimeout(function() {
			//			var firstCell = that.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0);
			//			TableSelection.unselectCells();
			//			jQuery(firstCell).get(0).focus();
			//			// move focus in first cell
			//			that.obj.cells[0].wrapper.get(0).focus();
			//		}, 0);

			// stop bubbling and default-behaviour
			jqEvent.stopPropagation();
			jqEvent.preventDefault();
			return false;
		} );

		// ### create a wrapper for the table (@see HINT below)
		// wrapping div for the table to suppress the display of the resize-controls of
		// the editable divs within the cells
		// tha data-block-skip-scope attribute will keep the block plugin from setting the
		// FloatingMenu's scope when the block is clicked
		tableWrapper = jQuery(
			'<div class="' + this.get( 'classTableWrapper' ) + ' aloha-block-collection" data-block-skip-scope="true"></div>'
		);
		tableWrapper.contentEditable( false );

		// mark the wrapper as ephemeral
		Ephemera.markWrapper(tableWrapper);

		// wrap the tableWrapper around the table
		this.obj.wrap( tableWrapper );

		// Check because the aloha block plugin may not be loaded
		var parent = this.obj.parent();
		if (parent.alohaBlock) {
			parent.alohaBlock();
		}

		// :HINT The outest div (Editable) of the table is still in an editable
		// div. So IE will surround the the wrapper div with a resize-border
		// Workaround => just disable the handles so hopefully won't happen any ugly stuff.
		// Disable resize and selection of the controls (only IE)
		// Events only can be set to elements which are loaded from the DOM (if they
		// were created dynamically before) ;)

		htmlTableWrapper = this.obj.parents( '.' + this.get( 'classTableWrapper' ) );
		htmlTableWrapper.get( 0 ).onresizestart = function ( e ) { return false; };
		htmlTableWrapper.get( 0 ).oncontrolselect = function ( e ) { return false; };
		htmlTableWrapper.get( 0 ).ondragstart = function ( e ) { return false; };
		htmlTableWrapper.get( 0 ).onmovestart = function ( e ) { return false; };
		// the following handler prevents proper selection in the editable div in the caption!
		// htmlTableWrapper.get( 0 ).onselectstart = function ( e ) { return false; };

		this.tableWrapper = this.obj.parents( '.' + this.get( 'classTableWrapper' ) ).get( 0 );

		jQuery( this.cells ).each( function () {
			this.activate();
		} );

		// after the cells where replaced with contentEditables ... add selection cells
		// first add the additional columns on the left side
		this.attachSelectionColumn();
		// then add the additional row at the top
		this.attachSelectionRow();
		this.makeCaptionEditable();
		this.checkWai();
		this.isActive = true;

		// when we stored the range, it was in the current table,
		// so we need to re-select (because we changed the DOM structure around the table)
		if (range) {
			// check whether the startContainer and/or endContainer are one of the table's cells.
			// if yes, replace the container with the editable wrapper
			jQuery(this.cells).each(function() {
				if (this.obj.is(range.startContainer)) {
					if (this.wrapper.contents().length === 0) {
						this.wrapper.html('&nbsp;');
					}
					range.startContainer = this.wrapper.contents().get(0);
					range.startOffset = 0;
				}
				if (this.obj.is(range.endContainer)) {
					if (this.wrapper.contents().length === 0) {
						this.wrapper.html('&nbsp;');
					}
					range.endContainer = this.wrapper.contents().get(0);
					range.endOffset = 0;
				}
			});
			this.focus();
			window.setTimeout( function() {
				range.select();
			}, 1);
		}

		Aloha.trigger( 'aloha-table-activated' );
	};

	/**
	 * Make the table caption editable (if present)
	 */
	Table.prototype.makeCaptionEditable = function() {
		var caption = this.obj.find('caption').eq(0);
		if (caption) {
			this.tablePlugin.makeCaptionEditable(caption);
		}
	};

	/**
	 * Check the WAI conformity of the table and sets the attribute.
	 *
	 * @returns {boolean} True is WAI is activated, False otherwise.
	 */
	Table.prototype.checkWai = function () {
		var thisWai = this.wai;
		if (!thisWai) {
			return false;
		}

		var waiGreen = this.get('waiGreen'),
			waiRed = this.get('waiRed');

		thisWai.removeClass(waiGreen + ' ' + waiRed);

		// Y U NO explain why we must check that summary is longer than 5 characters?

		if (jQuery.trim(this.obj[0].summary) !== '') {
			thisWai.addClass(waiGreen);
			return true;
		}

		thisWai.addClass(waiRed);
		return false;
	};

	/**
	 * Add the selection-column to the left side of the table and attach the events
	 * for selection rows
	 *
	 * @return void
	 */
	Table.prototype.attachSelectionColumn = function() {
		// create an empty cell
		var emptyCell = jQuery('<td>'),
			rowIndex, columnToInsert, rowObj, that = this, rows, i;

		// set the unicode '&nbsp;' code
		emptyCell.html('\u00a0');

		that = this;
		rows = this.obj.context.rows;

		// add a column before each first cell of each row
		for ( i = 0; i < rows.length; i++) {
			rowObj = jQuery(rows[i]);
			columnToInsert = emptyCell.clone();
			columnToInsert.addClass(this.get('classSelectionColumn'));
			columnToInsert.css('width', this.get('selectionArea') + 'px');
			//rowObj.find('td:first').before(columnToInsert);
			rowObj.prepend(columnToInsert);
			// rowIndex + 1 because an addtional row is still added
			rowIndex = i + 1;

			// this method sets the selection-events to the cell
			this.attachRowSelectionEventsToCell(columnToInsert);
		}
	};

	/**
	 * Binds the needed selection-mouse events to the given cell
	 *
	 * @param cell
	 *            The jquery object of the table-data field
	 * @return void
	 */
	Table.prototype.attachRowSelectionEventsToCell = function(cell){
		var that = this;

		// mark cell as ephemeral
		Ephemera.markElement(cell);

		// unbind eventually existing events of this cell
		cell.unbind('mousedown');
		cell.unbind('mouseover');

		// prevent ie from selecting the contents of the table
		cell.get(0).onselectstart = function() { return false; };

		cell.bind('mousedown', function(e) {
			// set flag that the mouse is pressed
//TODO to implement the mousedown-select effect not only must the
//mousedown be set here but also be unset when the mouse button is
//released.
//			that.mousedown = true;
			return that.rowSelectionMouseDown(e);
		});

		cell.bind('mouseover', function(e){
			// only select more crows if the mouse is pressed
			if ( that.mousedown ) {
				return that.rowSelectionMouseOver(e);
			}
		});
	};

	/**
	 * Mouse-Down event for the selection-cells on the left side of the table
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.rowSelectionMouseDown = function ( jqEvent ) {
		// focus the table (if not already done)
		this.focus();

		// if no cells are selected, reset the selection-array
		if ( this.selection.selectedCells.length == 0 ) {
			this.rowsToSelect = [];
		}

		// set the origin-rowId of the mouse-click
		this.clickedRowId = jqEvent.currentTarget.parentNode.rowIndex;

		// set single column selection
		if ( jqEvent.metaKey ) {
			var arrayIndex = jQuery.inArray( this.clickedRowId, this.rowsToSelect );
			if ( arrayIndex >= 0 ) {
				this.rowsToSelect.splice( arrayIndex, 1 );
			} else {
				this.rowsToSelect.push( this.clickedRowId );
			}
		// block of columns selection
		} else if ( jqEvent.shiftKey ) {
			this.rowsToSelect.sort( function( a, b ) { return a - b; } );
			var start = this.rowsToSelect[ 0 ];
			var end = this.clickedRowId;
			if ( start > end ) {
				start = end;
				end = this.rowsToSelect[ 0 ];
			}
			this.rowsToSelect = [];
			for ( var i = start; i <= end; i++ ) {
				this.rowsToSelect.push( i );
			}
		// single column
		} else {
			this.rowsToSelect = [ this.clickedRowId ];
		}

		// mark the selection visual
		this.selectRows();

		// prevent browser from selecting the table
		jqEvent.preventDefault();

		// stop bubble, otherwise the mousedown of the table is called ...
		jqEvent.stopPropagation();

		// prevent ff/chrome/safare from selecting the contents of the table
		return false;
	};

	/**
	 * The mouse-over event for the selection-cells on the left side of the table.
	 * On mouse-over check which column was clicked, calculate the span between
	 * clicked and mouse-overed cell and mark them as selected
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.rowSelectionMouseOver = function (jqEvent) {
		var rowIndex = jqEvent.currentTarget.parentNode.rowIndex,
			indexInArray, start, end, i;

		// only select the row if the mouse was clicked and the clickedRowId isn't
		// from the selection-row (row-id = 0)
		if (this.mousedown && this.clickedRowId >= 0) {

			// select first cell
			//var firstCell = this.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0);
			//jQuery(firstCell).get(0).focus();

			indexInArray = jQuery.inArray(rowIndex, this.rowsToSelect);

			start = (rowIndex < this.clickedRowId) ? rowIndex : this.clickedRowId;
			end = (rowIndex < this.clickedRowId) ? this.clickedRowId : rowIndex;

			this.rowsToSelect = new Array();
			for ( i = start; i <= end; i++) {
				this.rowsToSelect.push(i);
			}

			// this actually selects the rows
			this.selectRows();

			// prevent browser from selecting the table
			jqEvent.preventDefault();

			// stop bubble, otherwise the mousedown of the table is called ...
			jqEvent.stopPropagation();

			// prevent ff/chrome/safare from selecting the contents of the table
			return false;
		}
	};

  /**
   * Binds the needed selection-mouse events to the given cell
   *
   * @param cell
   *            The jquery object of the table-data field
   * @return void
   */
	Table.prototype.attachSelectionRow = function () {
		var that = this;

		// create an empty td
		var emptyCell = jQuery('<td>');
		emptyCell.html('\u00a0');

		// get the number of columns in the table (first row)
		// iterate through all rows and find the maximum number of columns to add
		var numColumns = 0;
		for( var i = 0; i < this.obj.context.rows.length; i++ ){
			var curNumColumns = 0;

			for( var j = 0; j < this.obj.context.rows[i].cells.length; j++ ){
				var colspan = Utils.colspan( this.obj.context.rows[i].cells[j] );
				curNumColumns += colspan;
			}

			if( numColumns < curNumColumns ) {
				numColumns = curNumColumns;
			}
		}

		var selectionRow = jQuery('<tr>');
		selectionRow.addClass(this.get('classSelectionRow'));
		selectionRow.css('height', this.get('selectionArea') + 'px');

		// mark selection row as ephemeral
		Ephemera.markElement(selectionRow);

		for (var i = 0; i < numColumns; i++) {

			var columnToInsert = emptyCell.clone();
			// the first cell should have no function, so only attach the events for
			// the rest
			if (i > 0) {
				// bind all mouse-events to the cell
				this.attachColumnSelectEventsToCell(columnToInsert);
				//set the colspan of selection column to match the colspan of first row columns
			} else {
				var columnToInsert = jQuery('<td>').clone();
				columnToInsert.addClass(this.get('classLeftUpperCorner'));
				var clickHandler = function (e) {
					// select the Table
					that.focus();
					that.selection.selectAll();

					// set the selection type before updating the scope
					that.tablePlugin.activeTable.selection.selectionType = 'cell';
					that.tablePlugin.updateFloatingMenuScope();

					// As side-effect of the following call the focus
					// will be set on the first selected cell.
					// This will be overwritten with the summary
					// attribute-field, if the setting summaryinsidebar
					// is false.
					that._removeCursorSelection();

					//If the summary should be modified in the sidebar
					//we activate the sidebar panel
					if (that.tablePlugin.settings.summaryinsidebar) {
						that.tablePlugin.sidebar.open();
						that.tablePlugin.sidebarPanel.activate(that.obj);
						that.tablePlugin.sidebar.correctHeight();
					}

					// jump in Summary field
					// attempting to focus on summary input field will occasionally result in the
					// following exception:
					//uncaught exception: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIDOMHTMLInputElement.setSelectionRange]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: src/dep/ext-3.2.1/ext-all.js :: <TOP_LEVEL> :: line 11" data: no]
					// this occurs when the tab in which the summary field is contained is not visible
					// TODO: I'm adding a try catch clause here for the time being, but a proper solution, which addresses the problem of how to handle invisible fields ought to be persued.

					try {
						that.tablePlugin.summary.focus();
						e.stopPropagation();
						e.preventDefault();
					} catch (e) {
						Console.error ('Table', e.message);
					}

					return false;
				};
				this.wai = jQuery('<div/>').width(25).height(12).click(clickHandler);
				columnToInsert.append(this.wai);
			}

			// add the cell to the row
			selectionRow.append(columnToInsert);
		}

		// global mouseup event to reset the selection properties
		jQuery(document).bind('mouseup', function(e) { that.columnSelectionMouseUp(e) } );

		this.obj.find('tr:first').before( selectionRow );
	};

	/**
	 * Binds the events for the column selection to the given cell.
	 *
	 * @param cell
	 *            the jquery object of the td-field
	 * @return void
	 */
	Table.prototype.attachColumnSelectEventsToCell = function ( cell ) {
		var that = this;

		// unbind eventually existing events of this cell
		cell.unbind( 'mousedown' );
		cell.unbind( 'mouseover' );

		// prevent ie from selecting the contents of the table
		cell.get( 0 ).onselectstart = function () { return false; };

		cell.bind( 'mousedown',  function ( e ) { that.columnSelectionMouseDown( e ) } );
		cell.bind( 'mouseover',  function ( e ) { that.columnSelectionMouseOver( e ) } );
	};

	/**
	 * Handles the mouse-down event for the selection-cells on the top of the
	 * menu
	 *
	 * @param {jQuery:Event} jqEvent - the jquery-event object
	 * @return void
	 */
	Table.prototype.columnSelectionMouseDown = function ( jqEvent ) {
		// focus the table (if not already done)
		this.focus();

		// if no cells are selected, reset the selection-array
		if ( this.selection.selectedCells.length == 0 ) {
			this.columnsToSelect = [];
		}

		// set the origin-columnId of the mouse-click
		this.clickedColumnId = jQuery( jqEvent.currentTarget.parentNode )
									.children().index( jqEvent.currentTarget );

		// set single column selection
		if ( jqEvent.metaKey ) {
			var arrayIndex = jQuery.inArray( this.clickedColumnId, this.columnsToSelect );
			if ( arrayIndex >= 0 ) {
				this.columnsToSelect.splice( arrayIndex, 1 );
			} else {
				this.columnsToSelect.push( this.clickedColumnId );
			}
		// block of columns selection
		} else if ( jqEvent.shiftKey ) {
			this.columnsToSelect.sort( function( a, b ) { return a - b; } );
			var start = this.columnsToSelect[ 0 ];
			var end = this.clickedColumnId;
			if ( start > end ) {
				start = end;
				end = this.columnsToSelect[ 0 ];
			}
			this.columnsToSelect = [];
			for ( var i = start; i <= end; i++ ) {
				this.columnsToSelect.push( i );
			}
		// single column
		} else {
			this.columnsToSelect = [ this.clickedColumnId ];
		}

		// mark the selection visual
		this.selectColumns();

		// prevent browser from selecting the table
		jqEvent.preventDefault();

		// stop bubble, otherwise the mousedown of the table is called ...
		jqEvent.stopPropagation();

		// prevent ff/chrome/safare from selecting the contents of the table
		return false;
	};

	/**
	 * Mouseover-event for the column-selection cell. This method calcluates the
	 * span between the clicked column and the mouse-overed cell and selects the
	 * columns inbetween. and mark them as selected
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.columnSelectionMouseOver = function (jqEvent) {

		var
			colIdx = jqEvent.currentTarget.cellIndex,
			columnsToSelect = [],
			start,
			end;

		// select all columns from the last clicked to the hoverd
		if ( this.mouseDownColIdx ) {
			start = (colIdx < this.mouseDownColIdx) ? colIdx : this.mouseDownColIdx;
			end = (colIdx < this.mouseDownColIdx) ? this.mouseDownColIdx : colIdx;
			for (var i = start; i <= end; i++) {
				columnsToSelect.push(i);
			}
			this.selectColumns( columnsToSelect );
		}
	};

	/**
	 * MouseUp-event for the column-selection. This method resets the
	 * selection mode
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.columnSelectionMouseUp = function (jqEvent) {
		this.mouseDownColIdx = false;
	};

	/**
	 * Deletes the selected rows. If no row are selected, delete the row, where the
	 * cursor is positioned. If all rows of the table should be deleted, the whole
	 * table is deletet and removed from the tableRegistry.
	 *
	 * @return void
	 */
	Table.prototype.deleteRows = function() {
		var
			rowIDs = [],
			rowsToDelete = {},
			table = this;

		// if a selection was made, delete the selected cells
		if (0 === this.selection.selectedCells.length) {
			return;
		}

		for (var i = 0; i < this.selection.selectedCells.length; i++) {
			rowsToDelete[this.selection.selectedCells[i].parentNode.rowIndex] = true;
		}

	    for (rowId in rowsToDelete) {
	       rowIDs.push(rowId);
	    }

		// if all rows should be deleted, set a flag to remove the WHOLE table
		var deleteTable = false;
		if (rowIDs.length == this.numRows) {
			deleteTable = true;
		}

		// delete the whole table
		if (deleteTable) {
			var that = this;
			Dialog.confirm({
				title : i18n.t('Table'),
				text : i18n.t('deletetable.confirm'),
				yes : function () {
					that.deleteTable();
				}
			});
		} else {

			rowIDs.sort(function(a,b){return a - b;});

			// check which cell should be focused after the deletion
			var focusRowId = rowIDs[0];
			if (focusRowId > (this.numRows - rowIDs.length)) {
				focusRowId --;
			}

			// get all rows
			var rows = this.getRows();

			//splits all cells on the rows to be deleted
			jQuery.each( rowIDs, function ( unused, rowId ) {
				var row = rows[ rowId ];
				for (var i = 0; i < row.cells.length; i++) {
					Utils.splitCell( row.cells[i], function () {
						return table.newActiveCell().obj;
					});
				}
			});

			//decreases rowspans of cells that span the row to be deleted
			//and removes the row
			var grid = Utils.makeGrid( rows );
			jQuery.each( rowIDs, function ( unused, rowId ) {
				var row = grid[ rowId ];
				for ( var j = 0; j < row.length; ) {
					var cellInfo = row[ j ];
					var rowspan = Utils.rowspan( cellInfo.cell );
					if ( 1 < rowspan ) {
						jQuery( cellInfo.cell ).attr( 'rowspan', rowspan - 1);
					}
					j += cellInfo.colspan;
				}
				jQuery( rows[ rowId ] ).remove();
			});

			// reduce the attribute storing the number of rows in the table
			this.numRows -= rowIDs.length;

			// IE needs a timeout to work properly
			window.setTimeout( function() {
				var lastCell = jQuery( rows[1].cells[ focusRowId +1 ] );
				lastCell.focus();
			}, 5);

			// finally unselect the marked cells
			this.selection.unselectCells();
		}
	};

	/**
	 * Deletes the selected columns. If no columns are selected, delete the column, where the
	 * cursor is positioned. If all columns of the table should be deleted, the whole
	 * table is deleted from the dom and removed from the tableRegistry.
	 *
	 * @return void
	 */
	Table.prototype.deleteColumns = function() {
		var
			colIDs = [],
			cellToDelete = [],
			// get all rows to iterate
		    rows = this.getRows(),
			that = this,
			changeColspan = [],
			cells,
			cellInfo;

		var grid = Utils.makeGrid(rows);
		var selectColWidth = 1; //width of the select-row column
		var selectedColumnIdxs = this.selection.selectedColumnIdxs;
		// if at least on whole table column was selected using cell selection
		// it should also be possible to delete the column
		// therefore we need to determine which columns are selected using the 
		// current rectangle 
		if (
			(!selectedColumnIdxs || selectedColumnIdxs.length === 0) &&
			// check if the current rectangle is active
			this.selection.currentRectangle &&
			// check if a whole column is selected
			this.selection.currentRectangle.top === 1 &&
			this.selection.currentRectangle.bottom >= this.numRows &&
			// check if there are really meaningful values in the rectangle
			this.selection.currentRectangle.right > 0 &&
			this.selection.currentRectangle.left > 0
		) {
			selectedColumnIdxs = [];
			for (var l = this.selection.currentRectangle.left; l <= this.selection.currentRectangle.right; l++) {
				selectedColumnIdxs.push(l);
			}
		}

		// if all columns should be deleted, remove the WHOLE table
		// delete the whole table
		if ( selectedColumnIdxs.length == grid[0].length - selectColWidth ) {

			Dialog.confirm({
				title : i18n.t('Table'),
				text : i18n.t('deletetable.confirm'),
				yes : function () {
					that.deleteTable();
				}
			});

		} else {

			colIDs.sort(function(a,b) {return a - b;} );

			//TODO there is a bug that that occurs if a column is
			//selected and deleted, and then a column with a greater
			//x-index is selected and deleted.

			//sorted so we delete from right to left to minimize interfernce of deleted rows
			
			var gridColumns = selectedColumnIdxs.sort(function(a,b){ return b - a; });
			for (var i = 0; i < gridColumns.length; i++) {
				var gridColumn = gridColumns[i];
				for (var j = 0; j < rows.length; j++) {
					var cellInfo = grid[j][gridColumn];
					if ( ! cellInfo ) {
						//TODO this case occurred because of a bug somewhere which should be fixed
						continue;
					}
					if ( 0 === cellInfo.spannedX ) {
						if (1 < cellInfo.colspan) {
							var nCell = this.newActiveCell().obj;
							jQuery( cellInfo.cell ).after(nCell);
							nCell.attr('rowspan', cellInfo.rowspan);
							nCell.attr('colspan', cellInfo.colspan - 1);
						}
						jQuery( cellInfo.cell ).remove();
					} else {
						jQuery( cellInfo.cell ).attr('colspan', cellInfo.colspan - 1);
					}
					//ensures that always 0 === cellInfo.spannedY
					j += cellInfo.rowspan - 1;
				}
				//rebuild the grid to reflect the table structure change
				grid = Utils.makeGrid(rows);
			}

			// reduce the attribute storing the number of rows in the table
			this.numCols -= colIDs.length;

			// IE needs a timeout to work properly
			window.setTimeout( function() {
				var lastCell = jQuery( rows[1].cells[1] );
				lastCell.focus();
			}, 5);

			this.selection.unselectCells();
		}
	};

	/**
	 * Deletes the table from the dom and remove it from the tableRegistry.
	 *
	 * @return void
	 */
	Table.prototype.deleteTable = function() {

		var deleteIndex = -1;
		for (var i = 0; i < this.tablePlugin.TableRegistry.length; i++){
			if (this.tablePlugin.TableRegistry[i].obj.attr('id') == this.obj.attr('id')) {
				deleteIndex = i;
				break;
			}
		}
		if (deleteIndex >= 0) {
			// before deleting the table, deactivate it
			this.deactivate();

			this.selection.selectionType = undefined;
			this.tablePlugin.TableRegistry.splice(i, 1);

			// we will set the cursor right before the removed table
			var newRange = Aloha.Selection.rangeObject;
			// TODO set the correct range here (cursor shall be right before the removed table)
			newRange.endContainer = this.obj.get(0).parentNode;
			newRange.startContainer = newRange.endContainer;

			newRange.endOffset = Dom.getIndexInParent(this.obj.get(0));
			newRange.startOffset = newRange.endOffset;

			newRange.clearCaches();

			this.obj.remove();

			// IE needs a timeout to work properly
			window.setTimeout( function() {
				this.parentEditable.obj.focus();
			}, 5);

			// select the new range
			newRange.correctRange();
			newRange.select();
		}
	};

	/**
	 * @param {string} position
	 *            could be 'after' or 'before'. defines the position where the new
	 *            rows should be inserted
	 */
	function rowIndexFromSelection( position, selection ) {

		var newRowIndex = -1;

		// get the index where the new rows should be inserted
		var cellOfInterest = null;
		if ( 'before' === position ) {
			cellOfInterest = selection.selectedCells[ 0 ];
		} else if ( 'after' === position ) {
			var offset = selection.selectedCells.length - 1;
			cellOfInterest = selection.selectedCells[ offset ];
		}

		if (cellOfInterest && cellOfInterest.nodeType == 1) {
			newRowIndex = cellOfInterest.parentNode.rowIndex;
		}

		return newRowIndex;
	}

	/**
	 * Wrapper function for this.addRow to add a row before the active row
	 *
	 * @see Table.prototype.addRow
	 */
	Table.prototype.addRowBeforeSelection = function(highlightNewRows) {
		var newRowIndex = rowIndexFromSelection( 'before', this.selection );
		if ( -1 !== newRowIndex ) {
			this.addRow( newRowIndex );
		}
	};

	/**
	 * Wrapper function for this.addRow to add a row after the active row
	 *
	 * @see Table.prototype.addRow
	 */
	Table.prototype.addRowAfterSelection = function() {
		var newRowIndex = rowIndexFromSelection( 'after', this.selection );
		if ( -1 !== newRowIndex ) {
			this.addRow( newRowIndex + 1 );
		}
	};

	/**
	 * Adds a new row to the table.
	 *
	 * @param {int} rowIndex
	 *        the index at which the new row shall be inserted
	 * @return <HTMLElemenet> last row inserted
	 */
	Table.prototype.addRow = function(newRowIndex) {
		var rowsToInsert = 1;
		var $insertionRow;
		var classSelectionColumn = this.get('classSelectionColumn');

		var $rows = this.obj.children().children('tr');
		for (var j = 0; j < rowsToInsert; j++) {
			$insertionRow = jQuery('<tr>');

			// create the first column, the "select row" column
			var $selectionColumn = jQuery('<td>');
			$selectionColumn.addClass(classSelectionColumn);
			this.attachRowSelectionEventsToCell($selectionColumn);
			$insertionRow.append($selectionColumn);

			var grid = Utils.makeGrid($rows);
			var selectColOffset = 1;
			if ( newRowIndex >= grid.length ) {
				for (var i = selectColOffset; i < grid[0].length; i++) {
					$insertionRow.append(this.newActiveCell().obj);
				}
			} else {
				var newRow = grid[newRowIndex];
				for (var i = selectColOffset, len = newRow.length; i < len; ) {
					var cellInfo = newRow[i];
					if (Utils.containsDomCell(cellInfo)) {
						var colspan = cellInfo.colspan;
						while (colspan--) {
							$insertionRow.append(this.newActiveCell().obj);
						}
					} else {
						jQuery( cellInfo.cell ).attr('rowspan', cellInfo.rowspan + 1);
					}
					i += cellInfo.colspan;
				}
			}

			if ( newRowIndex >= $rows.length ) {
				$rows.eq( $rows.length - 1 ).after( $insertionRow );
			} else {
				$rows.eq( newRowIndex ).before( $insertionRow );
			}
		}

		this.numRows += rowsToInsert;

		return $insertionRow[0];
	};

	/**
	 * Wrapper method to add columns on the right side
	 *
	 * @see Table.addColumns
	 * @return void
	 */
	Table.prototype.addColumnsRight = function () {
		this.addColumns('right');
	};

	/**
	 * Wrapper method to add columns on the left side
	 *
	 * @see Table.addColumns
	 * @return void
	 */
	Table.prototype.addColumnsLeft = function() {
		this.addColumns('left');
	};

	/**
	 * Inserts new columns into the table. Either on the right or left side. If
	 * columns are selected, the amount of selected columns will be inserted on the
	 * 'right' or 'left' side. If no cells are selected, 1 new column will be
	 * inserted before/after the column of the last active cell.
	 * As well all column-selection events must be bound to the firsts row-cell.
	 *
	 * @param position
	 *            could be 'left' or 'right'. defines the position where the new
	 *            columns should be inserted
	 * @return void
	 */
	Table.prototype.addColumns = function( position ) {
		var
			that = this,
			emptyCell = jQuery( '<td>' ),
		    rows = this.getRows(),
			cell,
			currentColIdx,
			columnsToSelect = [],
			selectedColumnIdxs = this.selection.selectedColumnIdxs;

		if ( 0 === selectedColumnIdxs.length ) {
			return;
		}

		selectedColumnIdxs.sort( function ( a, b ) { return a - b; } );

		// refuse to insert a column unless a consecutive range has been selected
		if ( ! Utils.isConsecutive( selectedColumnIdxs ) ) {
			Dialog.alert( {
				title : i18n.t( 'Table' ),
				text  : i18n.t( 'table.addColumns.nonConsecutive' )
			});
			return;
		}

		if ( 'left' === position ) {
			currentColIdx = selectedColumnIdxs[ 0 ];
			// inserting a row before the selected column indicies moves
			// all selected columns one to the right
			for ( var i = 0; i < this.selection.selectedColumnIdxs.length; i++ ) {
				this.selection.selectedColumnIdxs[ i ] += 1;
			}
		} else {//"right" == position
			currentColIdx = selectedColumnIdxs[ selectedColumnIdxs.length - 1 ];
		}

		var grid = Utils.makeGrid( rows );

		for ( var i = 0; i < rows.length; i++ ) {
			// prepare the cell to be inserted
			cell = emptyCell.clone();
			cell.html( '\u00a0' );

			// on first row correct the position of the selected columns
			if ( i == 0 ) {
				// this is the first row, so make a column-selection cell
				this.attachColumnSelectEventsToCell( cell );
			} else {
				// activate the cell for this table
				cellObj = this.newActiveCell( cell.get(0) );
				cell = cellObj.obj;
			}

			var leftCell = Utils.leftDomCell( grid, i, currentColIdx );
			if ( null == leftCell ) {
				jQuery( rows[i] ).prepend( cell );
			} else {
				if ( 'left' === position && Utils.containsDomCell( grid[ i ][ currentColIdx ] ) ) {
					jQuery( leftCell ).before( cell );
				} else {//right
					jQuery( leftCell ).after( cell );
				}
			}

			this.numCols++;
		}
	};

	/**
	 * Helper method to set the focus-attribute of the table to true
	 *
	 * @return void
	 */
	Table.prototype.focus = function() {
		if (!this.hasFocus) {
			if (!this.parentEditable.isActive) {
				this.parentEditable.obj.focus();
			}

			// @iefix
			this.tablePlugin.setFocusedTable(this);

			// select first cell
			// TODO put cursor in first cell without selecting
			//var firstCell = this.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0);
			//jQuery(firstCell).get(0).focus();

		}

		// TODO workaround - fix this. the selection is updated later on by the browser
		// using setTimeout here is hideous, but a simple execution-time call will fail
	// DEACTIVATED by Haymo prevents selecting rows
	//	setTimeout('Aloha.Selection.updateSelection(false, true)', 50);

	};

	/**
	 * Helper method to set the focus-attribute of the table to false
	 *
	 * @return void
	 */
	Table.prototype.focusOut = function() {
		if (this.hasFocus) {
			this.tablePlugin.setFocusedTable(undefined);
			this.selection.selectionType = undefined;
		}
	};

	/**
	 * Undoes the cursor-selection after cells have been selected.  This
	 * is done to be more consistent in the UI - there should either be
	 * a cursor-selection or a cell-selection, but not both.
	 */
	Table.prototype._removeCursorSelection = function() {
		// We can't remove the selection on IE because whenever a
		// row/column is selected, and then another row/column is
		// selected, the browser windows scrolls to the top of the page
		// (som kind of browser bug).

		// This is no problem for IE because IE removes the
		// cursor-selection by itself and shows a frame around the
		// table, with resize handles (the frame seems useless).

		// On other browsers, we can't remove the selection because the
		// floating menu will disappear when one selects a rows/column
		// and types a key (that's the same effect as when one clicks
		// outside the editable).

		//TODO: currently, removing the cursor selection can't be
		//     reliably implemented.
		//if ( ! jQuery.browser.msie ) {
		//    Aloha.getSelection().removeAllRanges();
		//}

		// The following is a workaround for the above because we can't
		// leave the cursor-selection outside of the table, since
		// otherwise the floating menu scope will be incorrect when one
		// CTRL-clicks on the rows or columns.

		var selection = Aloha.getSelection();

		if ( !selection ||
				!selection._nativeSelection ||
					selection._nativeSelection._ranges.length == 0 ) {
			return;
		}

		var range = selection.getRangeAt( 0 );
		if ( null == range.startContainer ) {
			return;
		}

		// if the selection is  already in the table, do nothing
		if ( 0 !== jQuery( range.startContainer ).closest('table').length ) {
			return;
		}

		// if no cells are selected, do nothing
		if ( 0 === this.selection.selectedCells.length ) {
			return;
		}

		// set the foces to the first selected cell
		var container = TableCell.getContainer( this.selection.selectedCells[ 0 ] );
		jQuery( container ).focus();
	}

	/**
	 * Marks all cells of the specified column as marked (adds a special class)
	 *
	 * @return void
	 */
	Table.prototype.selectColumns = function ( columns ) {
		var columnsToSelect;

		if (columns) {
			columnsToSelect = columns;
		} else {
			columnsToSelect = this.columnsToSelect;
		}

		this.selection.selectColumns(columnsToSelect);
		this.tablePlugin._columnheaderButton.setState(this.selection.isHeader());

		// ====== BEGIN UI specific code - should be handled on event aloha-table-selection-changed by UI =======

		setActiveStyle(this.selection.selectedCells, this.tablePlugin.columnConfig,
				this.tablePlugin.columnMSItems,	this.tablePlugin.columnMSButton);

		// ====== END UI specific code - should be handled by UI =======

		// blur all editables within the table
		this.obj.find('div.aloha-ui-table-cell-editable').blur();

		Utils.selectAnchorContents(this.selection.selectedCells);

		this.selection.notifyCellsSelected();
		this._removeCursorSelection();
	};

	/**
	 * Marks all cells of the specified row as marked (adds a special class)
	 *
	 * @return void
	 */
	Table.prototype.selectRows = function () {

		this.selection.selectRows(this.rowsToSelect);
		this.tablePlugin._rowheaderButton.setState(this.selection.isHeader());

		// ====== BEGIN UI specific code - should be handled on event aloha-table-selection-changed by UI =======

		setActiveStyle(this.selection.selectedCells, this.tablePlugin.rowConfig,
				this.tablePlugin.rowMSItems, this.tablePlugin.rowMSButton);

		// ====== END UI specific code - should be handled by UI =======

		// blur all editables within the table
		this.obj.find('div.aloha-ui-table-cell-editable').blur();

		Utils.selectAnchorContents(this.selection.selectedCells);

		this.selection.notifyCellsSelected();
		this._removeCursorSelection();
	};

	/**
	 * Deactivation of a Aloha-table. Clean up ... remove the wrapping div and the
	 * selection-helper divs
	 *
	 * @return void
	 */
	Table.prototype.deactivate = function() {
		// unblockify the table wrapper
		var parent = this.obj.parent();
		if (parent.mahaloBlock) {
			parent.mahaloBlock();
		}

		this.obj.removeClass(this.get('className'));
		if (jQuery.trim(this.obj.attr('class')) == '') {
			this.obj.removeAttr('class');
		}
		this.obj.removeAttr('contenteditable');
	//	this.obj.removeAttr('id');

		// unwrap the selectionLeft-div if available
		if (this.obj.parents('.' + this.get('classTableWrapper')).length){
			this.obj.unwrap();
		}

		// remove the selection row
		this.obj.find('tr.' + this.get('classSelectionRow') + ':first').remove();
		// remove the selection column (first column left)
		var that = this;
		jQuery.each(this.obj.context.rows, function(){
			jQuery(this).children('td.' + that.get('classSelectionColumn')).remove();
		});

		// remove the "selection class" from all td and th in the table
		this.obj.find('td, th').removeClass(this.get('classCellSelected'));
		this.obj.find('td, th').removeClass('aloha-table-cell_active');

		// remove cursor-styles
		this.obj.find('td, th').css('cursor', '');

		this.obj.unbind();
		this.obj.children('tbody').unbind();

		// wrap the inner html of the contentEditable div to its outer html
		for (var i = 0; i < this.cells.length; i++) {
			var Cell = this.cells[i];
			Cell.deactivate();
		}

		// remove editable span in caption (if any)
		this.obj.find('caption div').each(function() {
			jQuery(this).contents().unwrap();
		});

		// better unset ;-) otherwise activate() may think you're activated.
		this.isActive = false;
	};

	/**
	 * Attach the event for column resize for the given cell.
	 * @param {DOMElement} tableCell
	 *
	 * @return void
	 */
	Table.prototype.attachColumnResize = function(cell) {

		var that = this;

		//unbind any exisiting resize event handlers
		that.detachRowColResize( cell );

		var rows = cell.closest( 'tbody' ).children( 'tr' );
		var cellRow = cell.closest( 'tr' );
		var gridId = Utils.cellIndexToGridColumn(rows,
			rows.index(cellRow),
			cellRow.children().index(cell)
		);

		var resizeColumns = function(pixelsMoved) {
			var expandToWidth, reduceToWidth;

			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				var currentCell = jQuery( jQuery( rows[ri] ).children()[ ci ] );

				// skip the select & cells with colspans
				if ( currentCell.hasClass( 'aloha-table-selectrow' ) || currentCell.closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ) || colspan > 1 ) {
					return true;
				}

				if (gridCi === gridId ) {
					if (!reduceToWidth) {
						reduceToWidth = currentCell.width() - pixelsMoved;
					}

					Utils.resizeCellWidth( currentCell, reduceToWidth );

				} else if (gridCi === gridId - 1) {
					if (!expandToWidth) {
						expandToWidth = currentCell.width() + pixelsMoved;
					}

					Utils.resizeCellWidth( currentCell, expandToWidth );

				}

				return true;
			});
		};

		cell.bind('mousedown.resize', function($event) {
			// prevent cell resizing, if mousedown was on a block handle
			if (jQuery($event.target).hasClass('aloha-block-draghandle')) {
				return;
			}

			var $guide = jQuery('<div></div>');
			var isResizing = false;

			Utils.getCellResizeBoundaries(gridId, rows, function(maxPageX, minPageX) {
				isResizing = true;

				// unset the selection type
				that.selection.resizeMode = true;

				// move the guide while dragging
				jQuery( 'body' ).bind( 'mousemove.dnd_col_resize', function(e) {
					// limit the maximum resize
					if ( e.pageX > minPageX && e.pageX < maxPageX ) {
						$guide.css('left', e.pageX);
					}
				});

				// do the actual resizing after drag stops
				jQuery( 'body' ).bind( 'mouseup.dnd_col_resize', function(e) {
					var pixelsMoved = 0;

					if ( e.pageX < minPageX ) {
						pixelsMoved = minPageX - cell.offset().left;
					} else if ( e.pageX > minPageX && e.pageX < maxPageX ) {
						pixelsMoved = e.pageX - cell.offset().left;
					} else if ( e.pageX > maxPageX ) {
						pixelsMoved = maxPageX - cell.offset().left;
					}

					if ( pixelsMoved !== 0 ) {
						resizeColumns( pixelsMoved );
					}

					jQuery( 'body' ).unbind( 'mousemove.dnd_col_resize' );
					jQuery( 'body' ).unbind( 'mouseup.dnd_col_resize' );

					// unset the selection resize mode
					that.selection.resizeMode = false;

					$guide.remove();
				});
			});

			if (!isResizing) {
				return;
			}

			var $cell = jQuery(cell);
			var width = $cell.outerWidth() - $cell.innerWidth();
			var height = $cell.closest('tbody').innerHeight();
			$guide.css({
				'height': (height < 1) ? 1 : height,
				'width': (width < 1) ? 1 : width,
				'top': $cell.closest('tbody').offset().top,
				'left': $cell.offset().left,
				'position': 'absolute',
				'background-color': '#80B5F2'
			}).appendTo('body');
		});

	};

	/**
	 * Attach the event handler for row resize for the given cell.
	 * @param {DOMElement} tableCell
	 *
	 * @return void
	 */
	Table.prototype.attachRowResize = function(cell, lastRow) {

		var that = this;

		//unbind any exisiting resize event handlers
		that.detachRowColResize( cell );

		var resizeRows = function(pixelsMoved) {
			var expandingRow;

			if (lastRow) {
				expandingRow = cell.closest( 'tr' );
			} else {
				expandingRow = cell.closest( 'tr' ).prev( 'tr' );
			}

			var currentRowHeight = expandingRow.height();
			var expandToHeight = currentRowHeight + pixelsMoved;

			// correct if the height is a minus value
			if ( expandToHeight < 0 ) {
				expandToHeight = 1;
			}

			expandingRow.css( 'height', expandToHeight );
		};

		cell.bind('mousedown.resize', function($event) {
			// prevent cell selection, if mousedown was on a block handle
			if (jQuery($event.target).hasClass('aloha-block-draghandle')) {
				return;
			}

			// create a guide
			var guide = jQuery( '<div></div>' );

			var guideTop = function() {
				if (lastRow) {
					return cell.offset().top + cell.outerHeight();
				} else {
					return cell.offset().top;
				}
			};

			var width = cell.closest( 'tbody' ).innerWidth();
			var height = cell.outerHeight() - cell.innerHeight();

			guide.css({
				'width': (width < 1) ? 1 : width,
				'height': (height < 1) ? 1: height,
				'top': guideTop(),
				'left': cell.closest( 'tbody' ).offset().left,
				'position': 'absolute',
				'background-color': '#80B5F2'
			});
			jQuery( 'body' ).append( guide );

			// set the minimum resize
			var minHeight = function() {
				if (lastRow) {
					return cell.closest('tr').offset().top;
				} else {
					return cell.closest('tr').prev( 'tr' ).offset().top;
				}
			};

			// set the selection resize mode
			that.selection.resizeMode = true;

			// move the guide while dragging
			jQuery( 'body' ).bind( 'mousemove.dnd_row_resize', function(e) {
				if ( e.pageY > minHeight() ) {
					guide.css( 'top', e.pageY );
				}
			});

			// do the actual resizing after drag stops
			jQuery( 'body' ).bind( 'mouseup.dnd_row_resize', function(e) {

				var pixelsMoved = 0;

				if (lastRow) {
					pixelsMoved = e.pageY - ( cell.offset().top + cell.outerHeight() );
				} else {
					pixelsMoved = e.pageY - cell.offset().top;
				}

				resizeRows( pixelsMoved );

				jQuery( 'body' ).unbind( 'mousemove.dnd_row_resize' );
				jQuery( 'body' ).unbind( 'mouseup.dnd_row_resize' );

				// unset the selection resize mode
				that.selection.resizeMode = false;

				guide.remove();
			});

		});

	};

	/**
	 * Attach the table width resize event.
	 * @param {DOMElement} table
	 *
	 * @return void
	 */
	Table.prototype.attachTableResizeWidth = function(table) {

		var that = this;
		var tableContainer = table.closest('.aloha-table-wrapper');
		var trSelector = "tr:not(.aloha-table-selectcolumn)";
		var lastColumn = table.find(trSelector + " th:last-child, " +
			                        trSelector + " td:last-child");
		var lastCell;

		jQuery.each( lastColumn, function() {
			// don't use colspanned cell as the base cell
			if ( !jQuery( this ).attr('colspan') || jQuery( this ).attr('colspan') < 2 ) {
				lastCell = jQuery( this );
				return false;
			}
		});

		// change the cursor
		lastColumn.css( 'cursor', 'col-resize' );

		var resizeColumns = function(pixelsMoved) {
			var rows = table.find( 'tr' );
			var lastCellRow = lastCell.closest( 'tr' );
			var gridId = Utils.cellIndexToGridColumn(
				                   rows,
			                      rows.index( lastCellRow ),
			                      lastCellRow.children().index( lastCell )
			                   );
			var expandToWidth = pixelsMoved - Utils.getCellBorder(lastCell) - Utils.getCellPadding(lastCell);

			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				var currentCell = jQuery( jQuery( rows[ri] ).children()[ ci ] )

				// skip the select cells and cells with colspans
				if ( currentCell.hasClass( 'aloha-table-selectrow' ) || currentCell.closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ) || colspan > 1 ) {
					return true;
				}

				if (gridCi === gridId ) {
					Utils.resizeCellWidth( currentCell, expandToWidth );
				} else {
					Utils.resizeCellWidth( currentCell, currentCell.width() );
				}

				return true;
			});
		};

		lastColumn.bind( 'mousedown.resize', function() {

			// create a guide
			var guide = jQuery( '<div></div>' );

			var height = table.children( 'tbody' ).innerHeight();
			var width = lastCell.outerWidth() - lastCell.innerWidth();

			guide.css({
				'height': (height < 1) ? 1 : height,
				'width': (width < 1) ? 1 : width,
				'top': table.find('tbody').offset().top,
				'left': table.offset().left + table.outerWidth(),
				'position': 'absolute',
				'background-color': '#80B5F2'
			});
			jQuery('body').append( guide );

			// set the maximum and minimum resize
			var maxPageX = tableContainer.offset().left + tableContainer.width();
			var minPageX = lastCell.offset().left + ( lastCell.innerWidth() - lastCell.width() ) + Utils.getMinColWidth( lastCell );

			// unset the selection type
			that.selection.resizeMode = true;

			// move the guide while dragging
			jQuery( 'body' ).bind( 'mousemove.dnd_col_resize', function(e) {
				// limit the maximum resize
				if ( e.pageX > minPageX && e.pageX < maxPageX ) {
					guide.css( 'left', e.pageX );
				}
			});

			// do the actual resizing after drag stops
			jQuery( 'body' ).bind( 'mouseup.dnd_col_resize', function(e) {
				var pixelsMoved = 0;

				if ( e.pageX <= minPageX ) {
				 	pixelsMoved = minPageX - lastCell.offset().left;
				} else if ( e.pageX > minPageX && e.pageX < maxPageX ) {
					pixelsMoved = e.pageX - lastCell.offset().left;
				} else if ( e.pageX > maxPageX ) {
				  pixelsMoved = maxPageX - lastCell.offset().left;
				}

				// set the table width
				resizeColumns( pixelsMoved );

				// unbind the events and reset the cursor
				jQuery( 'body' ).unbind( 'mousemove.dnd_col_resize' );
				jQuery( 'body' ).unbind( 'mouseup.dnd_col_resize' );
				lastColumn.unbind( 'mousedown.resize' );
				lastColumn.css( 'cursor', 'default' );

				// unset the selection resize mode
				that.selection.resizeMode = false;

				guide.remove();
			});

		});

	};

	/**
	 * Detach any column/row resize event handlers attached to the cell.
	 * @param {DOMElement} tableCell
	 *
	 * @return void
	 */
	Table.prototype.detachRowColResize = function(cell) {
		return cell.unbind('mousedown.resize');
	};

	/**
	 * toString-method for Table object
	 *
	 * @return void
	 */
	Table.prototype.toString = function() {
		return 'Table';
	};

	Table.prototype.newCell = function(domElement) {
		return new TableCell(domElement, this);
	};

	Table.prototype.newActiveCell = function(domElement) {
		var cell = new TableCell(domElement, this);
		cell.activate();
		return cell;
	};

	/**
	 * @return the rows of the table as an array of DOM nodes
	 */
	Table.prototype.getRows = function () {
		//W3C DOM property .rows supported by all modern browsers
		var rows = this.obj.get( 0 ).rows;
		//converts the HTMLCollection to a real array
		return jQuery.makeArray( rows );
	};

	return Table;
});

/* table-plugin.js is part of the Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2014 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 * License http://aloha-editor.org/license.php
 */
define('table/table-plugin',[
	'aloha',
	'jquery',
	'PubSub',
	'aloha/plugin',
	'aloha/pluginmanager',
	'aloha/content-rules',
	'ui/ui',
	'ui/scopes',
	'ui/button',
	'ui/toggleButton',
	'ui/dialog',
	'ui/port-helper-attribute-field',
	'ui/port-helper-multi-split',
	'i18n!table/nls/i18n',
	'i18n!aloha/nls/i18n',
	'table/table-create-layer',
	'table/table',
	'table/table-plugin-utils',
	'table/table-selection',
	'util/dom',
	'aloha/ephemera',
	'aloha/console'
], function (
	Aloha,
	$,
	PubSub,
	Plugin,
	PluginManager,
	ContentRules,
	Ui,
	Scopes,
	Button,
	ToggleButton,
	Dialog,
	AttributeField,
	MultiSplitButton,
	i18n,
	i18nCore,
	CreateLayer,
	Table,
	Utils,
	TableSelection,
	Dom,
	Ephemera,
	Console
) {
	var jQuery = $;
	var GENTICS = window.GENTICS;
	var configurations = {};

	/**
	 * Register the TablePlugin as Aloha.Plugin
	 */
	var TablePlugin = new Plugin('table');

	/**
	 * The Create-Layer Object of the TablePlugin
	 *
	 * @see Table.CreateLayer
	 */
	TablePlugin.createLayer = undefined;

	/**
	 * default button configuration
	 */
	TablePlugin.config = [ 'table' ];

	/**
	 * An Array which holds all newly created tables contains DOM-Nodes of
	 * table-objects
	 */
	TablePlugin.TableRegistry = new Array();

	/**
	 * Holds the active table-object
	 */
	TablePlugin.activeTable = undefined;

	/**
	 * parameters-objects for tables
	 *
	 * @param className
	 *            The class of activated tables
	 */
	TablePlugin.parameters = {
		className            : 'aloha-table',                 // class of editable tables
		classSelectionRow    : 'aloha-table-selectcolumn',    // class for the upper table-row to select columns
		classSelectionColumn : 'aloha-table-selectrow',       // class for the left bound table-cells to select rows
		classLeftUpperCorner : 'aloha-table-leftuppercorner', // class for the left upper corner cell
		classTableWrapper    : 'aloha-table-wrapper',         // class of the outest table-wrapping div
		classCellSelected    : 'aloha-cell-selected',         // class of cell which are selected (row/column selection)
		waiRed               : 'aloha-wai-red',               // class that shows wai of div
		waiGreen             : 'aloha-wai-green',             // class that shows wai of div
		selectionArea        : 10                             // width/height of the selection rows (in pixel)
	};

	/**
	 * @hide
	 * {name:'green', text:'Green',tooltip:'Green',iconClass:'GENTICS_table GENTICS_button_green',cssClass:'green'}
	 */
	TablePlugin.checkConfig = function (c){
		if (typeof c == 'object' && c.length) {
			var newC = [];

			for (var i = 0; i < c.length; i++) {
				if (c[i]) {
					newC.push({
						name      : c[i].name,
						text	  : c[i].text	   ? c[i].text		: c[i].name,
						tooltip	  : c[i].tooltip   ? c[i].tooltip	: c[i].text,
						iconClass : c[i].iconClass ? c[i].iconClass	: 'aloha-icon-' + c[i].name,
						cssClass  : c[i].cssClass  ? c[i].cssClass	: c[i].name
					});
				}
			}

			c = newC;
		} else {
			c = [];
		}

		return c;
	};

	/**
	 * Update the WAI image
	 *
	 * @param {TablePlugin} tablePlugin tablePlugin
	 */
	function updateWaiImage(tablePlugin) {
		var $element = $(tablePlugin.summary.getInputElem()),
			waiRed = tablePlugin.activeTable.get('waiRed'),
			waiGreen = tablePlugin.activeTable.get('waiGreen');

		$element.removeClass(waiRed + ' ' + waiGreen);
		if (tablePlugin.activeTable.checkWai()) {
			$element.addClass(waiGreen);
		}
		else {
			$element.addClass(waiRed);
		}
	}

	/**
	 * Checks whether the given DOM element is nested within a table.
	 *
	 * @param {jQuery.<HTMLElement>} $element
	 * @return {boolean} True if the given element is nested in a table.
	 */
	function isWithinTable($element) {
		return 0 < $element.parents('.aloha-editable table').length;
	}

	/**
	 * Checks whether the given DOM element is nested within an aloha block.
	 *
	 * @param {jQuery.<HTMLElement>} $element
	 * @return {boolean} True if the given element is nested in an aloha block.
	 */
	function isWithinBlock($element) {
		var i;
		var $node;
		var $parents = $element.parents();
		for (i = 0; i < $parents.length; i++) {
			$node = $parents.eq(i);
			if ($node.is('.aloha-editable')) {
				return false;
			}
			if ($node.is('.aloha-block')) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Checks whether the table is an editable element.
	 *
	 * @return {boolean} True if the table's parent element is contentEditable;
	 *                   false otherwise.
	 */
	function isEditableTable(table) {
		return GENTICS.Utils.Dom.isEditable(table);
	}

	/**
	 * Checks for the presence of nested tables in the given element.
	 *
	 * @param {jQuery.<HTMLElement>} $element jQuery unit set containing a DOM
	 *                                        element.
	 * @return {boolean} True if nested tables were detected; false otherwise.
	 */
	function checkForNestedTables($element) {
		var selector = $element.is('table') ? 'table' : 'table table';
		if ($element.find(selector).length) {
			Console.warn('Table Plugin',
					'Nested tables found. They will not be initialized.');
			return true;
		}
		return false;
	}

	/**
	 * Creates a table, if it is allowed, and registers a new Table object for
	 * the given table DOM element.
	 *
	 * If the table's editable parent is activated, the table will also
	 * automatically be activated.
	 *
	 * @param {HTMLElement} element HTML table element.
	 * @return {Table|null} The created Table object or null if it was not
	 *                      allowed to create the table.
	 */
	function createNewTable(element) {
		var $table = $(element);
		var create = isEditableTable(element)
		          && !isWithinTable($table)
		          && !isWithinBlock($table);
		if (create) {
			var table = new Table(element, TablePlugin);
			var $host = $(Dom.getEditingHostOf(element));
			table.parentEditable = Aloha.getEditableById($host.attr('id'));
			TablePlugin.TableRegistry.push(table);
			checkForNestedTables($table);
			if (Aloha.activeEditable === table.parentEditable) {
				table.activate();
			}
			return table;
		}
		return null;
	}

	/**
	 * Sets the currently selected elements as headers of the table, or removes header-status
	 * if the whole selection is already used as a header
	 *
	 * @param {Aloha.Table} table the table-object for which the headers are to be set
	 * @param {string} scope for which the header should be used (i.e. 'row' or 'column')
	 */
	function toggleHeaderStatus(table, scope) {
		var	i,
			j,
			allHeaders = table.selection.isHeader(),
			domCell, // representation of the cell in the dom
			bufferCell; // temporary buffer

		for (i = 0; i < table.selection.selectedCells.length; i++) {
			domCell = table.selection.selectedCells[i];

			// tries to match the current cell with a cell-object in the table
			for (j = 0; j < table.cells.length; j++) {
				if (domCell === table.cells[j].obj[0]) {
					cell = table.cells[j];
					break;
				}
			}

			// the transformed dom objects are first stored in a buffer, and only applied to
			// the table-cell-object if a match was found
			if (allHeaders) {
				bufferCell = Aloha.Markup.transformDomObject(domCell, 'td').removeAttr('scope').get(0);
			} else {
				bufferCell = Aloha.Markup.transformDomObject(domCell, 'th').attr('scope', scope).get(0);
			}

			if (cell != null) {
				// assign the changed dom-element to the table-cell
				cell.obj[0] = bufferCell;

				// reactivate the table cell in order to bind events to the changed dom object
				// TODO: re-attaching event-handlers should be factored out into a utility function
				// so we don't have to do the whole activation/deactivation process for the cells
				cell.deactivate();
				cell.activate();
			}

			// uncommented code-segment, presumably added to force IE to target the wrapper
			// on mouse-down by applying a timeout after event propagation
			jQuery(table.selection.selectedCells[i]).bind('mousedown', function (jqEvent) {
				var wrapper = jQuery(this).children('div').eq(0);
				window.setTimeout(function () {
					wrapper.trigger( 'focus' );
				}, 1);
			});
		}
	}

	/**
	 * If the specified style is not already active in all selected cells, it is applied;
	 * otherwise, it is removed from the cells
	 *
	 * @param {Array} config defined styles as defined in the configuration
	 * @param {String} cssClass
	 * @param {Array} sc the selection of target table cells
	 */
	function applyStyle(config, cssClass, sc) {
		var appliedToAll = true;

		for (var i = 0; i < sc.length; i++) {
			if (jQuery(sc[i]).attr('class').indexOf(cssClass) < 0 ) {
				appliedToAll = false;
				break;
			}
		}

		if (!appliedToAll) {
			for (var i = 0; i < sc.length; i++) {
				jQuery(sc[i]).addClass(cssClass);
				for (var f = 0; f < config.length; f++) {
					if (config[f].cssClass != cssClass) {
						jQuery(sc[i]).removeClass(config[f].cssClass);
					}
				}
			}
		} else {
			for (var i = 0; i < sc.length; i++) {
				jQuery(sc[i]).removeClass(cssClass);
			}
		}
	}

	/**
	 * Init method of the Table-plugin transforms all tables in the document
	 *
	 * @return void
	 */
	TablePlugin.init = function() {
		var that = this;

		Ephemera.classes(this.get('className'), this.get('classCellSelected'));

		this.tableConfig  = this.checkConfig(this.tableConfig  || this.settings.tableConfig);
		this.columnConfig = this.checkConfig(this.columnConfig || this.settings.columnConfig);
		this.rowConfig    = this.checkConfig(this.rowConfig    || this.settings.rowConfig);
		this.cellConfig   = this.checkConfig(this.cellConfig   || this.settings.cellConfig);

		this.tableResize = this.settings.tableResize === undefined ? false : this.settings.tableResize;
		this.colResize   = this.settings.colResize   === undefined ? false : this.settings.colResize;
		this.rowResize   = this.settings.rowResize   === undefined ? false : this.settings.rowResize;

		// disable table resize settings on browsers below IE8
		if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) {
			this.tableResize = false;
			this.colResize = false;
			this.rowResize = false;
		}

		// add reference to the create layer object
		this.createLayer = new CreateLayer(this);

		PubSub.sub('aloha.editable.created', function (message) {
			var editable = message.editable;
			var config = that.getEditableConfig(editable.obj);
			var enabled = config
			           && ($.inArray('table', config) > -1)
			           && ContentRules.isAllowed(editable.obj[0], 'table');

			configurations[editable.getId()] = !!enabled;

			editable.obj.bind('mousedown', function () {
				TablePlugin.setFocusedTable(undefined);
			});

			editable.obj.find('table').each(function (index, elem) {
				createNewTable(elem);
			});
		});

		// initialize the table buttons
		this.initTableButtons();

		Aloha.bind( 'aloha-table-selection-changed', function () {
			// check if selected cells are split/merge able and set button status
			if (!TablePlugin.activeTable || !TablePlugin.activeTable.selection) {
				return;
			}

			TablePlugin.updateFloatingMenuScope();

			if (TablePlugin.activeTable.selection.cellsAreSplitable()) {
				that._splitcellsButton.enable(true);
				that._splitcellsRowButton.enable(true);
				that._splitcellsColumnButton.enable(true);
			} else {
				that._splitcellsButton.enable(false);
				that._splitcellsRowButton.enable(false);
				that._splitcellsColumnButton.enable(false);
			}

			if (TablePlugin.activeTable.selection.cellsAreMergeable()) {
				that._mergecellsButton.enable(true);
				that._mergecellsRowButton.enable(true);
				that._mergecellsColumnButton.enable(true);
			} else {
				that._mergecellsButton.enable(false);
				that._mergecellsRowButton.enable(false);
				that._mergecellsColumnButton.enable(false);
			}
		});

		//		PubSub.sub('aloha.selection.context-change', function (message) {
		// Problem with this PubSub.sub event:
		// This event is only thrown if the context has changed. (selection.js:126:triggerSelectionContextChanged)
		// This makes that the scope changes to Aloha.continuoustext (scopes.js:43).
		// This is because selection is called a least twice (selection.js:525). The second time the context has not changed.
		Aloha.bind('aloha-selection-changed', function (event, rangeObject) {
			var range = rangeObject;
			var editable = Aloha.activeEditable;

			// this case probably occurs when the selection is empty?
			if (!range.startContainer || !editable) {
				TablePlugin.leaveTableScopes();
				return;
			}

			// show hide buttons regarding configuration and DOM position
			if (configurations[Aloha.activeEditable.getId()] && Aloha.Selection.mayInsertTag('table') ) {
				that._createTableButton.show();
			} else {
				that._createTableButton.hide();
			}

			if (!that.activeTable) {
				TablePlugin.leaveTableScopes();
				return;
			}

			var table = range.findMarkup(function () {
				return this.nodeName === 'TABLE';
			}, editable.obj);

			if (table) {
				TablePlugin.updateFloatingMenuScope();
				TablePlugin.setActiveCellStyle();
			} else {
				TablePlugin.leaveTableScopes();
				that.activeTable.selection.cellSelectionMode = false;
				that.activeTable.selection.baseCellPosition = null;
				that.activeTable.selection.lastSelectionRange = null;
				that.activeTable.focusOut();
			}
		});

		PubSub.sub('aloha.editable.activated', function (message) {
			that._splitcellsButton.enable(false);
			that._mergecellsButton.enable(false);
			that._splitcellsRowButton.enable(false);
			that._mergecellsRowButton.enable(false);
			that._splitcellsColumnButton.enable(false);
			that._mergecellsColumnButton.enable(false);

			message.editable.obj.find('table').each(function () {
				var registry = TablePlugin.TableRegistry;
				for (var i = 0; i < registry.length; i++) {
					if (registry[i].obj.attr('id') === jQuery(this).attr('id')) {
						registry[i].activate();
						return true;
					}
				}
				// Because table this is a new table that is not yet in the
				// registry
				createNewTable(this);
			});
		});

		PubSub.sub('aloha.editable.deactivated', function () {
			if (TablePlugin.activeTable) {
				TablePlugin.activeTable.selection.unselectCells();
			}
			TablePlugin.setFocusedTable(undefined);
			var registry = TablePlugin.TableRegistry;
			for (var i = 0; i < registry.length; i++) {
				registry[i].deactivate();
			}
		});

		Aloha.bind('aloha-smart-content-changed', function () {
			if (Aloha.activeEditable) {
				Aloha.activeEditable.obj.find('table').each(function () {
					if (TablePlugin.indexOfTableInRegistry(this) == -1) {
						if (createNewTable(this)) {
							this.id = GENTICS.Utils.guid();
						}
					}
				});
			}
		});

		if (this.settings.summaryinsidebar) {
			Aloha.bind('aloha-plugins-loaded', function () {
				that.initSidebar(Aloha.Sidebar.right.show());
			});
		}
	};

	var tableNamespace = 'aloha-table';

	function nsSel() {
		var stringBuilder = [], prefix = tableNamespace;
		jQuery.each(arguments, function () {
			stringBuilder.push('.' + (this == '' ? prefix : prefix + '-' + this));
		});
		return jQuery.trim(stringBuilder.join(' '));
	}

	//Creates string with this component's namepsace prefixed the each classname
	function nsClass() {
		var stringBuilder = [], prefix = tableNamespace;
		jQuery.each(arguments, function () { 
			stringBuilder.push(this == '' ? prefix : prefix + '-' + this);
		});
		return jQuery.trim(stringBuilder.join(' '));
	}

	TablePlugin.initSidebar = function (sidebar) {
		var pl = this;
		pl.sidebar = sidebar;
		pl.sidebarPanel = sidebar.addPanel({

			id       : nsClass('sidebar-panel'),
			title    : i18n.t('table.sidebar.title'),
			content  : '',
			expanded : true,
			activeOn : 'table',

			onInit   : function () {
				var that = this,
	            content = this.setContent(
	                '<label class="' + nsClass('label') + '" for="' + nsClass('textarea') + '" >' + i18n.t('table.label.target') + '</label>' +
	                	'<textarea id="' + nsClass('textarea') + '" class="' + nsClass('textarea') + '" />').content;

				jQuery(nsSel('textarea')).live('keyup', function () {
					//The original developer thought that escaping the
					//quote characters of the textarea value are
					//necessary to work around a bug in IE. I could not
					//reproduce the bug, so I commented the following
					//out.
					//.replace("\"", '&quot;').replace("'", "&#39;")
 					jQuery(that.effective).attr('summary', jQuery(nsSel('textarea')).val());
 					var waiDiv = jQuery('div[class*="wai"]', 'table#' + jQuery(that.effective).attr('id'));
 					waiDiv.removeClass(pl.get('waiGreen'));
 					waiDiv.removeClass(pl.get('waiRed'));

 					if (jQuery(nsSel('textarea')).val().trim() != '') {
 						waiDiv.addClass(pl.get('waiGreen'));
				    } else {
				    	waiDiv.addClass(pl.get('waiRed'));
				    }
 				});
            },

            onActivate: function (effective) {
            	var that = this;
				that.effective = effective;
				jQuery(nsSel('textarea')).val(jQuery(that.effective).attr('summary'));
            }

        });
		sidebar.show();
	};

	/**
	 * test if the table is editable
	 * @return boolean true if the table's parent element is contentEditable, false otherwise
	 */
	TablePlugin.isEditableTable = function (table) {
		return GENTICS.Utils.Dom.isEditable( table );
	};

	/**
	 * @param {DOMElement} table
	 * @return {Number}
	 */
	TablePlugin.indexOfTableInRegistry = function ( table ) {
		var registry = this.TableRegistry;

		for ( var i = 0; i < registry.length; i++ ) {
			// We need to find exactly the same object from the
			// registry since we could also deal with cloned objects
			if ( registry[ i ].obj[ 0 ].id == table.id ) {
				return i;
			}
		}

		return -1;
	};

	/**
	 * @param {DOMElement} table
	 * @return {Table}
	 */
	TablePlugin.getTableFromRegistry = function ( table ) {
		var i = this.indexOfTableInRegistry( table );
		if ( i > -1 ) {
			return this.TableRegistry[ i ];
		}
		return null;
	};

	/**
	 * Checks whether the current selection is inside a table within an
	 * editable
	 *
	 * @return {Boolean} true if we are inside a table
	 */
	TablePlugin.isSelectionInTable = function () {
		var range = Aloha.Selection.getRangeObject();
		var container = jQuery( range.commonAncestorContainer );

		if ( container.length == 0 ) {
			return  false;
		}

		if ( container.parents( '.aloha-editable table' ).length ) {
			return true;
		}

		return false;
	};

	TablePlugin.preventNestedTables = function () {
		if ( this.isSelectionInTable() ) {
			Dialog.alert({
				title : i18n.t( 'Table' ),
				text  : i18n.t( 'table.createTable.nestedTablesNoSupported' )
			});

			return true;
		}

		return false;
	};

	TablePlugin.initMergeSplitCellsBtns = function(){
		// TODO current it is not possible to add the same buttons to
		//      multiple tabs. To work around this limitation we are
		//      defining the mergecells and splitcells components
		//      multiple times, once for each tab.

		this._mergecellsButton = Ui.adopt("mergecells", Button, {
			tooltip: i18n.t("button.mergecells.tooltip"),
			icon: "aloha-icon aloha-icon-mergecells",
			scope: this.name + '.cell',
			click: function() {
				if (TablePlugin.activeTable) {
					TablePlugin.activeTable.selection.mergeCells();
				}
			}
		});

		this._splitcellsButton = Ui.adopt("splitcells", Button, {
			tooltip: i18n.t("button.splitcells.tooltip"),
			icon: "aloha-icon aloha-icon-splitcells",
			scope: this.name + '.cell',
			click: function() {
				var activeCell;
				if (TablePlugin.activeTable) {
					if (TablePlugin.activeTable.selection.selectedCells.length > 0) {
						TablePlugin.activeTable.selection.splitCells();
					} else {
						// if there is currently no selection, the active cell is split instead
						activeCell = TablePlugin.selectedOrActiveCells();
						if (activeCell.length > 0) {
							Utils.splitCell(activeCell, function () {
								return TablePlugin.activeTable.newActiveCell().obj;
							});
							Aloha.trigger('aloha-table-selection-changed');
						}
					}
				}
			}
		});

		this._mergecellsRowButton = Ui.adopt("mergecellsRow", Button, {
			tooltip: i18n.t("button.mergecells.tooltip"),
			icon: "aloha-icon aloha-icon-mergecells",
			scope: this.name + '.row',
			click: function() {
				if (TablePlugin.activeTable) {
					TablePlugin.activeTable.selection.mergeCells();
				}
			}
		});

		this._splitcellsRowButton = Ui.adopt("splitcellsRow", Button, {
			tooltip: i18n.t("button.splitcells.tooltip"),
			icon: "aloha-icon aloha-icon-splitcells",
			scope: this.name + '.row',
			click: function() {
				if (TablePlugin.activeTable) {
					TablePlugin.activeTable.selection.splitCells();
				}
			}
		});

		this._mergecellsColumnButton = Ui.adopt("mergecellsColumn", Button, {
			tooltip: i18n.t("button.mergecells.tooltip"),
			icon: "aloha-icon aloha-icon-mergecells",
			scope: this.name + '.column',
			click: function() {
				if (TablePlugin.activeTable) {
					TablePlugin.activeTable.selection.mergeCells();
				}
			}
		});

		this._splitcellsColumnButton = Ui.adopt("splitcellsColumn", Button, {
			tooltip: i18n.t("button.splitcells.tooltip"),
			icon: "aloha-icon aloha-icon-splitcells",
			scope: this.name + '.column',
			click: function() {
				if (TablePlugin.activeTable) {
					TablePlugin.activeTable.selection.splitCells();
				}
			}
		});
	};

	TablePlugin.initNaturalFitBtn = function() {
		var that = this;

		if (this.colResize || this.rowResize) {
			this._tableNaturalFitButton = Ui.adopt("naturalFit", Button, {
				tooltip: i18n.t("button.naturalfit.tooltip"),
				icon: "aloha-icon aloha-icon-table-naturalfit",
				scope: this.name + '.cell',
				click: function() {
					if (that.activeTable) {
						var tableObj = that.activeTable.obj;
						tableObj.find('td, th').each(function() {
							jQuery(this).find('div').css('width', '');
							jQuery(this).css('width', '');
						});
						tableObj.find('tr').each(function() {
							jQuery(this).css('height', '');
						});
					}
				}
			});
		}
	};

	/**
	 * Adds default row buttons, and custom formatting buttons to floating menu
	 */
	TablePlugin.initRowsBtns = function () {
		var that = this;

		this._addrowbeforeButton = Ui.adopt("addrowbefore", Button, {
			tooltip: i18n.t( "button.addrowbefore.tooltip"),
			icon: "aloha-icon aloha-icon-addrowbefore",
			scope: this.name + '.row',
			click: function() {
				if (that.activeTable) {
					that.activeTable.addRowBeforeSelection();
				}
			}
		});

		this._addrowafterButton = Ui.adopt("addrowafter", Button, {
			tooltip: i18n.t("button.addrowafter.tooltip"),
			icon: "aloha-icon aloha-icon-addrowafter",
			scope: this.name + '.row',
			click: function() {
				if (that.activeTable) {
					that.activeTable.addRowAfterSelection();
				}
			}
		});

		this._deleterowsButton = Ui.adopt("deleterows", Button, {
			tooltip: i18n.t("button.delrows.tooltip"),
			icon: "aloha-icon aloha-icon-deleterows",
			scope: this.name + '.row',
			click: function() {
				if (that.activeTable) {
					var aTable = that.activeTable;
					Dialog.confirm({
						title: i18n.t('Table'),
						text: i18n.t('deleterows.confirm'),
						yes: function(){
							aTable.deleteRows();
						}
					});
				}
			}
		});

		this._rowheaderButton = Ui.adopt("rowheader", ToggleButton, {
			tooltip: i18n.t("button.rowheader.tooltip"),
			icon: "aloha-icon aloha-icon-rowheader",
			scope: this.name + '.row',
			click: function() {
				if (that.activeTable) {
					that.activeTable.refresh();

					toggleHeaderStatus(that.activeTable, 'col');

					// Update selection to the new row
					that.activeTable.selection.selectRows(that.activeTable.selection.selectedRowIdxs);
					that.activeTable.selection.unselectCells();
				}
			}
		});

		// generate formatting buttons
		this.rowMSItems = [];
		jQuery.each(this.rowConfig, function (j, itemConf) {
			that.rowMSItems.push({
				name: itemConf.name,
				text: i18n.t(itemConf.text),
				tooltip: i18n.t(itemConf.tooltip),
				iconClass: 'aloha-icon aloha-row-layout ' + itemConf.iconClass,
				click: function () {
					if (that.activeTable) {
						applyStyle(that.rowConfig, itemConf.cssClass, that.activeTable.selection.selectedCells);

						// selection could have changed.
						that.activeTable.selectRows();
					}
				}
			});
		});

		if (this.rowMSItems.length > 0) {
			this.rowMSItems.push({
				name    : 'removeFormat',
				text    : i18n.t('button.removeFormat.text'),
				tooltip : i18n.t('button.removeFormat.tooltip'),
				'cls'   : 'aloha-ui-multisplit-fullwidth',
				wide    : true,
				click   : function () {
					if (that.activeTable) {
						var sc = that.activeTable.selection.selectedCells;
						// if a selection was made, transform the selected cells
						for (var i = 0; i < sc.length; i++) {
							for (var f = 0; f < that.rowConfig.length; f++) {
								jQuery(sc[i]).removeClass(that.rowConfig[f].cssClass);
							}
						}
						// selection could have changed.
						that.activeTable.selectRows();
					}
				}
			});
		}

		this.rowMSButton = MultiSplitButton({
			items: this.rowMSItems,
			name: 'formatRow',
			hideIfEmpty: true,
			scope: this.name + '.row'
		});
	};

	/**
	 * Adds default column buttons, and custom formatting buttons to floating menu
	 */
	TablePlugin.initColumnBtns = function () {
		var that = this;

		this._addcolumnleftButton = Ui.adopt("addcolumnleft", Button, {
			tooltip: i18n.t("button.addcolleft.tooltip"),
			icon: "aloha-icon aloha-icon-addcolumnleft",
			scope: this.name + '.column',
			click: function() {
				if (that.activeTable) {
					that.activeTable.addColumnsLeft();
				}
			}
		});

		this._addcolumnrightButton = Ui.adopt("addcolumnright", Button, {
			tooltip: i18n.t("button.addcolright.tooltip"),
			icon: "aloha-icon aloha-icon-addcolumnright",
			scope: this.name + '.column',
			click: function() {
				if (that.activeTable) {
					that.activeTable.addColumnsRight();
				}
			}
		});

		this._deletecolumnsButton = Ui.adopt("deletecolumns", Button, {
			tooltip: i18n.t("button.delcols.tooltip"),
			icon: "aloha-icon aloha-icon-deletecolumns",
			scope: this.name + '.column',
			click: function() {
				if (that.activeTable) {
					var aTable = that.activeTable;
					Dialog.confirm({
						title: i18n.t('Table'),
						text: i18n.t('deletecolumns.confirm'),
						yes: function(){
							aTable.deleteColumns();
						}
					});
				}
			}
		});

		this._columnheaderButton = Ui.adopt("columnheader", ToggleButton, {
			tooltip: i18n.t("button.columnheader.tooltip"),
			icon: "aloha-icon aloha-icon-columnheader",
			scope: this.name + '.column',
			click: function() {
				if (that.activeTable) {
					that.activeTable.refresh();

					toggleHeaderStatus(that.activeTable, 'row');

					// Update selection to the new column
					that.activeTable.selection.selectColumns(that.activeTable.selection.selectedColumnIdxs);
					that.activeTable.selection.unselectCells();
				}
			}
		});

		// generate formatting buttons
		this.columnMSItems = [];
		jQuery.each(this.columnConfig, function (j, itemConf) {
			var item = {
				name	  : itemConf.name,
				text	  : i18n.t(itemConf.text),
				tooltip	  : i18n.t(itemConf.tooltip),
				iconClass : 'aloha-icon aloha-column-layout ' + itemConf.iconClass,
				click	  : function (x,y,z) {
					if (that.activeTable) {
						applyStyle(that.columnConfig, itemConf.cssClass, that.activeTable.selection.selectedCells);

						// selection could have changed.
						that.activeTable.selectColumns();
					}
				}
			};

			that.columnMSItems.push(item);
		});

		if (this.columnMSItems.length > 0) {
			this.columnMSItems.push({
				name	: 'removeFormat',
				text	: i18n.t('button.removeFormat.text'),
				tooltip	: i18n.t('button.removeFormat.tooltip'),
				'cls'   : 'aloha-ui-multisplit-fullwidth',
				wide	: true,
				click	: function () {
					if (that.activeTable) {
						var sc = that.activeTable.selection.selectedCells;
						// if a selection was made, transform the selected cells
						for (var i = 0; i < sc.length; i++) {
							for (var f = 0; f < that.columnConfig.length; f++) {
								jQuery(sc[i]).removeClass(that.columnConfig[f].cssClass);
							}
						}
						// selection could have changed.
						that.activeTable.selectColumns();
					}
				}
			});
		}

		this.columnMSButton = MultiSplitButton({
			items: this.columnMSItems,
			name: 'formatColumn',
			hideIfEmpty: true,
			scope: this.name + '.column'
		});
	};

	/**
	 * Adds custom formatting buttons for cells to floating menu
	 */
	TablePlugin.initCellBtns = function () {
		var that = this;

		// generate formatting buttons
		this.cellMSItems = [];
		jQuery.each(this.cellConfig, function (j, itemConf) {
			var item = {
				name	  : itemConf.name,
				text	  : i18n.t(itemConf.text),
				tooltip	  : i18n.t(itemConf.tooltip),
				iconClass : 'aloha-icon aloha-column-layout ' + itemConf.iconClass,
				click	  : function (x,y,z) {
					if (that.activeTable) {
						applyStyle(that.cellConfig, itemConf.cssClass, that.selectedOrActiveCells());

						that.setActiveCellStyle();
					}
				}
			};

			that.cellMSItems.push(item);
		});

		if (this.cellMSItems.length > 0) {
			this.cellMSItems.push({
				name	: 'removeFormat',
				text	: i18n.t('button.removeFormat.text'),
				tooltip	: i18n.t('button.removeFormat.tooltip'),
				'cls'   : 'aloha-ui-multisplit-fullwidth',
				wide	: true,
				click	: function () {
					if (that.activeTable) {
						var sc = that.selectedOrActiveCells();
						// if a selection was made, transform the selected cells
						for (var i = 0; i < sc.length; i++) {
							for (var f = 0; f < that.cellConfig.length; f++) {
								jQuery(sc[i]).removeClass(that.cellConfig[f].cssClass);
							}
						}

						that.setActiveCellStyle();
					}
				}
			});
		}

		this.cellMSButton = MultiSplitButton({
			items: this.cellMSItems,
			name: 'formatCell',
			hideIfEmpty: true,
			scope: this.name + '.cell'
		});
	};


	/**
	 * initialize the buttons and register them on floating menu
	 */
	TablePlugin.initTableButtons = function () {
		var that = this;

		// generate the new scopes
		Scopes.createScope(this.name + '.row', 'Aloha.continuoustext');
		Scopes.createScope(this.name + '.column', 'Aloha.continuoustext');
		Scopes.createScope(this.name + '.cell', 'Aloha.continuoustext');

		this._createTableButton = Ui.adopt("createTable", Button, {
			tooltip: i18n.t("button.createtable.tooltip"),
			icon: "aloha-icon aloha-icon-createTable",
			scope: 'Aloha.continuoustext',
			click: function() {
				TablePlugin.createDialog(this.element);
			}
		});

		// now the specific table buttons

		// generate formatting buttons for columns
		this.initColumnBtns();

		// generate formatting buttons for rows
		this.initRowsBtns();

		// generate formatting buttons for cells
		this.initCellBtns();

		this.initMergeSplitCellsBtns();

		this.initNaturalFitBtn();

		// generate formatting buttons for tables
		this.tableMSItems = [];

		var tableConfig = this.tableConfig;

		jQuery.each(tableConfig, function(j, itemConf){
			that.tableMSItems.push({
				name: itemConf.name,
				text: i18n.t(itemConf.text),
				tooltip: i18n.t(itemConf.tooltip),
				iconClass: 'aloha-icon aloha-table-layout ' + itemConf.iconClass,
				click: function(){
					// set table css class
					if (that.activeTable) {
						if (!that.activeTable.obj.hasClass(itemConf.cssClass)) {
							for (var f = 0; f < tableConfig.length; f++) {
								that.activeTable.obj.removeClass(tableConfig[f].cssClass);
							}
							that.activeTable.obj.addClass(itemConf.cssClass);
							that.tableMSButton.setActiveItem(itemConf.cssClass);
						} else {
							for (var f = 0; f < tableConfig.length; f++) {
								that.activeTable.obj.removeClass(tableConfig[f].cssClass);
							}
							that.tableMSButton.setActiveItem();
						}
					}
				}
			});
		});

		if(this.tableMSItems.length > 0) {
			this.tableMSItems.push({
				name    : 'removeFormat',
				text    : i18n.t('button.removeFormat.text'),
				tooltip : i18n.t('button.removeFormat.tooltip'),
				'cls'   : 'aloha-ui-multisplit-fullwidth',
				wide    : true,
				click   : function () {
					// remove all table classes
					if (that.activeTable) {
						for (var f = 0; f < tableConfig.length; f++) {
							that.activeTable.obj.removeClass(that.tableConfig[f].cssClass);
						}
						that.tableMSButton.setActiveItem();
					}
				}
			});
		}

		this.tableMSButton = MultiSplitButton({
			items : this.tableMSItems,
			name : 'formatTable',
			hideIfEmpty: true,
			scope: this.name + '.cell'
		});

		this._tableCaptionButton = Ui.adopt("tableCaption", ToggleButton, {
			tooltip: i18n.t("button.caption.tooltip"),
			icon: "aloha-icon aloha-icon-table-caption",
			scope: this.name + '.cell',
			click: function() {
				if (that.activeTable) {
					// look if table object has a child caption
					var $caption = that.activeTable.obj.children("caption");

					if ( $caption.is('caption') && $caption.is(':visible') ) {
						$caption.hide();
					} else {
						if (!$caption.is('caption')) {
							$caption = jQuery('<caption></caption>');
							that.activeTable.obj.prepend($caption);
						}
						$caption.show();
						if (jQuery.trim($caption.text()).length === 0) {
							$caption.text(i18n.t('empty.caption'));
						}

						that.makeCaptionEditable($caption, $caption.text());

						// get the editable span within the caption and select it
						var cDiv = $caption.find('div').eq(0);
						var captionContent = cDiv.contents().eq(0);
						if (captionContent.length > 0) {
							var newRange = new GENTICS.Utils.RangeObject();
							newRange.startContainer = newRange.endContainer = captionContent.get(0);
							newRange.startOffset = 0;
							newRange.endOffset = captionContent.text().length;

							// blur all editables within the table
							that.activeTable.obj.find('div.aloha-table-cell-editable').blur();

							cDiv.focus();
							newRange.select();
							Aloha.Selection.updateSelection();
						}
					}
				}
			}
		});

		this.summary = AttributeField( {
			width : 275,
			name  : 'tableSummary',
			noTargetHighlight: true,
			scope: this.name + '.cell',
			element: jQuery('<input id="aloha-attribute-field-tableSummary" class="aloha-wai-red" style="color: black; padding-left: 32px; background-color: white"/>')
		} );

		this.summary.addListener('keyup', function() {
			if (that.activeTable) {
				updateWaiImage(that);
			}
		});
	};

	/**
	 * Helper method to make the caption editable
	 * @param caption caption as jQuery object
	 * @param captionText default text for the caption
	 */
	TablePlugin.makeCaptionEditable = function(caption, captionText) {
		var that = this;
		var cSpan = caption.children('div');
		if (cSpan.length === 0) {
			// generate a new div
			cSpan = jQuery('<div></div>');
			cSpan.addClass('aloha-ui aloha-editable-caption aloha-block');

			// mark the editable wrapper as ephemeral
			Ephemera.markWrapper(cSpan);

			if (caption.contents().length > 0) {
				// when the caption has content, we wrap it with the new div
				cSpan.append(caption.contents());
				caption.append(cSpan);
			} else {
				// caption has no content, so insert the default caption text
				if (captionText) {
					cSpan.text(captionText);
				}
				// and append the div into the caption
				caption.append(cSpan);
			}
		} else if (cSpan.length > 1) {
			// merge multiple divs (they are probably created by IE)
			caption.children('div:not(:first-child)').each(function () {
				$this = jQuery(this);
				cSpan.eq(0).append($this.contents());
				$this.remove();
			});
			cSpan = cSpan.eq(0);
		}
		// make the div editable
		cSpan.contentEditable(true);
	};

	/**
	 * This function adds the createDialog to the calling element
	 *
	 * @param callingElement
	 *            The element, which was clicked. It's needed to set the right
	 *            position to the create-table-dialog.
	 */
	TablePlugin.createDialog = function(callingElement) {
		// set the calling element to the layer the calling element mostly will be
		// the element which was clicked on it is used to position the createLayer
		this.createLayer.set('target', callingElement);

		// show the createLayer
		this.createLayer.show();
	};

	/**
	 * Creates a normal html-table, "activates" this table and inserts it into the
	 * active Editable
	 *
	 * @param cols
	 *            number of colums for the created table
	 * @param cols
	 *            number of rows for the created table
	 * @return void
	 */
	TablePlugin.createTable = function(cols, rows) {
		if ( this.preventNestedTables() ) {
			return;
		}

		// Check if there is an active Editable and that it contains an element (= .obj)
		if ( Aloha.activeEditable && typeof Aloha.activeEditable.obj !== 'undefined' ) {
			// create a dom-table object
			var table = document.createElement( 'table' );
			var tableId = table.id = GENTICS.Utils.guid();
			var tbody = document.createElement( 'tbody' );

			// create "rows"-number of rows
			for ( var i = 0; i < rows; i++ ) {
				var tr = document.createElement( 'tr' );
				// create "cols"-number of columns
				for ( var j = 0; j < cols; j++ ) {
					var text = document.createTextNode('');
					var td = document.createElement( 'td' );
					td.appendChild( text );
					tr.appendChild( td );
				}
				tbody.appendChild( tr );
			}
			table.appendChild( tbody );

			prepareRangeContainersForInsertion(
				Aloha.Selection.getRangeObject(), table );

			// insert the table at the current selection
			GENTICS.Utils.Dom.insertIntoDOM(
				jQuery( table ),
				Aloha.Selection.getRangeObject(),
				Aloha.activeEditable.obj
			);

			cleanupAfterInsertion();

			var tableReloadedFromDOM = document.getElementById( tableId );
			var tableObj = createNewTable(tableReloadedFromDOM);

			if (tableObj) {
				var range = Aloha.Selection.getRangeObject();

				range.startContainer = range.endContainer = tableObj.cells[0].wrapper[0];
				range.startOffset = range.endOffset = 0;
				range.select();

				// Because without the 10ms delay, we cannot place the cursor
				// automatically into the first cell in IE.
				if ($.browser.msie) {
					window.setTimeout(function () {
						tableObj.cells[0].wrapper.get(0).focus();
					}, 20 );
				} else {
					tableObj.cells[0].wrapper.get(0).focus();
				}
			}

			Aloha.activeEditable.smartContentChange({type: 'block-change'});

			// The selection starts out in the first cell of the new
			// table. The table tab/scope has to be activated
			// accordingly.
			tableObj.focus();
			TablePlugin.activeTable.selection.selectionType = 'cell';
			TablePlugin.updateFloatingMenuScope();

		} else {
			this.error( 'There is no active Editable where the table can be\
				inserted!' );
		}
	};

	TablePlugin.setFocusedTable = function(focusTable) {
		var that = this;

		// clicking outside the table unselects the cells of the table
		if (null != this.activeTable ) {
			this.activeTable.selection.unselectCells();
		}

		for (var i = 0; i < TablePlugin.TableRegistry.length; i++) {
			TablePlugin.TableRegistry[i].hasFocus = false;
		}
		if (typeof focusTable != 'undefined') {
			this.summary.setTargetObject(focusTable.obj, 'summary');
			if ( focusTable.obj.children("caption").is('caption') ) {
				// set caption button
				this._tableCaptionButton.setState(true);
				var c = focusTable.obj.children("caption");
				that.makeCaptionEditable(c);
			}
			focusTable.hasFocus = true;
		}
		TablePlugin.activeTable = focusTable;
		if (TablePlugin.activeTable) {
			updateWaiImage(TablePlugin);
		}

		// show configured formatting classes
		for (var i = 0; i < this.tableMSItems.length; i++) {
			this.tableMSButton.showItem(this.tableMSItems[i].name);
		}
		this.tableMSButton.setActiveItem();

		if (this.activeTable) {
			for (var i = 0; i < this.tableConfig.length; i++) {
				if (this.activeTable.obj.hasClass(this.tableConfig[i].cssClass)) {
					this.tableMSButton.setActiveItem(this.tableConfig[i].name);
				}
			}
		}
	};

	/**
	 * Calls the Aloha.log function with 'error' level
	 *
	 * @see Aloha.log
	 * @param msg
	 *            The message to display
	 * @return void
	 */
	TablePlugin.error = function(msg) {
		Aloha.Log.error(this, msg);
	};

	/**
	 * Calls the Aloha.log function with 'debug' level
	 *
	 * @see Aloha.log
	 * @param msg
	 *            The message to display
	 * @return void
	 */
	TablePlugin.debug = function(msg) {
		Aloha.Log.debug(this, msg);
	};

	/**
	 * Calls the Aloha.log function with 'info' level
	 *
	 * @see Aloha.log
	 * @param msg
	 *            The message to display
	 * @return void
	 */
	TablePlugin.info = function(msg) {
		Aloha.Log.info(this, msg);
	};

	/**
	 * Calls the Aloha.log function with 'info' level
	 *
	 * @see Aloha.log
	 * @param msg
	 *            The message to display
	 * @return void
	 */
	TablePlugin.log = function(msg) {
		Aloha.log('log', this, msg);
	};

	/**
	 * The "get"-method returns the value of the given key.
	 * First it searches in the config for the property.
	 * If there is no property with the given name in the
	 * "config"-object it returns the entry associated with
	 * in the parameters-object
	 *
	 * @param property
	 * @return void
	 *
	 */
	TablePlugin.get = function (property) {
		if (this.config[property]) {
			return this.config[property];
		}
		if (this.parameters[property]) {
			return this.parameters[property];
		}
		return undefined;
	};

	/**
	 * The "set"-method takes a key and a value. It checks if there is a
	 * key-value pair in the config-object. If so it saves the data in the
	 * config-object. If not it saves the data in the parameters-object.
	 *
	 * @param key the key which should be set
	 * @param value the value which should be set for the associated key
	 */
	TablePlugin.set = function (key, value) {
		if (this.config[key]) {
			this.config[key] = value;
		}else{
			this.parameters[key] = value;
		}
	};

	/**
	 * Make the given jQuery object (representing an editable) clean for saving
	 * Find all tables and deactivate them
	 * @param obj jQuery object to make clean
	 * @return void
	 */
	TablePlugin.makeClean = function ( obj ) {
		// it is generally not necessary to do anything here, because everything is handled by Ephemera.
		// the only exception is removing the cursor styles, because as of this moment, Ephemera is not capable
		// of removing css
		obj.find('td, th').css('cursor', '');
	};

	/**
	 * String representation of the Table-object
	 *
	 * @return The plugins namespace (string)
	 */
	TablePlugin.toString = function() {
		return this.prefix;
	};

	/**
	 * Leaves all possible TableScopes in the floating menu
	 * expect those in the retainScopes array
	 *
	 * @param  {array} retainScopes the name of the scopes which should not be left
	 */
	TablePlugin.leaveTableScopes = function(retainScopes, force) {
		var i = 0,
			scopes = [];
		retainScopes = $.isArray(retainScopes) ? retainScopes : [];

		scopes = TableSelection.getPossibleSelectionTypes();
		for (i = 0; i < scopes.length; i++) {
			// leave all possible scopes expect those in the retainScopes array
			if ($.inArray(scopes[i], retainScopes) === -1) {
				// always force leaving the scope because otherwise we need to keep track of how
				// often we entered the scope and leave it accordingly
				Scopes.leaveScope(TablePlugin.name + '.' + scopes[i], undefined, true);
			}
		}
	}
	/**
	 * Update the current floating menu scope according to the
	 * selected cells
	 */
	TablePlugin.updateFloatingMenuScope = function() {
		var i = 0,
			primaryScope,
			scopes;
		if (
			null != TablePlugin.activeTable &&
			null != TablePlugin.activeTable.selection.selectionType
		) {
			// save the primary scope
			primaryScope = Scopes.getPrimaryScope(),
			// get the new scopes
			scopes = TablePlugin.activeTable.selection.getCurrentSelectionTypes();
			// leave all scopes except the the current ones
			TablePlugin.leaveTableScopes(scopes);
			// Enter all needed table scopes
			for (i = 0; i < scopes.length; i++) {
				Scopes.enterScope(TablePlugin.name + '.' + scopes[i]);
			}
			// Check if the primaryScope changed and set the first scope as the currently active one
			if (scopes[0] !== primaryScope) {
				Scopes.setScope(TablePlugin.name + '.' + scopes[0]);
			}
		} else {
			// leave all scopes
			TablePlugin.leaveTableScopes();
		}
	};

	/**
	 * Set the cell-style to match the active item, if all selected cells have the same style
	 * TODO: Algorithm very similar to setActiveStyle in table.js, should be refactored
	 */
	TablePlugin.setActiveCellStyle = function() {
		var that = this;
		var allSelected = false;
		var className;

		// reset any selected cell styles
		this.cellMSButton.setActiveItem();

		var selectedCells = that.selectedOrActiveCells();

		for (var i = 0; i < that.cellConfig.length; i++) {
			if (jQuery(selectedCells[0]).hasClass(that.cellConfig[i].cssClass) ) {
				className = that.cellConfig[i].name;
				allSelected = true;
				break;
			}
		}

		// if all selected cells have the same class, set it as active
		jQuery(selectedCells).each(function(index) {
			if (!jQuery(this).hasClass(className)) {
				allSelected = false;
			}
		});
		if (allSelected) {
			this.cellMSButton.setActiveItem(className);
		}
	};

	TablePlugin.selectedOrActiveCells = function() {
		var that = this;
		var sc = this.activeTable.selection.selectedCells;

		// if there are no selected cells,
		// set the active cell as the selected cell.
		if (!sc || sc.length < 1) {
			var activeCell = function() {
			var range = Aloha.Selection.getRangeObject();
				if (Aloha.activeEditable) {
					return range.findMarkup( function() {
							return this.nodeName.toLowerCase() === 'td';
					}, Aloha.activeEditable.obj );
				} else {
					return null;
				}
			}

			var active_cell = activeCell();
			return (active_cell ? [ active_cell ] : []);
		} else {
			return sc;
		}
	};

	PluginManager.register(TablePlugin);

	/**
	 * Detects a situation where we are about to insert content into a
	 * selection that looks like this: <p> [</p>...
	 * We will assume that the nbsp inside the <p> node was placed there to
	 * "prop-up" the empty paragraph--that is--to make the empty paragraph
	 * visible in HTML5 conformant rendering engines, like WebKit. Without the
	 * white space, such browsers would correctly render an empty <p> as
	 * invisible.
	 *
	 * If we detect this situation, we remove the white space so that when we
	 * paste new content into the paragraph, it is not be split and leaving an
	 * empty paragraph on top of the pasted content.
	 *
	 * Note that we do not use <br />'s to prop up the paragraphs, as WebKit
	 * does, because IE, will break from the HTML5 specification and will
	 * display empty paragraphs if they are content-editable. So a <br />
	 * inside an empty content-editable paragraph will result in 2 lines to be
	 * shown instead of 1 in IE.
	 *
	 * @param {Object} range
	 * @param {DOMElement} table
	 */
	function prepareRangeContainersForInsertion ( range, table ) {
		var	eNode = range.endContainer,
			sNode = range.startContainer,
			eNodeLength = ( eNode.nodeType == 3 )
				? eNode.length
				: eNode.childNodes.length;


		if ( sNode.nodeType == 3 &&
				sNode.parentNode.tagName == 'P' &&
					sNode.parentNode.childNodes.length == 1 &&
						/^(\s|%A0)$/.test( escape( sNode.data ) ) ) {
			sNode.data = '';
			range.startOffset = 0;

			// In case ... <p> []</p>
			if ( eNode == sNode ) {
				range.endOffset = 0;
			}
		}

		// If the table is not allowed to be nested inside the startContainer,
		// then it will have to be split in order to insert the table.
		// We will therefore check if the selection touches the start and/or
		// end of their container nodes.
		// If they do, we will mark their container so that after they are
		// split we can check whether or not they should be removed
		if ( !GENTICS.Utils.Dom.allowsNesting(
				sNode.nodeType == 3 ? sNode.parentNode : sNode, table ) ) {

			if ( range.startOffset == 0 ) {
				jQuery( sNode.nodeType == 3 ? sNode.parentNode : sNode )
					.addClass( 'aloha-table-cleanme' );
			}

			if ( range.endOffset == eNodeLength ) {
				jQuery( eNode.nodeType == 3 ? eNode.parentNode : eNode )
					.addClass( 'aloha-table-cleanme' );
			}
		}
	};

	/**
	 * Looks for elements marked with "aloha-table-cleanme", and removes them
	 * if they are absolutely empty.
	 * Note that this will leave paragraphs which contain empty nested elements
	 * even though they are also invisible.
	 * We can consider removing these as well at a later stage, if needed.
	 */
	function cleanupAfterInsertion () {
		var dirty = jQuery( '.aloha-table-cleanme' ).removeClass(
						'aloha-table-cleanme' );

		for ( var i = 0; i < dirty.length; i++ ) {
			if ( jQuery.trim( jQuery( dirty[ i ] ).html() ) == '' &&
					!GENTICS.Utils.Dom.isEditingHost( dirty[ i ] ) ) {
				jQuery( dirty[ i ] ).remove();

				/*
				// For debugging: to see what we are deleting
				jQuery( dirty[ i ] ).css({
					border: '3px solid red',
					display: 'block'
				});
				*/
			}
		}
	};

	return TablePlugin;
});

define('format/nls/i18n',{
	"root":  {
		"button.strong.tooltip": "Strong",
		"button.em.tooltip": "Emphasize",
		"button.b.tooltip": "Bold",
		"button.i.tooltip": "Italic",
		"button.u.tooltip": "Underline",
		"button.cite.tooltip": "Cite",
		"button.q.tooltip": "Tooltip",
		"button.code.tooltip": "Code",
		"button.abbr.tooltip": "Abbreviation",
		"button.del.tooltip": "Strikethrough",
		"button.s.tooltip": "Strikethrough",
		"button.sub.tooltip": "Subscript",
		"button.sup.tooltip": "Superscript",
		"button.p.tooltip": "Paragraph",
		"button.h1.tooltip": "Heading 1",
		"button.h2.tooltip": "Heading 2",
		"button.h3.tooltip": "Heading 3",
		"button.h4.tooltip": "Heading 4",
		"button.h5.tooltip": "Heading 5",
		"button.h6.tooltip": "Heading 6",
		"button.pre.tooltip": "Preformatted text",
		"button.title.tooltip": "Title",
		"button.removeFormat.tooltip": "Remove formatting",
		"button.removeFormat.text": "Remove formatting",
		"GENTICS_button_p": "GENTICS_button_p",
		"GENTICS_button_h1": "GENTICS_button_h1",
		"GENTICS_button_h2": "GENTICS_button_h2",
		"GENTICS_button_h3": "GENTICS_button_h3",
		"GENTICS_button_h4": "GENTICS_button_h4",
		"GENTICS_button_h5": "GENTICS_button_h5",
		"GENTICS_button_h6": "GENTICS_button_h6",
		"GENTICS_button_pre": "GENTICS_button_pre",
		"GENTICS_button_title": "GENTICS_button_title",
		"formatBold": "Ctrl+b",
		"formatItalic": "Ctrl+i",
		"formatUnderline": "Ctrl+u",
		"formatParagraph": "Alt+Ctrl+0",
		"formatH1": "Alt+Ctrl+1",
		"formatH2": "Alt+Ctrl+2",
		"formatH3": "Alt+Ctrl+3",
		"formatH4": "Alt+Ctrl+4",
		"formatH5": "Alt+Ctrl+5",
		"formatH6": "Alt+Ctrl+6",
		"formatPre": "Alt+Ctrl+P",
		"formatDel": "Ctrl+\\",
		"formatSub": "Ctrl+,",
		"formatSup": "Ctrl+.",
		"floatingmenu.tab.format": "Format",
		"format.class.legend": "CSS class",
		"format.class.none": "none"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* format-plugin.js is part of the Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2014 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * License http://aloha-editor.org/license.php
 */
define('format/format-plugin', [
	'jquery',
	'aloha',
	'PubSub',
	'aloha/plugin',
	'aloha/state-override',
	'aloha/content-rules',
	'aloha/ephemera',
	'aloha/selection',
	'util/arrays',
	'util/html',
	'util/dom',
	'util/browser',
	'util/maps',
	'util/strings',
	'ui/ui',
	'ui/toggleButton',
	'ui/port-helper-multi-split',
	'i18n!format/nls/i18n'
], function (
	jQuery,
	Aloha,
	PubSub,
	Plugin,
	StateOverride,
	ContentRules,
	Ephemera,
	Selection,
	Arrays,
	Html,
	Dom,
	Browser,
	Maps,
	Strings,
	Ui,
	ToggleButton,
	MultiSplitButton,
	i18n
) {
	

	var $ = jQuery;
	var pluginNamespace = 'aloha-format';
	var commandsByElement = {
		'b': 'bold',
		'strong': 'bold',
		'i': 'italic',
		'em': 'italic',
		'del': 'strikethrough',
		'sub': 'subscript',
		'sup': 'superscript',
		'u': 'underline',
		's': 'strikethrough'
	};
	var componentNameByElement = { 'code': 'code',
		'strong': 'strong',
		'em': 'emphasis',
		's': 'strikethrough2'
	};
	var textLevelSemantics = {
		'u': true,
		'em': true,
		'strong': true,
		'b': true,
		'i': true,
		'cite': true,
		'q': true,
		'code': true,
		'abbr': true,
		'del': true,
		's': true,
		'sub': true,
		'sup': true
	};
	var blockLevelSemantics = {
		'p': true,
		'h1': true,
		'h2': true,
		'h3': true,
		'h4': true,
		'h5': true,
		'h6': true,
		'pre': true
	};
	var interchangeableNodeNames = {
		"B": ["STRONG", "B"],
		"I": ["EM", "I"],
		"STRONG": ["STRONG", "B"],
		"EM": ["EM", "I"]
	};

	/**
	 * Checks if the selection spans a whole node (HTML element)
	 * @param Range range
	 * @return {boolean}
	 */
	function isEntireNodeInRange(range) {
		var sc = range.startContainer;
		var	so = range.startOffset;
		var	ec = range.endContainer;
		var	eo = range.endOffset;
		return (sc === ec && so === 0 && eo === ec.length);
	}

	/**
	 * Alias for isInlineFormatable function from Html lib
	 */
	var isInlineNode = Html.isInlineFormattable;

	/**
	 * Expands the (invisible) range to encompass the whole node
	 * that was selected by the user
	 * @param Range range
	 * @return void
	 */
	function expandRange(range) {
		var cac = range.commonAncestorContainer;

		if (isInlineNode(cac) && cac.parentNode) {
			var parent = cac.parentNode;
			range.startContainer = parent;
			range.endContainer = parent;
			range.commonAncestorContainer = parent;
			range.startOffset = 0;
			range.endOffset = 1;
			expandRange(range);
			return;
		}

		// Because at this point there will be no further recursion, we should
		// finally update the state of `range`
		range.update();
	}

	/**
	 * Collision map to determine if we are
	 * working on a list related node.
	 */
	var LIST_ELEMENT = {
		'OL': true,
		'UL': true,
		'LI': true,
		'DL': true,
		'DT': true,
		'DD': true
	};

	/**
	 * Determine if we are working on a list element
	 * using the previous LIST_ELEMENT hash map.
	 * @param DOM node
	 * @return {boolean}
	 */
	function isListElement(node){
		return LIST_ELEMENT[node.nodeName];
	}

	/**
	 * Determines if a markup is a heading
	 * @param string markup
	 * @returns {boolean}
	 */
	function isHeading(markup){
		return jQuery.inArray(markup,['h1', 'h2', 'h3', 'h4', 'h5', 'h6']) >= 0;
	}

	/**
	 * Checks whether range spans multiple lists
	 * @param Range range
	 * @return {boolean}
	 */
	function spansMultipleLists(range){
		return range.startContainer !== range.endContainer;
	}

	/**
	 * Take list items out of their encompassing list element.
	 * Wrap items in <p> and delete any remaining empty lists.
	 * @param Range range
	 * @return void
	 */
	function unformatList(range){
		expandRange(range);

		var cac = range.commonAncestorContainer;

		if (!isListElement(cac)){
			return;
		}

		if (!isEntireNodeInRange(range) && !spansMultipleLists(range)) {
			return;
		}

		var selectedNodes = jQuery(range.startContainer.parentNode).nextUntil(jQuery(range.endContainer.parentNode).next()).andSelf();
		var prevNodes = jQuery(range.startContainer.parentNode).prevAll();
		var nextNodes = jQuery(range.endContainer.parentNode).nextAll();
		var listName = range.startContainer.parentNode.parentNode.nodeName;

		// Only one item selected
		if (selectedNodes.length == 1) {
			// Last item in list
			if (prevNodes.length == 0 && nextNodes.length == 0) {
				// The first unwrap removes the parent list tag,
				// the second one the list item tag.
				selectedNodes.unwrap().contents().unwrap().wrap('<p>');
			}
			// first list item
			else if (prevNodes.length === 0) {
				selectedNodes.addClass('_moved').remove().insertBefore(nextNodes.parent());
			}
			// last list item
			else if (nextNodes.length === 0) {
				selectedNodes.addClass('_moved').remove().insertAfter(prevNodes.parent());
			}
			// one list item in middle
			else {
				selectedNodes.addClass('_moved').remove().insertAfter(prevNodes.parent());
				jQuery('<' + listName.toLowerCase() + '>').append(nextNodes).insertAfter(selectedNodes);
			}
		}
		// multiple list items up to whole list
		else {
			selectedNodes.addClass('_moved').remove().insertAfter(cac);
			if (nextNodes.length > 0) {
				jQuery('<' + listName.toLowerCase() + '>').append(nextNodes).insertAfter(selectedNodes.last());
			}
		}


		// unwrap moved list elements
		jQuery('._moved').each(function() {
			var $p = jQuery('<p>');
			var $this = jQuery(this);

			$this.after($p);
			$this.contents().unwrap().appendTo($p);
		});


		// If we are at the first list element, get rid of original (now empty) list
		if (prevNodes.length === 0) {
			cac.remove();
		}
	}

	function formatInsideTableWorkaround(button) {
		var selectedCells = jQuery('.aloha-cell-selected');
		if (selectedCells.length > 0) {
			var cellMarkupCounter = 0;
			selectedCells.each(function () {
				var cellContent = jQuery(this).find('div'),
				cellMarkup = cellContent.find(button);
				if (cellMarkup.length > 0) {
					// unwrap all found markup text
					// <td><b>text</b> foo <b>bar</b></td>
					// and wrap the whole contents of the <td> into <b> tags
					// <td><b>text foo bar</b></td>
					cellMarkup.contents().unwrap();
					cellMarkupCounter++;
				}
				cellContent.contents().wrap('<'+button+'></'+button+'>');
			});

			// remove all markup if all cells have markup
			if (cellMarkupCounter === selectedCells.length) {
				selectedCells.find(button).contents().unwrap();
			}
			return true;
		}
		return false;
	}

	function textLevelButtonClickHandler(formatPlugin, button) {
		if (formatInsideTableWorkaround(button)) {
			return false;
		}
		formatPlugin.addMarkup( button ); 
		return false;
	}

	function blockLevelButtonClickHandler(formatPlugin, button) {
		if (formatInsideTableWorkaround(button)) {
			return false;
		}

		formatPlugin.changeMarkup( button );

		// setting the focus is needed for mozilla to have a working rangeObject.select()
		if (Aloha.activeEditable && jQuery.browser.mozilla && document.activeElement !== Aloha.activeEditable.obj[0]) {
			Aloha.activeEditable.obj.focus();
		}
		
		// triggered for numerated-headers plugin
		if (Aloha.activeEditable) {
			Aloha.trigger( 'aloha-format-block' );
		}
	}

	function makeTextLevelButton(formatPlugin, button) {
		var command = commandsByElement[button];
		var componentName = command;
		if (componentNameByElement.hasOwnProperty(button)) {
			componentName = componentNameByElement[button];
		}
		var component = Ui.adopt(componentName, ToggleButton, {
			tooltip : i18n.t('button.' + button + '.tooltip'),
			icon: 'aloha-icon aloha-icon-' + componentName,
			scope: 'Aloha.continuoustext',
			click: function () {
				return textLevelButtonClickHandler(formatPlugin, button); 
			}
		});
		return component;
	}

	function makeBlockLevelButton(formatPlugin, button) {
		return {
			name: button,
			tooltip: i18n.t('button.' + button + '.tooltip'),
			iconClass: 'aloha-icon ' + i18n.t('aloha-large-icon-' + button),
			markup: jQuery('<' + button + '>'),
			click: function () { 
				return blockLevelButtonClickHandler(formatPlugin, button); 
			}
		};
	}

	function makeRemoveFormatButton(formatPlugin, button) {
		return {
			name: button,
			text: i18n.t('button.' + button + '.text'),
			tooltip: i18n.t('button.' + button + '.tooltip'),
			wide: true,
			cls: 'aloha-ui-multisplit-fullwidth',
			click: function () {
				formatPlugin.removeFormat();
			}
		};
	}

	/**
	 * Checks the hierarchy of headings (h1, h2, ..., h6) and adds a class
	 * "aloha-heading-hierarchy-violated" if the hierarchy is violated.
	 * The hierarchy is violated if a heading is more than one hierarchy lower
	 * than the previous heading.
	 * It is also checked, if the hierarchy is higher than the highest hierarchy
	 * found in the plugin configuration.
	 * For example if the plugin configuration contains the possible headings:
	 * 'h2', 'h3', 'h4', 'h5', 'h6', then all occurrences of 'h1' will be marked.
	 * @param {config} the plugin config
	 * @returns {undefined}
	 */
	function checkHeadingHierarchy(config) {
		var parent = Aloha.activeEditable.obj,
			startHeading,
			lastCorrectHeading,
			currentHeading;

		if (config.length === 0) {
			return;
		}

		// The warning class should only be used with header tags, but the
		// insertparagraph command for example, copies all attributes, to
		// the new element, so the plugin has to remove them again.
		parent.find(":not(h1,h2,h3,h4,h5,h6)").each(function() {
			$(this).removeClass("aloha-heading-hierarchy-violated");
		});

		//set startheading to heading with smallest number available in the config
		for (var i = 0; i < config.length; i++){
			if (isHeading(config[i])) {
				if (typeof startHeading !== 'undefined') {
					if (parseInt(config[i].charAt(1),10) < startHeading) {
						startHeading = parseInt(config[i].charAt(1),10);
					}
				} else {
					//first heading found in config
					startHeading = parseInt(config[i].charAt(1),10);
				}
			}
		}

		//check the heading hierarchy of every heading
		if (typeof startHeading !== 'undefined') {
			//this find() returns all headings in tree order
			parent.find("h1,h2,h3,h4,h5,h6").each(function (){
				currentHeading = parseInt(this.nodeName.charAt(1),10);
				if (typeof lastCorrectHeading !== 'undefined') {
					//the current heading hierarchy must be lower than the startHeading hierarchy
					if (currentHeading < startHeading) {
						$(this).addClass("aloha-heading-hierarchy-violated");
					} else {
						//heading hierarchy is violated if a heading is more
						//than one hierarchy lower than the last correct heading
						if (currentHeading > (lastCorrectHeading+1)) {
							$(this).addClass("aloha-heading-hierarchy-violated");
						} else {
							//only set the last heading if the hierarchy is not violated
							lastCorrectHeading = currentHeading;
							$(this).removeClass("aloha-heading-hierarchy-violated");
						}
					}
				} else {
					//first heading! see if it starts with correct heading
					if (currentHeading === startHeading) {
						lastCorrectHeading = currentHeading;
						$(this).removeClass("aloha-heading-hierarchy-violated");
					} else {
						$(this).addClass("aloha-heading-hierarchy-violated");
					}
				}
			});
		}
	}

	function changeMarkup(button) {
		Selection.changeMarkupOnSelection(jQuery('<' + button + '>'));
		if (Aloha.settings.plugins.format && Aloha.settings.plugins.format.checkHeadingHierarchy === true) {
			checkHeadingHierarchy(this.formatOptions);
		}
	}

	function updateUiAfterMutation(formatPlugin, rangeObject) {
		// select the modified range
		rangeObject.select();
		// update Button toggle state. We take Selection.getRangeObject()
		// because rangeObject is not up-to-date
		onSelectionChanged(formatPlugin, Selection.getRangeObject());
	}

	function format(formatPlugin, rangeObject, markup) {
		Dom.addMarkup(rangeObject, markup);
		updateUiAfterMutation(formatPlugin, rangeObject);
	}

	function isFormatAllowed(tagname, plugin, editable) {
		if (!ContentRules.isAllowed(editable.obj[0], tagname)) {
			return false;
		}
		var config = plugin.getEditableConfig(editable.obj);
		return config ? $.inArray(tagname, config) > -1 : false;
	}

	function addMarkup(button) {
		var formatPlugin = this,
			markup = jQuery('<'+button+'>'),
			rangeObject = Selection.rangeObject;

		if ( typeof button === "undefined" || button == "" ) {
			return;
		}

		// check whether the markup is found in the range (at the start of the range)
		var nodeNames = interchangeableNodeNames[markup[0].nodeName] || [markup[0].nodeName];
		var foundMarkup = rangeObject.findMarkup(function() {
			return -1 !== Arrays.indexOf(nodeNames, this.nodeName);
		}, Aloha.activeEditable.obj);

		if (foundMarkup) {
			// remove the markup
			if (rangeObject.isCollapsed()) {
				// when the range is collapsed, we remove exactly the one DOM element
				Dom.removeFromDOM(foundMarkup, rangeObject, true);
			} else {
				// the range is not collapsed, so we remove the markup from the range
				Dom.removeMarkup(rangeObject, jQuery(foundMarkup), Aloha.activeEditable.obj);
			}
			updateUiAfterMutation(formatPlugin, rangeObject);
		} else {
			// when the range is collapsed, extend it to a word
			if (rangeObject.isCollapsed()) {
				Dom.extendToWord(rangeObject);
				if (rangeObject.isCollapsed()) {
					if (StateOverride.enabled()) {
						StateOverride.setWithRangeObject(
							commandsByElement[button],
							rangeObject,
							function (command, rangeObject) {
								format(formatPlugin, rangeObject, markup);
							}
						);
						return;
					}
				}
			}
			format(formatPlugin, rangeObject, markup);
		}
	}

	function onSelectionChanged(formatPlugin, rangeObject) {
		var effectiveMarkup,
		    foundMultiSplit, i, j, multiSplitItem;

		jQuery.each(formatPlugin.buttons, function (index, button) {
			var statusWasSet = false;
			var nodeNames = interchangeableNodeNames[button.markup[0].nodeName] || [button.markup[0].nodeName];
			for (i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[i];
				for (j = 0; j < nodeNames.length; j++) {
					if (Selection.standardTextLevelSemanticsComparator(effectiveMarkup, jQuery('<' + nodeNames[j] + '>'))) {
						button.handle.setState(true);
						statusWasSet = true;
					}
				}
			}
			if (!statusWasSet) {
				button.handle.setState(false);
			}
		});

		if (formatPlugin.multiSplitItems.length > 0) {
			foundMultiSplit = false;

			// iterate over the markup elements
			for (i = 0; i < rangeObject.markupEffectiveAtStart.length && !foundMultiSplit; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[i];

				for (j = 0; j < formatPlugin.multiSplitItems.length && !foundMultiSplit; j++) {
					multiSplitItem = formatPlugin.multiSplitItems[j];

					if (!multiSplitItem.markup) {
						continue;
					}

					// now check whether one of the multiSplitItems fits to the effective markup
					if (Selection.standardTextLevelSemanticsComparator(effectiveMarkup, multiSplitItem.markup)) {
						formatPlugin.multiSplitButton.setActiveItem(multiSplitItem.name);
						foundMultiSplit = true;
					}
				}
			}

			if (!foundMultiSplit) {
				formatPlugin.multiSplitButton.setActiveItem(null);
			}
		}

		handlePreformattedText(rangeObject.commonAncestorContainer);
	}
	/**
	 * Handles preformatted text.
	 * Adds empty paragraphs as landing stripes before and after a preformatted text.
	 *
	 * @private
	 * @param {Element} element a Dom element
	 */
	function handlePreformattedText(element) {
		var $element = jQuery(element);

		if ($element.is('.aloha-editing-p.aloha-placeholder')) {
			//remove all other placeholders
			$element[0].className = '';
			removePlaceholders();
			$element[0].className = 'aloha-editing-p aloha-placeholder';
			return;
		}

		removePlaceholders();

		if ($element.is('pre')) {
			//add placeholder before and after the preformatted text element
			var nextSibling = $element[0].nextSibling;
			var previousSibling = $element[0].previousSibling;
			if (!previousSibling || !nextSibling) {
				if (!previousSibling) {
					$element.before(createLanding());
				}
				if (!nextSibling) {
					$element.after(createLanding());
				}
			}
		}
	}

	/**
	 * Helper function to create the placeholder jQuery element
	 *
	 * @private
	 * @returns {Object} the landing jQuery element
	 */
	function createLanding() {
		//IE: add a "word joiner" character instead of a <br>
		var landing = Browser.ie
		            ? '<p class="aloha-editing-p aloha-placeholder">&#x2060;</p>'
		            : '<p class="aloha-editing-p aloha-placeholder"><br class="aloha-end-br"></p>';
		return jQuery(landing);
	}

	/**
	 * Remove editing placeholders
	 *
	 * @private
	 */
	function removePlaceholders(){
		if (Aloha.activeEditable) {
			Aloha.activeEditable.removePlaceholder(Aloha.activeEditable.obj);
		}
	}

	/**
	 * register the plugin with unique name
	 */
	return Plugin.create('format', {
		/**
		 * default button configuration
		 */
		config: [ 'b', 'i', 'sub', 'sup', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'removeFormat' ],

		/**
		 * available options / buttons
		 *
		 * @todo new buttons needed for 'code'
		 */
		availableButtons: [ 'code', 'u', 'strong', 'del', 'em', 'b', 'i', 's', 'sub', 'sup', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'removeFormat' ],

		/**
		 * HotKeys used for special actions
		 */
		hotKey: {
			formatBold:      'ctrl+b meta+b',
			formatItalic:    'ctrl+i meta+i',
			formatUnderline: 'ctrl+u meta+u',
			formatParagraph: 'alt+ctrl+0 alt+meta+0',
			formatH1:        'alt+ctrl+1 alt+meta+1',
			formatH2:        'alt+ctrl+2 alt+meta+2',
			formatH3:        'alt+ctrl+3 alt+meta+3',
			formatH4:        'alt+ctrl+4 alt+meta+4',
			formatH5:        'alt+ctrl+5 alt+meta+5',
			formatH6:        'alt+ctrl+6 alt+meta+6',
			formatPre:       'ctrl+p meta+p',
			formatDel:       'ctrl+d meta+d',
			formatSub:       'alt+shift+s',
			formatSup:       'ctrl+shift+s'
		},

		/**
		 * Initialize the plugin and set initialize flag on true
		 */
		init: function () {
			var me = this;

			Ephemera.classes('aloha-heading-hierarchy-violated');

			if (typeof this.settings.hotKey !== 'undefined') {
				jQuery.extend(true, this.hotKey, this.settings.hotKey);
			}

			this.initButtons();

			Aloha.bind('aloha-plugins-loaded', function () {
				// @todo add config option for sidebar panel
				me.initSidebar(Aloha.Sidebar.right);
			});

			var shouldCheckHeadingHierarchy = Strings.parseBoolean(this.settings.checkHeadingHierarchy);

			var checkHeadings = function () {
				checkHeadingHierarchy(me.formatOptions);
			};

			if (shouldCheckHeadingHierarchy) {
				Aloha.bind('aloha-smart-content-changed', checkHeadings);
				Aloha.bind('aloha-markup-change', checkHeadings);
			}

			// apply specific configuration if an editable has been activated
			PubSub.sub('aloha.editable.activated', function (message) {
				var editable = message.editable;
				me.applyButtonConfig(editable.obj);

				if (shouldCheckHeadingHierarchy) {
					checkHeadings();
				}

				var createAdder = function (tagname) {
					if (isFormatAllowed(tagname, me, editable)) {
						return function addFormat() {
							me.addMarkup(tagname);
							return false;
						};
					}
					return function () {
						return false;
					};
				};

				var createChanger = function (tagname) {
					if (isFormatAllowed(tagname, me, editable)) {
						return function changeFormat() {
							me.changeMarkup(tagname);
							return false;
						};
					}
					return function () {
						return false;
					};
				};

				var $editable = editable.obj;
				$editable.bind('keydown.aloha.format',  me.hotKey.formatBold,      createAdder('b'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatItalic,    createAdder('i'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatUnderline, createAdder('u'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatDel,       createAdder('del'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatSub,       createAdder('sub'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatSup,       createAdder('sup'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatParagraph, createChanger('p'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatH1,        createChanger('h1'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatH2,        createChanger('h2'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatH3,        createChanger('h3'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatH4,        createChanger('h4'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatH5,        createChanger('h5'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatH6,        createChanger('h6'));
				$editable.bind('keydown.aloha.format',  me.hotKey.formatPre,       createChanger('pre'));
			});

			PubSub.sub('aloha.editable.deactivated', function (message) {
				message.editable.obj.unbind('keydown.aloha.format');
			});
		},

		/**
		 * applys a configuration specific for an editable
		 * buttons not available in this configuration are hidden
		 * @param {Object} id of the activated editable
		 * @return void
		 */
		applyButtonConfig: function ($editable) {
			var config = this.getEditableConfig($editable),
			    button, i, len;

			if ( typeof config === 'object' ) {
				var config_old = [];
				jQuery.each(config, function(j, button) {
					if ( !(typeof j === 'number' && typeof button === 'string') ) {
						config_old.push(j);
					}
				});
				
				if ( config_old.length > 0 ) {
					config = config_old;
				}
			}
			this.formatOptions = config;

			var editable = $editable[0];

			// now iterate all buttons and show/hide them according to the config
			for ( button in this.buttons) {
				if (this.buttons.hasOwnProperty(button)) {
					if (!ContentRules.isAllowed(editable, button)) {
						this.buttons[button].handle.hide();
					} else if (jQuery.inArray(button, config) !== -1) {
						this.buttons[button].handle.show();
					} else {
						this.buttons[button].handle.hide();
					}
				}
			}

			// and the same for multisplit items
			len = this.multiSplitItems.length;
			for (i = 0; i < len; i++) {
				var name = this.multiSplitItems[i].name;

				// Currently removeFormat is the only button, that would not
				// insert tags, and can therefore ignore the content rules.
				if (name != 'removeFormat' && !ContentRules.isAllowed(editable, name)) {
					this.multiSplitButton.hideItem(name);
				} else if (jQuery.inArray(name, config) !== -1) {
					this.multiSplitButton.showItem(name);
				} else {
					this.multiSplitButton.hideItem(name);
				}
			}
		},

		/**
		 * initialize the buttons and register them on floating menu
		 * @param event event object
		 * @param editable current editable object
		 */
		initButtons: function () {
			var that = this;

			this.buttons = {};
			this.multiSplitItems = [];

			$.each(this.availableButtons, function(j, button) {
				var button_config = false;

				if (typeof j !== 'number' && typeof button !== 'string') {
					button_config = button;
					button = j;
				}

				if (textLevelSemantics[button]) {
					that.buttons[button] = {
						handle: makeTextLevelButton(that, button),
						markup: jQuery('<'+button+'>', {'class': button_config || ''})
					};
				} else if (blockLevelSemantics[button]) {
					that.multiSplitItems.push(makeBlockLevelButton(that, button));
				} else if ('removeFormat' === button) {
					that.multiSplitItems.push(makeRemoveFormatButton(that, button));
				} else {
					Aloha.log('warn', that, 'Button "' + button + '" is not defined');
				}
			});

			this.multiSplitButton = new MultiSplitButton({
				name: 'formatBlock',
				items: this.multiSplitItems,
				hideIfEmpty: true,
				scope: 'Aloha.continuoustext'
			});

			PubSub.sub('aloha.selection.context-change', function(message) {
				onSelectionChanged(that, message.range);
			});
		},

		initSidebar: function ( sidebar ) {
			var pl = this;
			pl.sidebar = sidebar;
			sidebar.addPanel( {

				id       : pl.nsClass( 'sidebar-panel-class' ),
				title    : i18n.t( 'floatingmenu.tab.format' ),
				content  : '',
				expanded : true,
				activeOn : this.formatOptions || false,

				onInit: function () {
				},

				onActivate: function ( effective ) {
					var that = this;
					that.effective = effective;
					
					if ( !effective[0] ) {
						return;
					}
					that.format = effective[0].nodeName.toLowerCase();

					var dom = jQuery('<div>').attr('class', pl.nsClass( 'target-container' ));
					var fieldset = jQuery('<fieldset>');
					fieldset.append(jQuery('<legend>' + that.format + ' ' + i18n.t( 'format.class.legend' )).append(jQuery('<select>')));
					
					dom.append(fieldset);
					
					var html = 
						'<div class="' + pl.nsClass( 'target-container' ) + '"><fieldset><legend>' + i18n.t( 'format.class.legend' ) + '</legend><select name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '">' + 
						'<option value="">' + i18n.t( 'format.class.none' ) + '</option>';

					if ( pl.config[that.format] && pl.config[that.format]['class'] ) {
						jQuery.each(pl.config[that.format]['class'], function(i ,v) {
							html += '<option value="' + i + '" >' + v + '</option>';
						});
					}

					html += '</select></fieldset></div>';

					var content = this.setContent(html).content;

					jQuery( pl.nsSel( 'framename' ) ).live( 'keyup', function () {
						jQuery( that.effective ).attr( 'target', jQuery( this ).val().replace( '\"', '&quot;' ).replace( "'", "&#39;" ) );
					} );

					that.effective = effective;
					jQuery( pl.nsSel( 'linkTitle' ) ).val( jQuery( that.effective ).attr( 'title' ) );
				}

			} );

			sidebar.show();
		},

		// duplicated code from link-plugin
		//Creates string with this component's namepsace prefixed the each classname
		nsClass: function () {
			var stringBuilder = [], prefix = pluginNamespace;
			jQuery.each( arguments, function () {
				stringBuilder.push( this == '' ? prefix : prefix + '-' + this );
			} );
			return jQuery.trim(stringBuilder.join(' '));
		},

		// duplicated code from link-plugin
		nsSel: function () {
			var stringBuilder = [], prefix = pluginNamespace;
			jQuery.each( arguments, function () {
				stringBuilder.push( '.' + ( this == '' ? prefix : prefix + '-' + this ) );
			} );
			return jQuery.trim(stringBuilder.join(' '));
		},

		addMarkup: addMarkup,
		changeMarkup: changeMarkup,

		/**
		 * Removes all formatting from the current selection.
		 * And deconstructs lists via unformatList method.
		 */
		removeFormat: function() {
			var formats = [
				'u', 'strong', 'em', 'b', 'i', 'q', 'del', 's', 'code', 'sub', 'sup',
				'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'quote', 'blockquote',
				'address', 'small', 'cite', 'dfn',
				'abbr', 'time', 'var', 'samp', 'kbd', 'mark', 'span', 'ins'
				],
			    rangeObject = Selection.rangeObject,
			    i;

			// formats to be removed by the removeFormat button may now be configured using Aloha.settings.plugins.format.removeFormats = ['b', 'strong', ...]
			if (this.settings.removeFormats) {
				formats = this.settings.removeFormats;
			}

			if (rangeObject.isCollapsed()) {
				return;
			}

			for (i = 0; i < formats.length; i++) {
				Dom.removeMarkup(
					rangeObject,
					jQuery('<' + formats[i] + '>'),
					Aloha.activeEditable.obj,
					false);
			}
			unformatList(rangeObject);

			// select the modified range
			rangeObject.select();
			Aloha.activeEditable.smartContentChange({type: 'block-change'});
		},

		/**
		 * toString method
		 * @return string
		 */
		toString: function () {
			return 'format';
		}
	});
});

define('ui/menuButton',[
	'jquery',
	'ui/component',
	'ui/utils',
	'jqueryui'
], function (
	$,
	Component,
	Utils
) {
	

	var MenuButton = Component.extend({
		init: function () {
			this.element = MenuButton.makeMenuButton(this);
		}
	});

	// static functions

	/**
	 * @param props button properties:
	 *        click - if provided will generate a split button,
	 *                  otherwise just a normal select button.
	 *        menu - array of props for nested buttons
	 *        text - button text
	 *        html - button html
	 *        iconUrl - button icon url
	 *        siblingContainer
	 *             - a $ object that will be searched for other split buttons.
	 *               If a split button is expanded, all the other split buttons in
	 *               this container will be closed.
	 */
	MenuButton.makeMenuButton = function (props) {
		var wrapper = $('<div>'   , {'class': 'aloha-ui-menubutton-container'});
		var expand  = Utils.makeButtonElement({'class': 'aloha-ui-menubutton-expand'});
		var menu    = $('<ul>'    , {'class': 'aloha-ui-menubutton-menu'});
		var action = null;
		var buttonset = null;

		if ($.browser.msie) {
			wrapper.addClass('aloha-ui-menubutton-iehack');
		}

		if (props.click) {
			action = Utils.makeButton(Utils.makeButtonElement({'class': 'aloha-ui-menubutton-action'}), props)
				.click(props.click);

			Utils.makeButton(expand, {}, true);

			buttonset = $('<div>')
				.buttonset()
				.append(action)
				.append(expand);
		} else {
			Utils.makeButton(expand, props, true)
			      .addClass('aloha-ui-menubutton-single');
		}

		if (props.tooltip) {
			wrapper.children('[title]').removeAttr('title').end()
				.attr('title', props.tooltip)
				.tooltip({
					placement: 'bottom',
					position: {
						my: 'left top',
						at: 'right bottom'
					}
				});

			if (props.menu) {
				wrapper.on('menushown', function() {
					wrapper.tooltip('hide');
				});
				wrapper.on('menuhidden', function() {
					wrapper.tooltip('show');
				});
			}
		}

		if (!props.menu) {
			return wrapper.append(action);
		}

		function hideMenu(menu) {
			menu.hide().parent().removeClass('aloha-ui-menubutton-pressed');
			wrapper.trigger('menuhidden');
		}

		expand.click(function () {
				wrapper.addClass('aloha-ui-menubutton-pressed');

				if (props.siblingContainer) {
					props.siblingContainer
						.find('.aloha-ui-menubutton-menu')
						.each(function () {
							if (this !== menu[0]) {
								hideMenu($(this));
							}
						});
				}

				if (menu.is(':visible')) {
					hideMenu(menu);
					return;
				}

				menu.show().position({
					my: 'left top',
					at: 'left bottom',
					of: action || expand
				});
				wrapper.trigger('menushown');

				// In order to prevent the floating menu from being partially
				// covered by the ribbon, we use "position: relative" and an
				// invisible border to pad the top of the document.  This
				// throws off the offset to the menu button so we need to
				// compensate in ordet to ensure that the menu is placed
				// underneatht the menubutton.
				// NB: For the time being we are not using the above fix.
				/*
				var target = action || expand;
				var bodyOffset = parseInt($('body').css('border-top-width'), 10) || 0;
				menu.css('top', target.height() + target.offset().top + bodyOffset);
				*/

				// This click event will bubble up to the document (preventing
				// this would leave a menu open when clicking on another menu
				// button), but this one event should be ignored. So we wrap
				// the actual handler, that will close the menu in a separate
				// click handler.
				var $doc = $(document);

				$doc.one('click', function () {
					$doc.one('click', function() {
						menu.hide();
						wrapper.removeClass('aloha-ui-menubutton-pressed')
							.trigger('menuhidden');
					});
				});
			});

		wrapper.append(buttonset || expand).append(menu);

		menu.append(makeNestedMenus(makeCloseHandler(menu), props.menu));

		menu.hide().menu({
			'select': onSelect
		});

		return wrapper;
	};

	function makeNestedMenus(parentCloseHandler, menu){
		var elems = [];
		$.each(menu, function (_, item) {
			var elem = $('<li>');
			elem.append($('<a>', {'href': 'javascript:void 0', 'html': Utils.makeButtonLabelWithIcon(item)}));
			if (item.click) {
				elem.click(function (){
					parentCloseHandler();
					item.click();
				});
			}
			if (item.menu) {
				var nestedMenu = $('<ul>').appendTo(elem);
				nestedMenu.append(
					makeNestedMenus(makeCloseHandler(nestedMenu, parentCloseHandler),
									item.menu));
			}
			elems.push(elem[0]);
		});
		return elems;
	}

	function makeCloseHandler(menu, parentCloseHandler) {
		parentCloseHandler = parentCloseHandler || $.noop;
		return function () {
			// We must blur the parent menu otherwise it will remain in
			// focused state and not expand the next time it is hovered over
			// after the user has selected an item.
			menu.blur().hide();
			menu.parent().removeClass('aloha-ui-menubutton-pressed');
			parentCloseHandler();
		};
	}

	function onSelect(event, ui) {
		var clickHandler = ui.item.data('aloha-ui-menubutton-select');
		if (clickHandler) {
			clickHandler(event, ui);
		}
		// We use preventDefault() to keep a click on a menu item from
		// scrolling to the top of the page.
		event.preventDefault();
	}

	return MenuButton;
});

define('list/nls/i18n',{
	"root":  {
		"button.createulist.tooltip": "Insert Unordered List",
		"button.createolist.tooltip": "Insert Ordered List",
		"button.createdlist.tooltip": "Insert Definition List",
		"button.indentlist.tooltip": "Increase Indent",
		"button.outdentlist.tooltip": "Decrease Indent",
		"floatingmenu.tab.list": "Lists"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* list-plugin.js is part of the Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2014 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 * License http://aloha-editor.org/license.php
 */
define('list/list-plugin',[
	'aloha',
	'jquery',
	'aloha/plugin',
	'aloha/content-rules',
	'aloha/engine',
	'util/dom',
	'ui/ui',
	'ui/scopes',
	'ui/button',
    'ui/menuButton',
	'PubSub',
	'i18n!list/nls/i18n'
], function (
	Aloha,
	$,
	Plugin,
	ContentRules,
	Engine,
	Dom,
	Ui,
	Scopes,
	Button,
	MenuButton,
	PubSub,
	i18n
) {
	

	var jQuery = $;
	var configurations = {};

	/**
	 * Initializes the list templates button menus.
	 *
	 * @private
	 * @param {ListPlugin} plugin
	 */
	function initializeTemplates(plugin) {
		if (plugin.templates.dl) {
			$.each(plugin.templates.dl.classes, function (i, cssClass) {
				plugin.definitionListStyleButtons.push(plugin.makeListStyleButton('dl', cssClass));
			});

			plugin._definitionListFormatSelectorButton = Ui.adopt(
				'definitionListFormatSelector',
				MenuButton,
				{
					click: function () {
						plugin.transformList('dl');
					},
					tooltip: i18n.t('button.createdlist.tooltip'),
					html: '<span class="ui-button-icon-primary ui-icon aloha-icon aloha-icon-definitionlist"></span>',
					menu: (plugin.definitionListStyleButtons.length) ? plugin.definitionListStyleButtons : null
				}
			);
		}

		if (plugin.templates.ol) {
			$.each(plugin.templates.ol.classes, function (i, cssClass) {
				plugin.orderedListStyleButtons.push(plugin.makeListStyleButton('ol', cssClass));
			});

			plugin._orderedListFormatSelectorButton = Ui.adopt(
				'orderedListFormatSelector',
				MenuButton,
				{
					click: function () {
						plugin.transformList('ol');
					},
					tooltip: i18n.t('button.createolist.tooltip'),
					html: '<span class="ui-button-icon-primary ui-icon aloha-icon aloha-icon-orderedlist"></span>',
					menu: (plugin.orderedListStyleButtons.length) ? plugin.orderedListStyleButtons : null
				}
			);
		}

		if (plugin.templates.ul) {
			$.each(plugin.templates.ul.classes, function (i, cssClass) {
				plugin.unorderedListStyleButtons.push(plugin.makeListStyleButton('ul', cssClass));
			});

			plugin._unorderedListFormatSelectorButton = Ui.adopt(
				'unorderedListFormatSelector',
				MenuButton,
				{
					click: function () {
						plugin.transformList('ul');
					},
					tooltip: i18n.t('button.createulist.tooltip'),
					html: '<span class="ui-button-icon-primary ui-icon aloha-icon aloha-icon-unorderedlist"></span>',
					menu: (plugin.unorderedListStyleButtons.length) ? plugin.unorderedListStyleButtons : null
				}
			);
		}
	}

	/**
	 * Subscribes event handlers to facilitate user interaction on editables.
	 *
	 * @private
	 * @param {ListPlugin} plugin
	 */
	function registerEventHandlers(plugin) {
		PubSub.sub('aloha.editable.created', function (message) {
			var editable = message.editable.obj[0];
			var config = plugin.getEditableConfig(message.editable.obj);
			configurations[message.editable.getId()] = {
				dl: config && ($.inArray('dl', config) > -1) && ContentRules.isAllowed(editable, 'dl'),
				ol: config && ($.inArray('ol', config) > -1) && ContentRules.isAllowed(editable, 'ol'),
				ul: config && ($.inArray('ul', config) > -1) && ContentRules.isAllowed(editable, 'ul')
			};
		});

		PubSub.sub('aloha.editable.destroyed', function (message) {
			delete configurations[message.editable.getId()];
		});

		PubSub.sub('aloha.editable.activated', function (message) {
			var config = configurations[message.editable.getId()];
			if (config) {
				toggleListOption(plugin, 'dl', config.dl);
				toggleListOption(plugin, 'ol', config.ol);
				toggleListOption(plugin, 'ul', config.ul);
			}
		});

		PubSub.sub('aloha.selection.context-change', function (message) {
			var $dlIcon = $('.aloha-icon-definitionlist').parents('.aloha-ui-menubutton-container');
			var $olIcon = $('.aloha-icon-orderedlist').parents('.aloha-ui-menubutton-container');
			var $ulIcon = $('.aloha-icon-unorderedlist').parents('.aloha-ui-menubutton-container');

			$dlIcon.removeClass('aloha-button-active');
			$olIcon.removeClass('aloha-button-active');
			$ulIcon.removeClass('aloha-button-active');

			plugin._outdentListButton.show(false);
			plugin._indentListButton.show(false);

			var i;
			var markup;
			var range = message.range;

			for (i = 0; i < range.markupEffectiveAtStart.length; i++) {
				markup = range.markupEffectiveAtStart[i];
				switch (markup.nodeName) {
				case 'DL':
					$dlIcon.addClass('aloha-button-active');
					$(markup).addClass('alohafocus');
					break;
				case 'OL':
					$olIcon.addClass('aloha-button-active');
					plugin._outdentListButton.show(true);
					plugin._indentListButton.show(true);
					break;
				case 'UL':
					$ulIcon.addClass('aloha-button-active');
					plugin._outdentListButton.show(true);
					plugin._indentListButton.show(true);
					break;
				}
			}

			// Remove jQuery UI menu classes/attributes from list-templates in submenus
			$('div.aloha-list-templates ul').removeClass('ui-menu ui-widget ui-widget-content ui-corner-all')
			          .attr('role', '')
			          .attr('aria-hidden', '')
			          .attr('aria-expanded', '')
			          .css('display', 'block');
		});

		Aloha.Markup.addKeyHandler(9, function (event) {
			return plugin.processTab(event);
		});
	}

	/**
	 * Small JS template function.
	 *
	 * @param  {string} str The template where substitution takes place
	 * @param  {object} obj The object containing strings to insert into template
	 * @return {string}
	 */
	function tmpl(str, obj) {
	    var replacer = function (wholeMatch, key) {
	            return obj[key] === undefined ? wholeMatch : obj[key];
	        },
	        regexp = /\${\s*([a-z0-9\-_]+)\s*}/ig;

	    do {
	        var beforeReplace = str;
	        str = str.replace(regexp, replacer);
	        var afterReplace = str !== beforeReplace;
	    } while (afterReplace);

	    return str;
	}

	/**
	 * Shows or hides the ul, ol or dl buttons in Aloha floating menu if they are
	 * configured.
	 *
	 * @param {plugin}  plugin the list plugin
	 * @param {string}  listtype the type of listbutton to toggle (ul, ol, dl)
	 * @param {boolean} show hide or show the button
	 */
	function toggleListOption(plugin, listtype, show) {
		switch (listtype) {
		case 'ul':
			if (plugin.templates.ul) {
				plugin._unorderedListFormatSelectorButton.show(show);
			}
			break;
		case 'ol':
			if (plugin.templates.ol) {
				plugin._orderedListFormatSelectorButton.show(show);
			}
			break;
		case 'dl':
			if (plugin.templates.dl) {
				plugin._definitionListFormatSelectorButton.show(show);
			}
			break;
		}
	}

	/**
	 * Transforms the given list element and its sub elements (if they are in the selection) into
	 * the given transformTo target.
	 * @param domToTransform - The list object that should be transformed
	 * @param transformTo - Transformationtarget e.g. 'ul' / 'ol'
	 */
	function transformExistingListAndSubLists (domToTransform, transformTo) {
		// find and transform sublists if they are in the selection
		jQuery(domToTransform).find(domToTransform.nodeName).each(function () {
			if (isListInSelection(this)) {
				Aloha.Markup.transformDomObject(this, transformTo, Aloha.Selection.rangeObject);
			}
		});

		// the element itself
		Aloha.Markup.transformDomObject(domToTransform, transformTo, Aloha.Selection.rangeObject);
	}

	/**
	 * Checks if a dom element is in the given Slectiontree.
	 * @param needle - the searched element
	 * @return returns true if the needle is found in the current selection tree.
	 */
	function isListInSelection(needle) {
		var selectionTree = Aloha.Selection.getSelectionTree();
		return checkSelectionTreeEntryForElement(selectionTree, needle);
	}

	/**
	 * Checks if the given needle is in the given treeElement or in one of its sub elements.
	 * @param treeElement - the tree element to be searched in
	 * @param needle - the searched element
	 */
	function checkSelectionTreeEntryForElement(treeElementArray, needle) {
		var found = false;
		jQuery.each(treeElementArray, function (index, element) {
			if ((element.domobj === needle && element.selection !== "none") || checkSelectionTreeEntryForElement(element.children, needle)) {
				found = true;
			}
		});
		return found;
	}

	/**
	 * Register the ListPlugin as Aloha.Plugin
	 */
	var ListPlugin = Plugin.create('list', {
		/**
		 * default button configuration
		 */
		config: [ 'ul', 'ol', 'dl' ],

		/**
		 * List of transformable elements
		 */
		transformableElements: {'p' : true, 'h1' : true, 'h2' : true, 'h3' : true, 'h4' : true, 'h5' : true, 'h6' : true, 'ul' : true, 'ol' : true, 'dl': true},

		/**
		* Default list styles
		*/
		templates: {
			ul: {
				classes: ['aloha-list-disc', 'aloha-list-circle', 'aloha-list-square'],
				template: '<ul class="${cssClass}"><li>${first}<ul class="${cssClass}"><li>${second}<ul class="${cssClass}"><li>${third}</li></ul></li></ul></li></ul>',
				locale: {
					fallback: {first: 'first layer', second: 'second layer', third: 'third layer'},
					de: {first: 'erste Ebene', second: 'zweite Ebene', third: 'dritte Ebene'}
				}
			},
			ol: {
				classes: ['aloha-list-decimal', 'aloha-list-decimal-leading-zero',
					'aloha-list-lower-roman', 'aloha-list-upper-roman', 'aloha-list-lower-greek',
					'aloha-list-lower-latin', 'aloha-list-upper-latin' ],
				template: '<ol class="${cssClass}"><li>${first}<ol class="${cssClass}"><li>${second}<ol class="${cssClass}"><li>${third}</li></ol></li></ol></li></ol>',
				locale: {
					fallback: {first: 'first layer', second: 'second layer', third: 'third layer'},
					de: {first: 'erste Ebene', second: 'zweite Ebene', third: 'dritte Ebene'}
				}
			},
			dl: {
				classes: ['aloha-list-blue', 'aloha-list-green', 'aloha-list-red'],
				template: '<dl class="${cssClass}"><dt>${first}<dt><dd>${second}</dd></dl>',
				locale: {
					fallback: {first: 'first item', second: 'second item'},
					de: {first: 'erstes Element', second: 'zweites Element'}
				}
			}
		},

		/**
		 * Set selected CSS class on current list element and all nested
		 * list elements that are contained in the selection
		 * @param String listtype: ol, ul or dl
		 * @param String style: selected CSS class
		 * @return void
		 */
		setListStyle: function (listtype, style) {
			var domObject = this.getStartingDomObjectToTransform();
			var nodeName = domObject.nodeName.toLowerCase();
			var listToStyle =  jQuery(domObject);
			var plugin = this;

			if (nodeName !== 'ul' && nodeName !== 'ol' && nodeName !== 'dl') {
				// we don't have a list yet, so transform selection to list
				this.transformList(listtype);
				domObject = this.getStartingDomObjectToTransform();
				nodeName = domObject.nodeName.toLowerCase();
				listToStyle = jQuery(this.getStartingDomObjectToTransform());
			}

			if (listtype === nodeName) {
				// remove all classes
				jQuery.each(this.templates[nodeName].classes, function (i, cssClass) {
					listToStyle.removeClass(cssClass);
				});

				listToStyle.addClass(style);

				// now proceed with all selected sublists
				listToStyle.find(listtype).each(function () {
					if (isListInSelection(this)) {
						var listToStyle = jQuery(this);
						jQuery.each(plugin.templates[listtype].classes, function (i, cssClass) {
							listToStyle.removeClass(cssClass);
						});
						listToStyle.addClass(style);
					}
				});
			}
		},

		/**
		* Array for ordered list style buttons
		*/
		orderedListStyleButtons: [],

		/**
		* Array for unordered list style buttons
		*/
		unorderedListStyleButtons: [],

		/**
		* Array for unordered definition style buttons
		*/
		definitionListStyleButtons: [],

		/**
		 * Construct button for list styles (CSS classes).
		 *
		 * @param  {String} listtype ol, ul or dl
		 * @param  {String} cssClass selected list style
		 * @return {Object} MenuButton menu property
		 */
		makeListStyleButton: function (listtype, cssClass) {
			var that = this;

			var template = that.templates[listtype];

			var locale = template.locale[Aloha.settings.locale]
			          || template.locale['fallback'];

			var html = tmpl(template.template, {
				cssClass : cssClass,
				first    : locale.first,
				second   : locale.second,
				third    : locale.third
			});

			return {
				html: '<div class="aloha-list-templates">' + html + '</div>',
				click: function () {
					that.setListStyle(listtype, cssClass);
				}
			};
		},

		/**
		 * Initializes the plugin. Register buttons, menus, and event handlers.
		 */
		init: function () {
			var plugin = this;

			plugin._indentListButton = Ui.adopt('indentList', Button, {
				tooltip: i18n.t('button.indentlist.tooltip'),
				icon: 'aloha-icon aloha-icon-indent',
				scope: 'Aloha.continuoustext',
				click: function () {
					plugin.indentList();
				}
			});

			plugin._outdentListButton = Ui.adopt('outdentList', Button, {
				tooltip: i18n.t('button.outdentlist.tooltip'),
				icon: 'aloha-icon aloha-icon-outdent',
				scope: 'Aloha.continuoustext',
				click: function () {
					plugin.outdentList();
				}
			});

			if (Aloha.settings.plugins && Aloha.settings.plugins.list && Aloha.settings.plugins.list.templates) {
				plugin.templates = Aloha.settings.plugins.list.templates;
			}

			initializeTemplates(plugin);
			registerEventHandlers(plugin);
			Scopes.createScope('Aloha.List', 'Aloha.continuoustext');
		},

		/**
		 * Process Tab and Shift-Tab pressed in lists
		 */
		processTab: function (event) {
			if (event.keyCode === 9/*tab*/ ) {
				if (event.shiftKey) {
					return this.outdentList();
				} else {
					return this.indentList();
				}
			}
			return true;
		},

		/**
		 * For the current selection, get the DOM object, which will be transformed to/from the list
		 * @return dom object or false
		 */
		getStartingDomObjectToTransform: function () {
			Aloha.Selection.checkForFirefoxIncorrectRange();
			var rangeObject = Aloha.Selection.rangeObject,
				i, effectiveMarkup;

			for ( i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[ i ];
				if (this.transformableElements[effectiveMarkup.nodeName.toLowerCase()]) {
					return effectiveMarkup;
				}
			}

			return false;
		},

		/**
		 * For the current selection, get the nearest list item as dom object
		 * @return dom object or false
		 */
		getNearestSelectedListItem: function () {
			var rangeObject = Aloha.Selection.rangeObject,
				i, effectiveMarkup;

			for ( i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[ i ];
				if (Dom.isListElement(effectiveMarkup)) {
					return effectiveMarkup;
				}
			}

			return false;
		},

		/**
		* Transforms a list into paragraphs
		* @param Dom domToTransform element to transform
		* @param String listElement the list element to transform (li, dt, dd)
		*/
		transformListToParagraph: function (domToTransform, listElement) {
			var newPara;
			var jqToTransform = jQuery(domToTransform);
			jQuery.each(jqToTransform.children(listElement), function (index, el) {
				newPara = Aloha.Markup.transformDomObject(el, 'p', Aloha.Selection.rangeObject);
				// if any lists are in the paragraph, move the to after the paragraph
				newPara.after(newPara.children('ol,ul,dl'));
				Engine.ensureContainerEditable(newPara.get(0));
			});

			// unwrap the elements (remove the enclosing list)
			jqToTransform.children().unwrap();
		},

		/**
		* When the list is nested into another, our list items will be
		* added to the list items of the outer list.
		* @param Dom Parent List Dom element
		* @param Dom List Dom Element
		*/
		fixupNestedLists: function (jqParentList, jqList) {
			// find the place where to put the children of the inner list
			if (jqParentList.get(0).nodeName.toLowerCase() === 'li') {
				// transform the list elements to be li (could by dt and dd)
				jQuery.each(jqList.children(), function (index, el) {
					Aloha.Markup.transformDomObject(el, 'li', Aloha.Selection.rangeObject);
				});
				// inner list is nested in a li (this conforms to the html5 spec)
				jqParentList.after(jqList.children());
				jqList.remove();
			} else {
				// inner list is nested in the outer list directly (this violates the html5 spec)
				jqList.children().unwrap();
			}
		},

		/**
		* Creates a list out of allowed elements
		* @param String listtype type of list we want to create (ul, ol, dl)
		* @param Dom domToTransform DOM object to transform
		*/
		createList: function (listtype, domToTransform) {
			var selectedSiblings = Aloha.Selection.rangeObject.getSelectedSiblings(domToTransform);
			var jqList;
			var jqNewEl;
			var lastAppendedEl;
			var el;
			var listElement;

			// create a new list
			switch (listtype) {
				case 'ol':
					jqList = jQuery('<ol></ol>');
					jqNewEl = jQuery('<li></li>');
					break;
				case 'ul':
					jqList = jQuery('<ul></ul>');
					jqNewEl = jQuery('<li></li>');
					break;
				case 'dl':
					jqList = jQuery('<dl></dl>');
					jqNewEl = jQuery('<dt></dt>');
					break;
			}

			// add the li into the list
			jqList.append(jqNewEl);
			// append the contents of the old dom element to the li
			jQuery(domToTransform).contents().appendTo(jqNewEl);
			// replace the old dom element with the new list
			jQuery(domToTransform).replaceWith(jqList);

			// update the selection range
			if (Aloha.Selection.rangeObject.startContainer == domToTransform) {
				Aloha.Selection.rangeObject.startContainer = jqNewEl.get(0);
			}
			if (Aloha.Selection.rangeObject.endContainer == domToTransform) {
				Aloha.Selection.rangeObject.endContainer = jqNewEl.get(0);
			}

			lastAppendedEl = jqNewEl;

			// now also transform all siblings
			if (selectedSiblings) {
				var o = true;
				var lastEl = false;
				for (var i = 0; i < selectedSiblings.length; ++i) {
					if (Dom.isBlockLevelElement(selectedSiblings[i])) {
						if (lastEl) {
							lastEl = false;
						}
						// transform the block level element
						if (listtype === 'dl') {
							if (!o) {
								jqNewEl = Aloha.Markup.transformDomObject(selectedSiblings[i], 'dt', Aloha.Selection.rangeObject);
							} else {
								jqNewEl = Aloha.Markup.transformDomObject(selectedSiblings[i], 'dd', Aloha.Selection.rangeObject);
							}
							o = !o;
						} else {
							jqNewEl = Aloha.Markup.transformDomObject(selectedSiblings[i], 'li', Aloha.Selection.rangeObject);
						}
						jqList.append(jqNewEl);
						lastAppendedEl = jqNewEl;
					} else {
						if (selectedSiblings[i].nodeType == 3 && jQuery.trim(selectedSiblings[i].data).length === 0) {
							continue;
						}
						if (!lastEl) {
							lastEl = jqNewEl;
							jqList.append(lastEl);
							lastAppendedEl = lastEl;
						}
						lastEl.append(selectedSiblings[i]);
					}
				}
			}

			// merge adjacent lists
			this.mergeAdjacentLists(jqList);

			//use rangy to change the selection to the contents of
			//the last li that was appended to the list
			el = lastAppendedEl.get(0);
			if (Dom.isEmpty(el)) {
				var range = Aloha.createRange();
				var selection = Aloha.getSelection();
				//IE7 requires an (empty or non-empty) text node
				//inside the li for the selection to work.
				el.appendChild(document.createTextNode(""));
				range.selectNodeContents( el.lastChild );
				selection.removeAllRanges();
				selection.addRange( range );
				Aloha.Selection.updateSelection();
			}
		},

		/**
		* Set up a new empty list
		* @param String listtype type of list we want to create (ul, ol, dl)
		* @return Dom domToTransform DOM object to transform
		*/
		prepareNewList: function (listtype) {
			var jqList;
			var jqNewEl;
			var el;
			var editable;
			var range;
			var selection;
			Aloha.Selection.updateSelection();

			// wrap a paragraph around the selection
			Aloha.Selection.changeMarkupOnSelection(jQuery('<p></p>'));
			var domToTransform = this.getStartingDomObjectToTransform();

			if (!domToTransform) {
				if ( jQuery(Aloha.Selection.rangeObject.startContainer).contentEditable() ) {
					// create a new list with an empty item
					switch (listtype) {
						case 'ol':
							jqList = jQuery('<ol></ol>');
							jqNewEl = jQuery('<li></li>');
							break;
						case 'ul':
							jqList = jQuery('<ul></ul>');
							jqNewEl = jQuery('<li></li>');
							break;
						case 'dl':
							jqList = jQuery('<dl></dl>');
							jqNewEl = jQuery('<dt></dt>');
							break;
					}

					jqList.append(jqNewEl);
					el = jqNewEl.get(0);
					el.appendChild(document.createTextNode(""));

					if (Dom.insertIntoDOM(jqList, Aloha.Selection.rangeObject)) {
						range = Aloha.createRange();
						selection = Aloha.getSelection();
						range.setStart( el.firstChild, 0 );
						range.setEnd( el.firstChild, 0 );
						selection.removeAllRanges();
						selection.addRange( range );
						Aloha.Selection.updateSelection();
						domToTransform = jqList.get(0);
					} else {
						Aloha.Log.error(this, 'Could not transform selection into a list');
					}
				} else {
					Aloha.Log.error(this, 'Could not transform selection into a list');
				}
			}
			return domToTransform;
		},

		/**
		 * Transform the current selection to/from a list
		 * @param String listtype type of list we want to transform to (ul, ol, dl)
		 */
		transformList: function (listtype) {
			var domToTransform = this.getStartingDomObjectToTransform();
			var jqList;
			var jqParentList;
			var	nodeName;

			// visible is set to true, but the button is not visible
			this._outdentListButton.show(true);
			this._indentListButton.show(true);

			if (!domToTransform || !domToTransform.parentNode) {
				domToTransform = this.prepareNewList(listtype);
				this.refreshSelection();

				if (domToTransform && domToTransform.nodeName.toLowerCase() === listtype) {
					return;
				}
			}

			// check the dom object
			nodeName = domToTransform.nodeName.toLowerCase();

			if (nodeName === listtype) {
				jqList = jQuery(domToTransform);
				jqParentList = jqList.parent();
				if (jqParentList.length > 0 && Dom.isListElement(jqParentList.get(0))) {
					// we are in a nested list
					this.fixupNestedLists(jqParentList, jqList);
				} else {
					// we are in an list and shall transform it to paragraphs
					if (listtype === 'dl') {
						this.transformListToParagraph(domToTransform, 'dd, dt');
					} else {
						this.transformListToParagraph(domToTransform, 'li');
					}
				}

			} else if (nodeName == 'ul' && listtype === 'ol') {
				transformExistingListAndSubLists(domToTransform, 'ol');
				this.mergeAdjacentLists(jQuery(domToTransform));
			} else if (nodeName == 'ol' && listtype === 'ul') {
				transformExistingListAndSubLists(domToTransform, 'ul');
				this.mergeAdjacentLists(jQuery(domToTransform));
			} else if (nodeName === 'ul' && listtype === 'dl') {
				this.transformListToParagraph(domToTransform, 'li');
				domToTransform = this.prepareNewList(listtype);
				this.createList(listtype, domToTransform);
			} else if (nodeName === 'ol' && listtype === 'dl') {
				this.transformListToParagraph(domToTransform, 'li');
				domToTransform = this.prepareNewList(listtype);
				this.createList(listtype, domToTransform);
			} else if (nodeName === 'dl' && listtype === 'ol' ) {
				this.transformListToParagraph(domToTransform, 'dd, dt');
				domToTransform = this.prepareNewList(listtype);
				this.createList(listtype, domToTransform);
			} else if (nodeName === 'dl' && listtype === 'ul' ) {
				this.transformListToParagraph(domToTransform, 'dd, dt');
				domToTransform = this.prepareNewList(listtype);
				this.createList(listtype, domToTransform);
			} else {
				this.createList(listtype, domToTransform);
			}

			this.refreshSelection();
		},


		/**
		 * Indent the selected list items by moving them into a new created, nested list
		 */
		indentList: function () {
			var listItem = this.getNearestSelectedListItem(),
				i, jqNewList, selectedSiblings, jqOldList, jqItemBefore;

			if (listItem) {
				jqItemBefore = jQuery(listItem).prev('li');

				// when we are in the first li of a list, there is no indenting
				if (jqItemBefore.length === 0) {
					// but we handled the TAB keystroke
					return false;
				}
				jqOldList = jQuery(listItem).parent();

				// get the also selected siblings of the dom object
				selectedSiblings = Aloha.Selection.rangeObject.getSelectedSiblings(listItem);

				// create the new list element by cloning the selected list element's parent
				jqNewList = jQuery(listItem).parent().clone(false).empty();
				jqNewList.append(listItem);

				// we found a list item before the first selected one, so append the new list to it
				jqItemBefore.append(jqNewList);

				// check for multiple selected items
				if (selectedSiblings) {
					for ( i = 0; i < selectedSiblings.length; ++i) {
						jqNewList.append(jQuery(selectedSiblings[i]));
					}
				}

				// merge adjacent lists
				this.mergeAdjacentLists(jqNewList, true);

				// refresh the selection
				this.refreshSelection();

				return false;
			}

			return true;
		},

		/**
		 * Outdent nested list items by moving them into the outer list
		 */
		outdentList: function () {
			var
				listItem = this.getNearestSelectedListItem(),
				i, jqNewPostList,
				jqListItem, jqList, jqParentList, wrappingLi,
				selectedSiblings, lastSelected;

			if (listItem) {
				// check whether the list is nested into another list
				jqListItem = jQuery(listItem);
				jqList = jqListItem.parent();

				// get the parent list
				jqParentList = jqList.parents('ul,ol');

				// check whether the inner list is directly inserted into a li element
				wrappingLi = jqList.parent('li');

				if (jqParentList.length > 0
						&& Dom.isListElement(jqParentList.get(0))) {
					// the list is nested into another list

					// get the also selected siblings of the dom object
					selectedSiblings = Aloha.Selection.rangeObject.getSelectedSiblings(listItem);

					// check for multiple selected items
					if (selectedSiblings && selectedSiblings.length > 0) {
						lastSelected = jQuery(selectedSiblings[selectedSiblings.length - 1]);
					} else {
						lastSelected = jqListItem;
					}

					// check whether we found not selected li's after the selection
					if (lastSelected.nextAll('li').length > 0) {
						jqNewPostList = jqList.clone(false).empty();
						jqNewPostList.append(lastSelected.nextAll());
						lastSelected.append(jqNewPostList);
					}

					// now move all selected li's into the higher list
					if (wrappingLi.length > 0) {
						wrappingLi.after(jqListItem);
					} else {
						jqList.before(jqListItem);
					}

					// check for multiple selected items
					if (selectedSiblings && selectedSiblings.length > 0) {
						for ( i = selectedSiblings.length - 1; i >= 0; --i) {
							jqListItem.after(jQuery(selectedSiblings[i]));
						}
					}

					// finally check whether there are elements left in the list
					if (jqList.contents('li').length === 0) {
						// list is completely empty, so remove it
						jqList.remove();
					}

					// check whether the wrapping li is empty now
					if (wrappingLi.length > 0 && wrappingLi.contents().length === 0) {
						wrappingLi.remove();
					}

					// refresh the selection
					this.refreshSelection();
				}

				return false;
			}

			return true;
		},

		/**
		 * Refresh the current selection and set to focus to the current editable again
		 */
		refreshSelection: function () {
			Aloha.Selection.rangeObject.update();
			Aloha.Selection.rangeObject.select();
			Aloha.Selection.updateSelection();
		},

		/**
		 * Merge adjacent lists (of same type) into the first list
		 * @param jqList jQuery object of a list
		 * @param allTypes true if all types of lists may be merged, false if only same types may be merged
		 */
		mergeAdjacentLists: function (jqList, allTypes) {
			// first get the first previous sibling of same type
			var firstList = jqList.get(0), jqNextList;

			while (
				firstList.previousSibling
				&& firstList.previousSibling.nodeType === 1
				&& this.isMergable(firstList.previousSibling, firstList, allTypes)
			) {
				firstList = firstList.previousSibling;
			}

			jqList = jQuery(firstList);
			// now merge all adjacent lists into this one
			while (
				firstList.nextSibling
				&& (
					(
						firstList.nextSibling.nodeType === 1
						&& this.isMergable(firstList.nextSibling, firstList, allTypes)
					) || (
						firstList.nextSibling.nodeType === 3
						&& jQuery.trim(firstList.nextSibling.data).length === 0
					)
				)
			) {
				jqNextList = jQuery(firstList.nextSibling);
				if (firstList.nextSibling.nodeType == 1) {
					jqNextList.contents().appendTo(jqList);
				}
				jqNextList.remove();
			}
		},

		/**
		 * Check whether the given DOM element toCheck is mergeable into the DOM element mergeInto
		 * @param toCheck DOM element to check
		 * @param mergeInto DOM element into which toCheck shall be merged
		 * @param allTypes true if all types of lists may be merged, false if only same types may be merged
		 */
		isMergable: function (toCheck, mergeInto, allTypes) {
			if (allTypes) {
				return toCheck.nodeName.toLowerCase() == 'ul' || toCheck.nodeName.toLowerCase() == 'ol';
			} else {
				return toCheck.nodeName == mergeInto.nodeName;
			}
		}
	});

	Engine.commands['insertdefinitionlist'] = {
		action: function (value, range) {
			ListPlugin.transformList('dl');
			if (range && Aloha.Selection.rangeObject) {
				range.startContainer = Aloha.Selection.rangeObject.startContainer;
				range.startOffset = Aloha.Selection.rangeObject.startOffset;
				range.endContainer = Aloha.Selection.rangeObject.endContainer;
				range.endOffset = Aloha.Selection.rangeObject.endOffset;
			}
		},
		indeterm: function () {
			// TODO
		},
		state: function () {
			for ( i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[ i ];
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ul></ul>'))) {
					return false;
				}
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ol></ol>'))) {
					return false;
				}
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<dl></dl>'))) {
					return true;
				}
			}

			return false;
		}
	};


	Engine.commands['insertorderedlist'] = {
		action: function (value, range) {
			ListPlugin.transformList('ol');
			if (range && Aloha.Selection.rangeObject) {
				range.startContainer = Aloha.Selection.rangeObject.startContainer;
				range.startOffset = Aloha.Selection.rangeObject.startOffset;
				range.endContainer = Aloha.Selection.rangeObject.endContainer;
				range.endOffset = Aloha.Selection.rangeObject.endOffset;
			}
		},
		indeterm: function () {
			// TODO
		},
		state: function () {
			for ( i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[ i ];
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ul></ul>'))) {
					return false;
				}
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ol></ol>'))) {
					return true;
				}
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<dl></dl>'))) {
					return false;
				}
			}

			return false;
		}
	};

	Engine.commands['insertunorderedlist'] = {
		action: function (value, range) {
			ListPlugin.transformList('ul');
			if (range && Aloha.Selection.rangeObject) {
				range.startContainer = Aloha.Selection.rangeObject.startContainer;
				range.startOffset = Aloha.Selection.rangeObject.startOffset;
				range.endContainer = Aloha.Selection.rangeObject.endContainer;
				range.endOffset = Aloha.Selection.rangeObject.endOffset;
			}
		},
		indeterm: function () {
			// TODO
		},
		state: function () {
			for ( i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[ i ];
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ul></ul>'))) {
					return true;
				}
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ol></ol>'))) {
					return false;
				}
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<dl></dl>'))) {
					return false;
				}
			}

			return false;
		}
	};

	Engine.commands['indent'] = {
		action: function (value, range) {
			ListPlugin.indentList();
			if (range && Aloha.Selection.rangeObject) {
				range.startContainer = Aloha.Selection.rangeObject.startContainer;
				range.startOffset = Aloha.Selection.rangeObject.startOffset;
				range.endContainer = Aloha.Selection.rangeObject.endContainer;
				range.endOffset = Aloha.Selection.rangeObject.endOffset;
			}
		},
		indeterm: function () {
			// TODO
		},
		state: function () {
			// TODO
			return false;
		}
	};

	Engine.commands['outdent'] = {
		action: function (value, range) {
			ListPlugin.outdentList();
			if (range && Aloha.Selection.rangeObject) {
				range.startContainer = Aloha.Selection.rangeObject.startContainer;
				range.startOffset = Aloha.Selection.rangeObject.startOffset;
				range.endContainer = Aloha.Selection.rangeObject.endContainer;
				range.endOffset = Aloha.Selection.rangeObject.endOffset;
			}
		},
		indeterm: function () {
			// TODO
		},
		state: function () {
			// TODO
			return false;
		}
	};

	/**
	 * A key handler that should be run as a keyup handler for the
	 * backspace and del keys. keyup fires after the browser has already
	 * performed the delete - this handler will perform a cleanup if
	 * necessary.
	 *
	 * Will work around an IE bug which breaks nested lists in the
	 * following situation, where [] is the selection, if backspace is
	 * pressed (same goes for the del key if the selection is at the end
	 * of the li that occurs before the selection):
	 *
	 * <ul>
	 *  <li>one</li>
	 *  <li><ul><li>two</li></ul></li>
	 * </ul>
	 * <p>[]</p>
	 *
	 * The browser behaviour, if one would presses backspace, results in
	 * the following:
	 *
	 * <ul>
	 *  <li>one</li>
	 *  <ul><li>two</li></ul>
	 * </ul>
	 *
	 * which is invalid HTML since the <ul>s are nested directly inside
	 * each other.
	 *
	 * Also, the following situation will cause the kind of invalid HTML
	 * as above.
	 * <ul>
	 *   <li>one</li>
	 *   <li><ul><li>two</li></ul></li>
	 *   <li>[]three</li>
	 * </ul>
	 *
	 * Also, the following situtation:
	 * <ul>
	 *   <li>one</li>
	 *   <li><ul><li>two</li></ul>
	 *       <p>[]three</p>
	 *       <li>four</li>
	 *   </li>
	 * </ul>
	 *
	 * And similar situations, some of which are not so easy to reproduce.
	 *
	 * @param event a jQuery key event
	 * @return false if no action needed to be taken, true if cleanup has been performed
	 */
	function deleteWorkaroundHandler(event) {
		if (8/*backspace*/ != event.keyCode && 46/*del*/ != event.keyCode) {
			return false;
		}

		var rangeObj = Aloha.getSelection().getRangeAt(0);
		var startContainer = rangeObj.startContainer;

		//the hack is only relevant if after the deletion has been
		//performed we are inside a li of a nested list
		var $nestedList = jQuery(startContainer).closest('ul, ol, dl');
		if ( ! $nestedList.length ) {
			return false;
		}
		var $parentList = $nestedList.parent().closest('ul, ol, dl');
		if ( ! $parentList.length ) {
			return false;
		}

		var ranges = Aloha.getSelection().getAllRanges();

		var actionPerformed = false;
		$parentList.each(function () {
			actionPerformed = actionPerformed || fixListNesting(jQuery(this));
		});

		if (actionPerformed) {
			Aloha.getSelection().setRanges(ranges);
			for (var i = 0; i < ranges.length; i++) {
				ranges[i].detach();
			}
		}

		return actionPerformed;
	}

	/**
	 * If dls, uls or ols are nested directly inside the given list (invalid
	 * HTML), they will be cleaned up by being appended to the preceding
	 * element.
	 */
	function fixListNesting($list) {
		var actionPerformed = false;
		$list.children('ul, ol').each(function () {
			Aloha.Log.debug("performing list-nesting cleanup");
			if ( ! jQuery(this).prev('li').append(this).length ) {
				//if there is no preceding li, create a new one and append to that
				jQuery(this).parent().prepend(document.createElement('li')).append(this);
			}
			actionPerformed = true;
		});
		$list.children('dl').each(function () {
			Aloha.Log.debug("performing list-nesting cleanup");
			if ( ! jQuery(this).prev('dt').append(this).length ) {
				//if there is no preceding dt, create a new one and append to that
				jQuery(this).parent().prepend(document.createElement('dt')).append(this);
			}
			actionPerformed = true;
		});
		return actionPerformed;
	}

	return ListPlugin;
});

/* dom-to-xhtml.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/*global serialize,serializeElement */
/**
 * Provides public utility methods to convert DOM nodes to XHTML.
 */
define('util/dom-to-xhtml',[
	'util/dom2',
	'util/browser',
	'aloha/ephemera',
	'aloha/console'
], function (
	Dom,
	Browser,
	Ephemera,
	console
) {
	

	/**
	 * Elements that are to be serialized like <img /> and not like <img></img>
	 */
	var emptyElements = {
		"area": true,
		"base": true,
		"basefont": true,
		"br": true,
		"col": true,
		"frame": true,
		"hr": true,
		"img": true,
		"input": true,
		"isindex": true,
		"link": true,
		"meta": true,
		"param": true,
		"embed": true
	};

	/**
	 * Attributes that are to be serialized like checked="checked" for any attribute value.
	 */
	var booleanAttrs = {
		"checked": true,
		"compact": true,
		"declare": true,
		"defer": true,
		"disabled": true,
		"ismap": true,
		"multiple": true,
		"nohref": true,
		"noresize": true,
		"noshade": true,
		"nowrap": true,
		"readonly": true,
		"selected": true
	};

	/**
	 * Maps element names to a boolean that indicates whether IE7/IE8 doesn't recognize the element.
	 * This is necessary to repair the broken DOM structure caused by unrecognized elements.
	 * Contains some intial values to cover most common cases. If an
	 * element is serialized that is not present here, it will be
	 * examined (which may be costly) and added dynamically.
	 * See isUnrecognized().
	 */
	var isUnrecognizedMap = {
		"DIV": false,
		"SPAN": false,
		"UL": false,
		"OL": false,
		"LI": false,
		"TABLE": false,
		"TR": false,
		"TD": false,
		"TH": false,
		"I": false,
		"B": false,
		"EM": false,
		"STRONG": false,
		"A": false,
		"P": false
	};

	/**
	 * Encodes a string meant to be used wherever parsable character data occurs in XML.
	 * @param str
	 *        An unencoded piece of character data
	 * @return
	 *        The given string with & and < characters replaced with the corresponding HTML entity references.
	 */
	function encodePcdata(str) {
		return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
	}

	/**
	 * Encodes a string meant to be used between double-quoted attribute values.
	 *
	 * @param str
	 *        An unencoded attribute value
	 * @return
	 *        The given string with & < and " characters replaced with the corresponding HTML entity references.
	 */
	function encodeDqAttrValue(str) {
		return encodePcdata(str).replace(/"/g, '&quot;');
	}

	/**
	 * Serializes the attributes of the given element.
	 *
	 * Attributes that have the empty string as value will not appear in the string at all.
	 *
	 * @param element
	 *        An element to serialize the attributes of
	 * @param ephemera
	 *        Describes attributes that should be skipped.
	 *        See Ehpemera.ephemera().
	 * @return
	 *        A string made up of name="value" for each attribute of the
	 *        given element, separated by space. The string will have a leading space.
	 */
	function makeAttrString(element, ephemera) {
		var attrs = Dom.attrs(element);
		var str = "";
		var i, len, bool;
		// Dom.attrs() doesn't support some boolean attributes on IE7
		// which we have to compensate for.
		if (Browser.ie7) {
			for (bool in booleanAttrs) {
				if (booleanAttrs.hasOwnProperty(bool) && element[bool]) {
					// We don't want to add duplicate attributes
					for (i = 0, len = attrs.length; i < len; i++) {
						if (bool === attrs[i][0].toLowerCase()) {
							attrs.splice(i, 1);
							break;
						}
					}
					attrs.push([bool, bool]);
				}
			}
		}
		for (i = 0, len = attrs.length; i < len; i++) {
			// The XHTML spec says attributes are lowercase
			var attr  = attrs[i];
			var name  = attr[0].toLowerCase();
			var value = attr[1];

			if (ephemera && Ephemera.isAttrEphemeral(element, name, ephemera.attrMap || {}, ephemera.attrRxs || {})) {
				continue;
			}

			//TODO it's only a boolean attribute if the element is in an HTML namespace
			var isBool = booleanAttrs[name];

			if (!isBool && "" === value) {
				// I don't think it is ever an error to make an
				// attribute not appear if its string value is empty.
				continue;
			}

			// For boolean attributes, the mere existence of the attribute means it is true.
			str += " " + name + '="' + encodeDqAttrValue((isBool ? name : value)) + '"';
		}
		return str;
	}

	/**
	 * IE8 turns the following
	 * <book id="x">{content}</book>
	 * into
	 * <book id="x"></book>{content}</book><//book>
	 * This seems to occur with any element IE doesn't recognize.
	 *
	 * @param element
	 *        An element node.
	 * @return
	 *        true if the given element isn't recognized by IE and
	 *        causes a broken DOM structure as outlined above.
	 */
	function isUnrecognized(element) {
		var name = element.nodeName;
		var unrecognized = isUnrecognizedMap[name];
		if (null != unrecognized) {
			return unrecognized;
		}
		var closingName = "/" + element.nodeName;
		var sibling = element.nextSibling;
		unrecognized = false;
		while (null != sibling) {
			if (closingName == sibling.nodeName) {
				unrecognized = true;
				break;
			}
			sibling = sibling.nextSibling;
		}
		isUnrecognizedMap[name] = unrecognized;
		return unrecognized;
	}

	/**
	 * Serializes the children of the given element into an XHTML string.
	 *
	 * The same as serializeElement() except it only serializes the children.
	 * The start and end tag of the given element will not appear in the resulting XHTML.
	 *
	 * @see serializeElement()
	 */
	function serializeChildren(element, child, unrecognized, ephemera, xhtml) {
		while (null != child) {
			if (1 === child.nodeType && unrecognized && "/" + element.nodeName == child.nodeName) {
				child = child.nextSibling;
				break;
			} else if (1 === child.nodeType && isUnrecognized(child)) {
				child = serializeElement(child, child.nextSibling, true, ephemera, xhtml);
			} else {
				serialize(child, ephemera, xhtml);
				child = child.nextSibling;
			}
		}
		return child;
	}

	/**
	 * Serializes an element into an XHTML string.
	 *
	 * @param element
	 *        An element to serialize.
	 * @param child
	 *        The first child of the given element. This will usually be
	 *        element.firstChild. On IE this may be element.nextSibling because
	 *        of the broken DOM structure IE sometimes generates.
	 * @param unrecognized
	 *        Whether the given element is unrecognized on IE. If IE doesn't
	 *        recognize the element, it will create a broken DOM structure
	 *        which has to be compensated for. See isUnrecognized() for more.
	 * @param ephemera
	 *        Describes content that should not be serialized.
	 *        Only attrMap and attrRxs are supported at the moment.
	 *        See Ephemera.ephemera().
	 * @param xhtml
	 *        An array which receives the serialized element and whic, if joined,
	 *        will yield the XHTML string.
	 * @return
	 *        null if all siblings of the given child have been processed as children
	 *        of the given element, or otherwise the first sibling of child that is not considered
	 *        a child of the given element.
	 */
	function serializeElement(element, child, unrecognized, ephemera, xhtml) {
		// TODO: we should only lowercase element names if they are in an HTML namespace
		var elementName = element.nodeName.toLowerCase();
		// This is a hack around an IE bug which strips the namespace prefix
		// of element.nodeName if it occurs inside an contentEditable=true.
		if (element.scopeName && 'HTML' != element.scopeName && -1 === elementName.indexOf(':')) {
			elementName = element.scopeName.toLowerCase() + ':' + elementName;
		}
		if (!unrecognized && null == child && emptyElements[elementName]) {
			xhtml.push('<' + elementName + makeAttrString(element, ephemera) + '/>');
		} else {
			xhtml.push('<' + elementName + makeAttrString(element, ephemera) + '>');
			child = serializeChildren(element, child, unrecognized,  ephemera, xhtml);
			xhtml.push('</' + elementName + '>');
		}
		return child;
	}

	/**
	 * Serializes a DOM node into a XHTML string.
	 *
	 * @param node
	 *        A DOM node to serialize.
	 * @param ephemera
	 *        Describes content that should not be serialized.
	 *        Only attrMap and attrRxs are supported at the moment.
	 *        See Ephemera.ephemera().
	 * @param xhtml
	 *        An array that will receive snippets of XHTML,
	 *        which if joined will yield the XHTML string.
	 */
	function serialize(node, ephemera, xhtml) {
		var nodeType = node.nodeType;
		if (1 === nodeType) {
			serializeElement(node, node.firstChild, isUnrecognized(node), ephemera, xhtml);
		} else if (3 === node.nodeType) {
			xhtml.push(encodePcdata(node.nodeValue));
		} else if (8 === node.nodeType) {
			xhtml.push('<' + '!--' + node.nodeValue + '-->');
		} else {
			console.warn('Unknown node type encountered during serialization, ignoring it:' +
						' type=' + node.nodeType +
						' name=' + node.nodeName +
						' value=' + node.nodeValue);
		}
	}

	return {
		/**
		 * Serializes a number of DOM nodes in an array-like object to an XHTML string.
		 *
		 * The XHTML of the nodes in the given array-like object will be concatenated.
		 *
		 * @param nodes
		 *        An array or jQuery object or another array-like object to serialize.
		 * @param ephemera
		 *        Describes content that should not be serialized.
		 *        Only attrMap and attrRxs are supported at the moment.
		 *        See Ephemera.ephemera().
		 * @return
		 *        The serialized XHTML String representing the given DOM nodes in the given array-like object.
		 *        The result may look like an XML fragment with multiple top-level elements and text nodes.
		 * @see nodeToXhtml()
		 */
		contentsToXhtml: function (element, ephemera) {
			var xhtml = [];
			serializeChildren(element, element.firstChild, false, ephemera, xhtml);
			return xhtml.join("");
		},

		/**
		 * Serializes a DOM node to an XHTML string.
		 *
		 * Beware that the serialization method will generate XHTML as
		 * close as possible to the DOM tree represented by the given
		 * node. The result will only be valid XHTML if the DOM tree
		 * doesn't violate any contained-in rules.
		 *
		 * Element attributes with an empty string as value will not
		 * appear in the serialized output.
		 *
		 * Element attribute names are case-insensitive in HTML5, so
		 * they may come out in mixed-case depending on what the browser
		 * provides.
		 *
		 * When iterating over the DOM, CDATA sections are comment nodes
		 * on some browsers (Chrome) and not there at all on others (IE).
		 * This is the same as what comes out from element.innerHTML.
		 *
		 * IE8 bug: comments will sometimes be silently stripped inside
		 * contentEditable=true. Conditional includes don't work inside
		 * contentEditable=true. See the tests for more information.
		 *
		 * IE8 bug: a title element will not be serialized correctly
		 * unless it occurs in the head of a HTML document, even if it occurs
		 * in a non-HTML namespace (maybe it works with a prefix).
		 * This will probably also apply for other HTML elements that
		 * occur in the header.
		 *
		 * IE8 bug: unrecognized elements in the HTML scope will cause
		 * broken DOM structure (some HTML5 elements that are not yet
		 * implemented in IE for example). Some effort was made to fix a
		 * broken DOM structure, if it is encountered. There is one case
		 * which results in an unrecoverably broken DOM structure, which
		 * is an unrecognized element not preceded by some text. See the
		 * tests for further information.
		 *
		 * IE8 bug: whitespace is not reliably preserved when the style
		 * white-space:pre (or similar) is used. See the tests for
		 * further information. Whitespace inside <pre> elements will
		 * be preserved, but \n characters will become \r characters.
		 *
		 * IE7 bug: URLs in href and src attributes of a and img
		 * elements will be absolutized (including hostname and
		 * protocol) if they are given as a relative path.
		 *
		 * IE bug: Namespace support inside contentEditable=true is a
		 * bit shaky on IE. Don't use it if possible. See the tests to
		 * get an idea of what seems to work. Make namespace prefixes
		 * and element names all lower-case, as they are always
		 * lower-cased, even if the element doesn't occur in an HTML
		 * namespace. Don't use default namespaces, use prefixes (except
		 * for an HTML namespace).
		 *
		 * @param node
		 *        A DOM node to serialize
		 * @param ephemera
		 *        Describes content that should not be serialized.
		 *        Only attrMap and attrRxs are supported at the moment.
		 *        See Ephemera.ephemera().
		 * @return
		 *        The serialized XHTML string represnting the given DOM node.
		 */
		nodeToXhtml: function (node, ephemera) {
			var xhtml = [];
			serialize(node, ephemera, xhtml);
			return xhtml.join("");
		}
	};
});

/* dom-to-xhtml-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * The dom-to-xhtml plugin extends the serialization method of the
 * Aloha.Editable.getContent() instance method to generate valid XHTML
 * (in so far as the DOM of the editables itself is valid).
 * Attributes described by Ehpemera.ephemera() will be removed.
 */
define('dom-to-xhtml/dom-to-xhtml-plugin',[
	'aloha',
	'jquery',
	'aloha/plugin',
	'aloha/ephemera',
	'util/dom-to-xhtml'
], function (
	Aloha,
	$,
	Plugin,
	Ephemera,
	domToXhtml
) {
	

	return Plugin.create('dom-to-xhtml', {
		/**
		 * Called by the plugin-manager on intialization.
		 *
		 * @Override
		 */
		init: function () {
			var that = this;
			Aloha.Editable.setContentSerializer(function (editableElement) {
				if (!that.settings.editables && !that.settings.config) {
					return domToXhtml.contentsToXhtml(editableElement);
				}

				if (that.settings.editables && that.settings.editables['#' + $(editableElement).attr('id')] == 'dom-to-xhtml') {
					return domToXhtml.contentsToXhtml(editableElement, Ephemera.ephemera());
				} else if (that.settings.config &&
						that.settings.config == 'dom-to-xhtml' &&
						!that.settings.editables['#' + $(editableElement).attr('id')]) {
					return domToXhtml.contentsToXhtml(editableElement, Ephemera.ephemera());
				} else {
					return $(editableElement).html();
				}
			});
		}
	});
});

/*!
* Aloha Editor
* Author & Copyright (c) 2012-2013 Gentics Software GmbH
* aloha-sales@gentics.com
* Licensed unter the terms of http://www.aloha-editor.com/license.html
*
* @overview
* Utility functions for content handling.
*/
define('contenthandler/contenthandler-utils',[
	'jquery',
	'util/html'
], function (
	$,
	Html
) {
	

	/**
	 * Checks whether the markup describes a paragraph that is propped by
	 * a <br> tag but is otherwise empty.
	 *
	 * Will return true for:
	 *
	 * <p id="foo"><br class="bar" /></p>
	 *
	 * as well as:
	 *
	 * <p><br></p>
	 *
	 * @param {string} html Markup
	 * @return {boolean} True if html describes a propped paragraph.
	 */
	function isProppedParagraph(html) {
		var trimmed = $.trim(html);
		if (!trimmed) {
			return false;
		}
		var div = $('<div>' + trimmed + '</div>')[0];
		var first = div.firstChild;
		var containsSingleP = first === div.lastChild && 'P' === first.nodeName;
		if (!containsSingleP) {
			return false;
		}
		var $visible = $(first.childNodes).filter(function (i, node) {
			return Html.isRenderedNode(node);
		});
		return $visible.length === 1 && $visible[0].nodeName === 'BR';
	}

	function wrapContent(content) {
		if (typeof content === 'string') {
			return $('<div>' + content + '</div>');
		}
		if (content instanceof $) {
			return $('<div>').append(content);
		}
		return null;
	}

	return {
		wrapContent: wrapContent,
		isProppedParagraph: isProppedParagraph
	};
});

/* wordcontenthandler.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('contenthandler/wordcontenthandler',[
	'jquery',
	'aloha',
	'aloha/contenthandlermanager',
	'contenthandler/contenthandler-utils',
	'util/dom2',
	'util/html'
], function (
	$,
	Aloha,
	Manager,
	Utils,
	Dom2,
    Html
) {
	

	var jQuery = $;

	/**
	 * Matches the string "mso".
	 *
	 * @type {RexExp}
	 * @const
	 */
	var MSO = /mso/i;

	/**
	 * Matches string starting with "#".
	 *
	 * @type {RexExp}
	 * @const
	 */
	var HASH_HREF = /^#(.*)/;

	/**
	 * Matches numbering or bullet
	 *
	 * @type {RegExp}
	 * @const
	 */
	var NUMBER_OR_BULLET = /^(([0-9]{1,3}\.)|([0-9]{1,3}\))|([a-zA-Z]{1,5}\.)|([a-zA-Z]{1,5}\))|(.))$/;

	/**
	 * Matches numbering
	 *
	 * @type {RegExp}
	 * @const
	 */
	var NUMBER = /^([0-9]{1,3}\.)|([0-9]{1,3}\)|([a-zA-Z]{1,5}\.)|([a-zA-Z]{1,5}\)))$/

	/**
	 * Class used to mark span's that contain the bullet info
	 *
	 * @type {String}
	 * @const
	 */
	var BULLET_CLASS = 'aloha-list-bullet';

	/**
	 * Class used to mark list items
	 *
	 * @type {String}
	 * @const
	 */
	var LIST_ELEMENT_CLASS = 'aloha-list-element';

	/**
	 * Checks whether the given node is empty, ignoring white spaces.
	 *
	 * @param {jQuery.<HTMLElement>} $node
	 * @return {boolean} True if $node is empty.
	 */
	function isEmpty($node) {
		switch ($node[0].nodeName.toLowerCase()) {
		case 'table':
			return 0 === $node.find('tbody,tr').length;
		case 'tbody':
			return 0 === $node.find('tr').length;
		case 'tr':
			return 0 === $node.find('td,th').length;
		default:
			return '' === $.trim($node.text());
		}
	}

	/**
	 * Checks whether the given content element can be assumed to originate
	 * from Microsoft Word.
	 *
	 * @param {jQuery.<HTMLElement>} $content
	 * @return True if the content is determined to originate from an
	 *         office document.
	 */
	function isWordContent($content) {
		// Because reading the html of the content is way faster than iterating
		// its entire node tree, therefore we attempt this first.
		if (0 === $content.length || !MSO.test($content[0].outerHTML)) {
			return false;
		}
		var $nodes = $content.find('*');
		var i;
		var style;
		var classNames;
		// Because if "mso" is found somewhere in the style or class names then
		// the content originated form MS Word.
		for (i = 0; i < $nodes.length; i++) {
			style = $nodes.eq(i).attr('style');
			if (style && style.toLowerCase().indexOf('mso') >= 0) {
				return true;
			}
			classNames = $nodes.eq(i).attr('class');
			if (classNames && classNames.toLowerCase().indexOf('mso') >= 0) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Removes unrendered child nodes from `$content`.
	 * @param {jQuery.<HTMLElement> $content
	 */
	function removeUnrenderedChildNodes($content) {
		var childNodes = $content[0].childNodes;
		var i;
		var len;

		for (i = 0, len = childNodes.length; i < len; i++) {
			if (childNodes[i] && Html.isUnrenderedNode(childNodes[i])) {
				$content[0].removeChild(childNodes[i]);
			}
		}
	}

	/**
	 * Cleanup MS Word HTML.
	 *
	 * @param {jQuery.<HTMLElement>} $content
	 */
	function clean($content) {
		var $nodes = $content.find('*');
		var nodeName;
		var $node;
		var href;
		var i;
		var len;

		for (i = 0, len = $nodes.length; i < len; i++) {
			$node = $nodes.eq(i);
			nodeName = $node[0].nodeName.toLowerCase();

			if ('div' === nodeName || 'span' === nodeName) {

				// Because footnotes for example are wrapped in divs and should
				// be unwrap.
				$node.contents().unwrap();
			} else if ('ul' === nodeName || 'ol' === nodeName) {
				/*
				 * Word renders nested lists as
				 *
				 * <ul>
				 *   <li>Foo</li>
				 *   <ul>
				 *     <li>Bar</li>
				 *   </ul>
				 * </ul>
				 *
				 * instead of
				 *
				 * <ul>
				 *   <li>Foo
				 *     <ul>
				 *       <li>Bar</li>
				 *     </ul>
				 *   </li>
				 * </ul>
				 *
				 * Since the markup is not valid, the nested lists would be removed by
				 * the generic content handler.
				 */
				if ($node.parent().is('ul,ol')) {
					var prev = $node.prev('li');

					if (prev.length > 0) {
						prev.append($node)
					}
				}
			} else if ('td' !== nodeName && isEmpty($node)) {

				// Because any empty element (like spaces wrapped in spans) are
				// not needed, except table cells.
				$node.contents().unwrap();
			}
		}

		removeUnrenderedChildNodes($content);
	}

	/**
	 * Transform Title and Subtitle from MS Word.
	 *
	 * @param {jQuery.<HTMLElement>} $content
	 */
	function transformTitles($content) {
		$content.find('p.MsoTitle').each(function () {
			Aloha.Markup.transformDomObject($(this), 'h1');
		});
		$content.find('p.MsoSubtitle').each(function () {
			Aloha.Markup.transformDomObject($(this), 'h2');
		});
	}

	var WordContentHandler = Manager.createHandler({

		/**
		 * Handle content pasted from Word or Open/Libre Office.
		 *
		 * Tries to detect content pasted from office document and transforms
		 * into clean HTML.
		 *
		 * @param {jQuery.<HTMLElement>|string} content
		 * @return {string} Clean HTML
		 */
		handleContent: function (content) {
			var $content = Utils.wrapContent(content);
			if (!$content) {
				return content;
			}
			if (isWordContent($content)) {
				this.transformWordContent($content);
			}
			return $content.html();
		},

		/**
		 * Check whether the given list span (first span in a paragraph which shall be a list item) belongs to an ordered list
		 * @param listSpan
		 * @return true for ordered lists, false for unordered
		 */
		isOrderedList: function (listSpan) {
			// when the span has fontFamily "Wingdings" it is an unordered list
			if (listSpan.css('fontFamily') == 'Wingdings' || listSpan.css('fontFamily') == 'Symbol') {
				return false;
			}
			// otherwise check for a number, letter or '(' as first character
			return $.trim(listSpan.text()).match(/^([0-9]{1,3}\.)|([0-9]{1,3}\)|([a-zA-Z]{1,5}\.)|([a-zA-Z]{1,5}\)))$/) ? true : false;
		},
		
		/**
		 * Checks if the specified node is enclosed in a <!--[if !supportLists]--> comment.
		 * @param node The Node, whose position should be checked.
		 * @param stopAt An ancestor Node of node, where the search will be stopped (this node will not be searched any more).
		 * @return true if the node is enclosed in a <!--[if !supportLists]--> comment within the stopAt node, otherwise false.
		 */
		checkElementIsEnclosedInListsComment: function (node, stopAt) {
			while (node && node !== stopAt) {
				var sibling = node;
				while ((sibling = sibling.previousSibling) !== null) {
					if (Node.COMMENT_NODE === sibling.nodeType && $.trim(sibling.textContent) === '[if !supportLists]') {
						return true;
					}
				}
				node = node.parentNode;
			}
			return false;
		},

		/**
		 * Try to detect the list type (ordered or bulleted).
		 * Remove the elements that were used to render the numbering or bullets
		 * @param {jQuery.<HTMLElement>} jqElem
		 * @return true for numbered list, false for bulleted list
		 */
		detectListType: function (jqElem) {
			var ordered = false;
			var removeSpan = true;
			// get the first span in the element
			var firstSpan = jQuery(jqElem.find('span.' + BULLET_CLASS));
			if (firstSpan.length === 0) {
				firstSpan = jqElem.find('span').filter(function() {
					var $this = $(this);
					var style = $this.attr('style') || '';
					return style.indexOf('mso-list: Ignore') >= 0 || style.indexOf('mso-list:Ignore') >= 0;
				});
			}
			if (firstSpan.length === 0) {
				firstSpan = jqElem.find('span').eq(0);
				if (firstSpan.length > 0) {
					removeSpan = WordContentHandler.checkElementIsEnclosedInListsComment(firstSpan.get(0), jqElem.get(0));
				}
			}
			if ($.trim(firstSpan.text()).length !== 0) {
				// use the span to detect whether the list shall be ordered or unordered
				ordered = this.isOrderedList(firstSpan);
				// finally remove the span (numbers, bullets are rendered by the browser)
				if (removeSpan) {
					firstSpan.remove();
				}
			} else {
				firstSpan.remove();
				var f = function (index) {
					var el = jQuery(this);
					if (NUMBER_OR_BULLET.test($.trim(el.text()))) {
						ordered = NUMBER.test($.trim(el.text()));
						el.remove();
						return false;
					}
					el.contents().each(f);
				};
				jqElem.contents().each(f);
			}
			return ordered;
		},

		/**
		 * Transform lists pasted from word
		 * @param content
		 */
		transformListsFromWord: function (content) {
			var that = this,
				negateDetectionFilter, detectionFilter, spans,
				paragraphs;

			// first step is to find all paragraphs which will be converted into list elements and mark them by adding the class 'aloha-list-element'
			detectionFilter = 'p.MsoListParagraphCxSpFirst,p.MsoListParagraphCxSpMiddle,p.MsoListParagraphCxSpLast,p.MsoListParagraph,p span';
			paragraphs = content.find(detectionFilter);
			paragraphs.each(function () {
				var jqElem = jQuery(this),
					fontFamily = jqElem.css('font-family') || '',
					msoList = jqElem.css('mso-list') || '',
					style = jqElem.attr('style') || '';

				// detect special classes
				if (jqElem.hasClass('MsoListParagraphCxSpFirst') || jqElem.hasClass('MsoListParagraph')) {
					jqElem.addClass(LIST_ELEMENT_CLASS);
				} else if (fontFamily.indexOf('Symbol') >= 0) {
					jqElem.closest('p').addClass(LIST_ELEMENT_CLASS);
				} else if (fontFamily.indexOf('Wingdings') >= 0) {
					jqElem.closest('p').addClass(LIST_ELEMENT_CLASS);
				} else if (msoList !== '') {
					jqElem.closest('p').addClass(LIST_ELEMENT_CLASS);
				} else if (style.indexOf('mso-list') >= 0) {
					jqElem.closest('p').addClass(LIST_ELEMENT_CLASS);
				}
			});

			// now we search for paragraphs with three levels of nested spans, where the innermost span contains nothing but &nbsp;
			detectionFilter = 'p span span span';
			spans = content.find(detectionFilter);
			spans.each(function () {
				var jqElem = jQuery(this),
				    innerText = jQuery.trim(jqElem.text()).replace(/&nbsp;/g, ''),
					outerText;
				
				if (innerText.length === 0) {
					// check whether the outermost of the three spans contains nothing more than numbering
					outerText = jQuery.trim(jqElem.parent().parent().text()).replace(/&nbsp;/g, '');

					// patterns for list numbering
					// 1.
					// 1)
					// (1)
					// a.
					// a)
					// I.
					// i.
					// o § (or any other single character)
					if (NUMBER_OR_BULLET.test(outerText)) {
						jqElem.closest('p').addClass(LIST_ELEMENT_CLASS);
						jqElem.parent().parent().addClass(BULLET_CLASS);
					}
				}
			});

			// no detect all marked paragraphs and transform into lists
			detectionFilter = 'p.' + LIST_ELEMENT_CLASS;
			// We also have to include font because if IE9
			negateDetectionFilter = ':not(' + detectionFilter + ', font)';
			paragraphs = content.find(detectionFilter);

			if (paragraphs.length > 0) {
				paragraphs.each(function () {
					var jqElem = jQuery(this),
						jqNewLi, jqList, ordered, firstSpan, following, lists, margin, nestLevel;

					jqElem.removeClass(LIST_ELEMENT_CLASS);
					// first remove all font tags
					jqElem.find('font').each(function () {
						jQuery(this).contents().unwrap();
					});

					// initialize the nestlevel and the margin (we will try to detect nested
					// lists by comparing the left margin)
					nestLevel = [];
					margin = parseFloat(jqElem.css('marginLeft'));
					// Fix for not found margin on level 0
					if (isNaN(margin)) {
						margin = 0;
					}
					// this array will hold all ul/ol elements
					lists = [];
					// get all following list elements
					following = jqElem.nextUntil(negateDetectionFilter);

					ordered = that.detectListType(jqElem);

					// create the list element
					jqList = jQuery(ordered ? '<ol></ol>' : '<ul></ul>');
					lists.push(jqList);

					// add a new list item
					jqNewLi = jQuery('<li></li>');

					// add the li into the list
					jqList.append(jqNewLi);

					// append the contents of the old dom element to the li
					jqElem.contents().appendTo(jqNewLi);

					// replace the old dom element with the new list
					jqElem.replaceWith(jqList);

					// now proceed all following list elements
					following.each(function () {
						var jqElem = jQuery(this),
							newMargin, jqNewList;
						
						if (jqElem.is('font')) {
							//Fix for IE9
							return;
						}

						// remove all font tags
						jqElem.find('font').each(function () {
							jQuery(this).contents().unwrap();
						});
						// check the new margin
						newMargin = parseFloat(jqElem.css('marginLeft'));
						// Fix for not found margin on level 0
						if (isNaN(newMargin)) {
							newMargin = 0;
						}

						ordered = that.detectListType(jqElem);

						// check for nested lists by comparing the margins
						if (newMargin > margin) {
							// create a new list
							jqNewList = jQuery(ordered ? '<ol></ol>' : '<ul></ul>');
							// append the new list to the last list item of the prior list
							jqList.children(':last').append(jqNewList);

							// store the list and increase the nest level
							jqList = jqNewList;
							lists.push(jqList);
							nestLevel.push(newMargin);
							margin = newMargin;
						} else if (newMargin < margin && nestLevel.length > 0) {
							while(nestLevel.length > 0 && nestLevel[nestLevel.length - 1] > newMargin) {
								nestLevel.pop();
								lists.pop();
							}
							// end nested list and append element to outer list
							jqList = lists[lists.length - 1];
							margin = newMargin;
						}

						// create a list item
						jqNewLi = jQuery('<li></li>');
						// add the li into the list
						jqList.append(jqNewLi);

						// append the contents of the old dom element to the li
						jqElem.contents().appendTo(jqNewLi);
						// remove the old dom element
						jqElem.remove();
					});
				});
			}
		},

		/**
		 * Remove paragraph numbering from TOC feature
		 * @param content
		*/
		removeParagraphNumbering: function ( content ) {
			var detectionFilter = 'h1,h2,h3,h4,h5,h6',
				paragraphs = content.find(detectionFilter);
			
			if (paragraphs.length > 0) {
				paragraphs.each(function () {
					var jqElem = jQuery(this),
						spans = jqElem.find('span'),
						links = jqElem.find('a');
				
					// remove TOC numbering
					spans.each(function () {
						if ( jQuery.trim(jQuery(this).text()).match(/^([\.\(]?[\d\D][\.\(]?){1,4}$/) ) {
							jQuery(this).remove();
						}
					});
				
					// remove TOC anchor links
					links.each(function () {
						// no href, so it's an anchor
						if ( typeof jQuery(this).attr('href') === 'undefined' ) {
							jQuery(this).contents().unwrap();
						}
					});
				
				});
			}
		},

		
		/**
		 * Transform TOC
		 * @param content
		*/
		transformToc: function ( content ) {
			var detectionFilter = '[class*=MsoToc]',
				paragraphs = content.find(detectionFilter);

			paragraphs.each(function () {
				var jqElem = jQuery(this),
					spans = jqElem.find('span'),
					links = jqElem.find('a');

				// a table of contents entry looks like
				// 1. Title text ... 5
				// we get rid of the "... 5" part which repesents the page number
				spans.each(function () {
					if ( jQuery(this).attr('style') && jQuery(this).attr('style').search('mso-hide') > -1 ) {
						jQuery(this).remove();
					}
					jQuery(this).contents().unwrap();
				});

				// remove the anchor link of the toc item
				links.each(function () {
					jQuery(this).contents().unwrap();
				});
			});
		},

		/**
		 * This is the main transformation method
		 * @param {jQuery.<HTMLElement>} $content
		 */
		transformWordContent: function ($content) {
			this.transformToc($content);
			this.removeParagraphNumbering($content);
			this.transformListsFromWord($content);
			transformTitles($content);
			clean($content);
		}
	});
	
	return WordContentHandler;
});

/* genericcontenthandler.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('contenthandler/genericcontenthandler',[
	'jquery',
	'aloha',
	'aloha/contenthandlermanager',
	'contenthandler/contenthandler-utils'
], function (
	$,
	Aloha,
	Manager,
	Utils
) {
	

	/**
	 * Tags used for semantic formatting
	 * @type {Array.<String>}
	 * @see GenericContentHandler#transformFormattings
	 */
	var formattingTags = ['strong', 'em', 's', 'u', 'strike'];

	/**
	 * Transforms all tables in the given content to make them ready to for
	 * use with Aloha's table handling.
	 *
	 * Cleans tables of their unwanted attributes.
	 * Normalizes table cells.
	 *
	 * @param {jQuery.<HTMLElement>} $content
	 */
	function prepareTables($content) {
		// Because Aloha does not provide a way for the editor to
		// manipulate borders, cellspacing, cellpadding in tables.
		// @todo what about width, height?
		$content.find('table')
			.removeAttr('cellpadding')
			.removeAttr('cellspacing')
			.removeAttr('border')
			.removeAttr('border-top')
			.removeAttr('border-bottom')
			.removeAttr('border-left')
			.removeAttr('border-right');

		$content.find('td').each(function () {
			var td = this;

			// Because cells with a single empty <p> are rendered to appear
			// like empty cells, it simplifies the handeling of cells to
			// normalize these table cells to contain actual white space
			// instead.
			if (Utils.isProppedParagraph(td.innerHTML)) {
				td.innerHTML = '&nbsp;';
			}

			// Because a single <p> wrapping the contents of a <td> is
			// initially superfluous and should be stripped out.
			var $p = $('>p', td);
			if (1 === $p.length) {
				$p.contents().unwrap();
			}
		});

		// Because Aloha does not provide a means for editors to manipulate
		// these properties.
		$content.find('table,th,td,tr')
			.removeAttr('width')
			.removeAttr('height')
			.removeAttr('valign');

		// Because Aloha table handling simply does not regard colgroups.
		// @TODO Use sanitize.js?
		$content.find('colgroup').remove();
	}

	/**
	 * Return true if the nodeType is allowed in the settings,
	 * Aloha.settings.contentHandler.allows.elements
	 * 
	 * @param {String} nodeType	The tag name of the element to evaluate
	 * 
	 * @return {Boolean}
	 */
	function isAllowedNodeName(nodeType) {
		return !!(
			Aloha.settings.contentHandler &&
			Aloha.settings.contentHandler.allows &&
			Aloha.settings.contentHandler.allows.elements &&
			($.inArray(
		              nodeType.toLowerCase(), 
				      Aloha.settings.contentHandler.allows.elements
				         ) !== -1
			   )
		);
	}

	/**
	 * Replaces unnecessary new line characters within text nodes in Word HTML
	 * with a space.
	 *
	 * @param {jQuery.<HTMLElement>} $content
	 */
	function replaceNewlines($content) {
		$content.contents().each(function (index, node) {
			if (3 === node.nodeType) {
				node.nodeValue = node.nodeValue.replace(/[\r\n]+/gm, ' ');
			} else {
				replaceNewlines($(node));
			}
		});
	}

	var GenericContentHandler = Manager.createHandler({

		/**
		 * Transforms pasted content to make it safe and ready to be used in
		 * Aloha Editables.
		 *
		 * @param {jQuery.<HTMLElement>|string} content
		 * @return {string} Clean HTML
		 */
		handleContent: function (content) {
			var $content = Utils.wrapContent(content);
			if (!$content) {
				return content;
			}

			// If an aloha-block is found inside the pasted content, no modify
			// should be made in the pasted content because it can be assumed
			// this is content deliberately placed by Aloha and should not be
			// cleaned.
			if ($content.find('.aloha-block').length) {
				return $content.html();
			}

			prepareTables($content);
			this.cleanLists($content);
			this.removeComments($content);
			this.unwrapTags($content);
			this.removeStyles($content);
			this.removeNamespacedElements($content);
			//this.transformLinks($content);

			var transformFormatting = true;

			if (Aloha.settings.contentHandler &&
				Aloha.settings.contentHandler.handler &&
				Aloha.settings.contentHandler.handler.generic &&
				typeof Aloha.settings.contentHandler.handler.generic.transformFormattings !== 'undefinded' &&
				!Aloha.settings.contentHandler.handler.generic.transformFormattings) {
				transformFormatting = false;
			}

			if (transformFormatting) {
			    this.transformFormattings($content);
			}

			replaceNewlines($content);

			return $content.html();
		},

		/**
		 * Cleans lists.
		 * The only allowed children of ol or ul elements are li's. Everything
		 * else will be removed.
		 *
		 * See http://validator.w3.org/check with following invalid markup for
		 * example:
		 * <!DOCTYPE html><head><title></title></head><ul><li>ok</li><ol></ol></ul>
		 *
		 * @param {jQuery.<HTMLElement>} $content
		 */
		cleanLists: function ($content) {
			$content.find('ul,ol').find('>:not(li)').remove();

			// Remove paragraphs inside list elements, if they are
			// the only child. e.g.: li > p > text
			// This has been observed to happen with Word documents
			$content.find("li").each(function() {
				var $li = $(this);
				var $children = $li.children();
				if ($children.length === 1 && $children.is("p")) {
					$children.contents().appendTo($li);
					$children.remove();
				}
			});
		},

		/**
		 * Transform formattings
		 * @param content
		 */
		transformFormattings: function (content) {
			// find all formattings we will transform
			// @todo this makes troubles -- don't change semantics! at least in this way...

			var selectors = [],
				i;

			for (i = 0; i < formattingTags.length; i++) {
				if (!isAllowedNodeName(formattingTags[i])) {
					selectors.push(formattingTags[i]);
				}
			}

			content.find(selectors.join(',')).each(function () {
				if (this.nodeName === 'STRONG') {
					// transform strong to b
					Aloha.Markup.transformDomObject($(this), 'b');
				} else if (this.nodeName === 'EM') {
					// transform em to i
					Aloha.Markup.transformDomObject($(this), 'i');
				} else if (this.nodeName === 'S' || this.nodeName == 'STRIKE') {
					// transform s and strike to del
					Aloha.Markup.transformDomObject($(this), 'del');
				} else if (this.nodeName === 'U') {
					// transform u?
					$(this).contents().unwrap();
				}
			});
		},

		/**
		 * Transform links
		 * @param content
		 */
		transformLinks: function (content) {
			// find all links and remove the links without href (will be destination anchors from word table of contents)
			// aloha is not supporting anchors at the moment -- maybe rewrite anchors in headings to "invisible"
			// in the test document there are anchors for whole paragraphs --> the whole P appear as link
			content.find('a').each(function () {
				if (typeof $(this).attr('href') === 'undefined') {
					$(this).contents().unwrap();
				}
			});
		},

		/**
		 * Remove all comments
		 * @param content
		 */
		removeComments: function (content) {
			var that = this;

			// ok, remove all comments
			content.contents().each(function () {
				if (this.nodeType === 8) {
					$(this).remove();
				} else {
					// do recursion
					that.removeComments($(this));
				}
			});
		},

		/**
		 * Remove some unwanted tags from content pasted
		 * @param content
		 */
		unwrapTags: function (content) {
			var that = this;

			// Note: we exclude all elements (they will be spans) here, that have the class aloha-wai-lang
			// TODO find a better solution for this (e.g. invent a more generic aloha class for all elements, that are
			// somehow maintained by aloha, and are therefore allowed)
			content.find('span,font,div').not('.aloha-wai-lang').each(function () {
				if (this.nodeName == 'DIV') {
					// safari and chrome cleanup for plain text paste with working linebreaks
					if (this.innerHTML === '<br>') {
						$(this).contents().unwrap();
					} else {
						$(Aloha.Markup.transformDomObject($(this), 'p').append('<br>')).contents().unwrap();
					}
				} else {
					$(this).contents().unwrap();
				}
			});
		},

		/**
		 * Remove styles
		 * @param content
		 */
		removeStyles: function (content) {
			var that = this;

			// completely remove style tags
			content.children('style').filter(function () {
				return this.contentEditable !== 'false';
			}).remove();

			// remove style attributes and classes
			content.children().filter(function () {
				return this.contentEditable !== 'false';
			}).each(function () {
				$(this).removeAttr('style').removeClass();
				that.removeStyles($(this));
			});
		},

		/**
		 * Remove all elements which are in different namespaces
		 * @param content
		 */
		removeNamespacedElements: function ($content) {
			// get all elements
			$content.find('*').each(function () {
				// try to determine the namespace prefix ('prefix' works for W3C
				// compliant browsers, 'scopeName' for IE)

				var nsPrefix = this.prefix ? this.prefix
						: (this.scopeName ? this.scopeName : undefined);
				// when the prefix is set (and different from 'HTML'), we remove the
				// element
				if ((nsPrefix && nsPrefix !== 'HTML') || this.nodeName.indexOf(':') >= 0) {
					var $this = $(this), $contents = $this.contents();
					if ($contents.length) {
						// the element has contents, so unwrap the contents
						$contents.unwrap();
					} else {
						// the element is empty, so remove it
						$this.remove();
					}
				}
			});
		}
	});

	return GenericContentHandler;
});

/**
 * Copyright (c) 2010 by Gabriel Birke
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the 'Software'), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

function Sanitize(){
  var options;
  options = arguments[0] || {};
  this.jQuery = arguments[1] || {};
  this.config = {}
  this.config.elements = options.elements ? options.elements : [];
  this.config.attributes = options.attributes ? options.attributes : {};
  this.config.attributes[Sanitize.ALL] = this.config.attributes[Sanitize.ALL] ? this.config.attributes[Sanitize.ALL] : [];
  this.config.allow_comments = options.allow_comments ? options.allow_comments : false;
  this.allowed_elements = {};
  this.config.protocols = options.protocols ? options.protocols : {};
  this.config.add_attributes = options.add_attributes ? options.add_attributes  : {};
  this.dom = options.dom ? options.dom : document;
	for (var i = 0, len = this.config.elements.length; i < len; i++) {
		this.allowed_elements[this.config.elements[i]] = true;
	}
  this.config.remove_element_contents = {};
  this.config.remove_all_contents = false;
  if(options.remove_contents) {

     if (options.remove_contents instanceof Array) {
		  var elementContents = this.config.remove_element_contents;
		  for (var i = 0, len = options.remove_contents.length; i < len; i++) {
			  elementContents[options.remove_contents[i]] = true;
		  }
	  }
	  else {
		  this.config.remove_all_contents = true;
	  }
  }
  this.transformers = options.transformers ? options.transformers : [];

  // filters might let the sanitizer stop clean elements (and their children)
  this.filters = options.filters ? options.filters : [];
}

Sanitize.REGEX_PROTOCOL = /^([A-Za-z0-9\+\-\.\&\;\*\s]*?)(?:\:|&*0*58|&*x0*3a)/i
Sanitize.RELATIVE = '__relative__'; // emulate Ruby symbol with string constant

Sanitize.prototype.clean_node = function(container) {
  var fragment = this.dom.createDocumentFragment();
  this.current_element = fragment;
  this.whitelist_nodes = [];

  

  /**
   * Utility function to check if an element exists in an array
   */
  function _array_index(needle, haystack) {
	  var i,
	      len = haystack.length
	  for (i = 0; i < len; i++) {
		  if (haystack[i] === needle)
			  return i;
	  }
	  return -1;
  }

	function _merge_arrays_uniq() {
		var result = [],
		    uniq_hash = {},
			i,
		    len = arguments.length,
		    arg;
		for (i = 0; i < len; i++) {
			arg = arguments[i];
			if (!arg || !arg.length)
				continue;
			for (var j = 0, argLen = arg.length; j < argLen; j++) {
				if (uniq_hash[arg[j]])
					continue;
				uniq_hash[arg[j]] = true;
				result.push(arg[j]);
			}
		}
		return result;
	}
  
  /**
   * Clean function that checks the different node types and cleans them up accordingly
   * @param elem DOM Node to clean
   */
  function _clean(elem) {
    var clone,
	    i,
	    len = this.filters.length;

    // check whether the elem passes all of the filters
	  for (i = 0; i < len; i++) {
		  if (!this.filters[i](elem)) {
			  clone = elem.cloneNode(true);
			  this.current_element.appendChild(clone);
			  return;
		  }
	  }

    switch(elem.nodeType) {
      // Element
      case 1:
        _clean_element.call(this, elem)
        break;
      // Text
      case 3:
        var clone = elem.cloneNode(false);
        this.current_element.appendChild(clone);
        break;
      // Entity-Reference (normally not used)
      case 5:
        var clone = elem.cloneNode(false);
        this.current_element.appendChild(clone);
        break;
      // Comment
      case 8:
        if(this.config.allow_comments) {
          var clone = elem.cloneNode(false);
          this.current_element.appendChild(clone);
        }
      default:
        //console.log("unknown node type", elem.nodeType)
    }
 
  }
  
  function _clean_element(elem) {
    var i, len, parent_element, name, allowed_attributes, attr, attr_name, attr_node, protocols, del, attr_ok;
    var transform = _transform_element.call(this, elem);
    var jQuery = this.jQuery;
    var isIE7 = Aloha.browser.msie && Aloha.browser.version === "7.0";
    
    elem = transform.node;
    name = elem.nodeName.toLowerCase();
    
    // check if element itself is allowed
    parent_element = this.current_element;
    if (this.allowed_elements[name] || transform.whitelist) {
        this.current_element = this.dom.createElement(elem.nodeName);
        parent_element.appendChild(this.current_element);

      // clean attributes
      allowed_attributes = _merge_arrays_uniq(
        this.config.attributes[name],
        this.config.attributes['__ALL__'],
        transform.attr_whitelist
      );
	    len = allowed_attributes.length;
	    for (i = 0; i < len; i++) {
		    attr_name = allowed_attributes[i];
		    attr = elem.attributes[attr_name];
		    if (attr) {
			    attr_ok = true;
			    // Check protocol attributes for valid protocol
			    if (this.config.protocols[name] && this.config.protocols[name][attr_name]) {
				    protocols = this.config.protocols[name][attr_name];
				    del = attr.nodeValue.toLowerCase().match(Sanitize.REGEX_PROTOCOL);
				    if (del) {
					    attr_ok = (_array_index(del[1], protocols) != -1);
				    }
				    else {
					    attr_ok = (_array_index(Sanitize.RELATIVE, protocols) != -1);
				    }
			    }
			    if (attr_ok) {
				    // sanitize does not work in IE7. It tries to set the style attribute via setAttributeNode() and this is know to not work in IE7
				    // (see http://www.it-blogger.com/2007-06-22/microsofts-internetexplorer-und-mitglied-nicht-gefunden/ as a reference)
				    if (!isIE7 || (isIE7 && "style" !== attr_name)) {
					    this.current_element.setAttribute(attr_name, attr.nodeValue)
				    }
			    }
		    }
      }

      // Add attributes
      if(this.config.add_attributes[name]) {
        for(attr_name in this.config.add_attributes[name]) {
        	// sanitize does not work in IE7. It tries to set the style attribute via setAttributeNode() and this is know to not work in IE7
			// (see http://www.it-blogger.com/2007-06-22/microsofts-internetexplorer-und-mitglied-nicht-gefunden/ as a reference)
        	if(!isIE7 || (isIE7 && "style" !== attr_name)) {
	          this.current_element.setAttribute(attr_name, this.config.add_attributes[name][attr_name]);
        	}
        }
      }
    } // End checking if element is allowed
    // If this node is in the dynamic whitelist array (built at runtime by
    // transformers), let it live with all of its attributes intact.
    else if(_array_index(elem, this.whitelist_nodes) != -1) {
      this.current_element = elem.cloneNode(true);
      // Remove child nodes, they will be sanitiazied and added by other code
	    var childNodesLength = this.current_element.childNodes.length;
	    while(childNodesLength > 0) {
            this.current_element.removeChild(this.current_element.firstChild);
      }
      parent_element.appendChild(this.current_element);
    }

    // iterate over child nodes
    if(!this.config.remove_all_contents && !this.config.remove_element_contents[name]) {
	    var childNodes = elem.childNodes,
		    len = childNodes.length;
	    for (i = 0; i < len; i++) {
		    _clean.call(this, childNodes[i]);
	    }
    }

    // some versions of IE don't support normalize.
    if(this.current_element.normalize) {
      this.current_element.normalize();
    }
    this.current_element = parent_element;
  } // END clean_element function
  
  function _transform_element(node) {
    var output = {
      attr_whitelist:[],
      node: node,
      whitelist: false
    };
    var transform,
	    i,
	    j,
	    transLength = this.transformers.length,
	    len;
	for (i = 0; i < transLength; i++) {
      transform = this.transformers[i]({
        allowed_elements: this.allowed_elements,
        config: this.config,
        node: node,
        node_name: node.nodeName.toLowerCase(),
        whitelist_nodes: this.whitelist_nodes,
        dom: this.dom
      });
      if(transform == null) 
        continue;
      else if(typeof transform == 'object') {
        if(transform.whitelist_nodes && transform.whitelist_nodes instanceof Array) {
	      len = transform.whitelist_nodes.length
	      for (j = 0; j < len; j++) {
            if(_array_index(transform.whitelist_nodes[j], this.whitelist_nodes) == -1) {
              this.whitelist_nodes.push(transform.whitelist_nodes[j]);
            }
          }
        }
        output.whitelist = transform.whitelist ? true : false;
        if(transform.attr_whitelist) {
          output.attr_whitelist = _merge_arrays_uniq(output.attr_whitelist, transform.attr_whitelist);
        }
        output.node = transform.node ? transform.node : output.node;
      }
      else {
        throw new Error("transformer output must be an object or null");
      }
    }
    return output;
  }


  var childNodes = container.childNodes,
      i;
  for (i = 0, len = childNodes.length; i < len; i++) {
    _clean.call(this, childNodes[i]);
  }
  
  if(fragment.normalize) {
    fragment.normalize();
  }
  
  return fragment;
  
};
define("vendor/sanitize", function(){});

/* sanitizecontenthandler.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('contenthandler/sanitizecontenthandler',[
	'aloha/core',
	'jquery',
	'aloha/contenthandlermanager',
	'aloha/plugin',
	'aloha/console',
	'vendor/sanitize'
],
function ( Aloha, jQuery, ContentHandlerManager, Plugin, console ) {
	
	
	var sanitize;
	
	// predefined set of sanitize options if no dynamic or custom config is used
	if( !Aloha.defaults.sanitize ) {
		Aloha.defaults.sanitize = {};
	}

	// very restricted sanitize config
	Aloha.defaults.sanitize.restricted = {
		elements: [ 'b', 'em', 'i', 'strong', 'u', 'del', 'p', 'span', 'div', 'br' ]
	};

	// sanitize  config allowing a bit more (no tables)
	Aloha.defaults.sanitize.basic = {
		elements: [
			'a', 'abbr', 'b', 'blockquote', 'br', 'cite', 'code', 'dd', 'del', 'dl', 'dt', 'em',
			'i', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong', 'sub',
			'sup', 'u', 'ul' ],

		attributes: {
			'a' : ['href'],
			'blockquote' : ['cite'],
			'q' : ['cite'],
			'abbr': ['title']
		},

		//add_attributes: {
			//  'a': {'rel': 'nofollow'}
		//},

		protocols: {
			'a' : {'href': ['ftp', 'http', 'https', 'mailto', '__relative__']},
			'blockquote' : {'cite': ['http', 'https', '__relative__']},
			'q' : {'cite': ['http', 'https', '__relative__']}
		}
	};

	// relaxed sanitize config allows also tables
	Aloha.defaults.sanitize.relaxed = {
		elements: [
			'a', 'abbr', 'b', 'blockquote', 'br', 'caption', 'cite', 'code', 'col',
			'colgroup', 'dd', 'del', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
			'i', 'img', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong',
			'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'u',
			'ul', 'span', 'hr', 'object', 'div'
		],

		attributes: {
			'a': ['href', 'title', 'id', 'class', 'target', 'data-gentics-aloha-repository', 'data-gentics-aloha-object-id'],
			'div': ['id','class','style'],
			'abbr': ['title'],
			'blockquote': ['cite'],
			'br': ['class'],
			'col': ['span', 'width'],
			'colgroup': ['span', 'width'],
			'img': ['align', 'alt', 'height', 'src', 'title', 'width', 'class', 'data-caption', 'data-align', 'data-width', 'data-original-image'],
			'ol': ['start', 'type'],
			'p': ['class', 'style', 'id'],
			'q': ['cite'],
			'table': ['summary', 'width'],
									// For IE7 it matters the uppercase 'S' in rowSpan, colSpan
			'td': ['abbr', 'axis', 'colSpan', 'rowSpan', 'colspan', 'rowspan', 'width'],
			'th': ['abbr', 'axis', 'colSpan', 'rowSpan', 'colspan', 'rowspan', 'scope', 'width'],
			'ul': ['type'],
			'span': ['class','style','lang','xml:lang','role']
		},

		protocols: {
			'a': {'href': ['ftp', 'http', 'https', 'mailto', '__relative__']},
			'blockquote': {'cite': ['http', 'https', '__relative__']},
			'img': {'src' : ['http', 'https', '__relative__']},
			'q': {'cite': ['http', 'https', '__relative__']}
		}
	};

	function initSanitize (configAllows) {
		var 
			filter = [ 'restricted', 'basic', 'relaxed' ],
			config = Aloha.defaults.supports; // @TODO: needs to be implemented into all plugins

		// @TODO move to Aloha.settings.contentHandler.sanitize.allows ?
		if (Aloha.settings.contentHandler.allows) {
			config = Aloha.settings.contentHandler.allows;
		}

		// @TODO think about Aloha.settings.contentHandler.sanitize name/options
		if (typeof Aloha.settings.contentHandler.sanitize === 'string' &&
			jQuery.inArray(Aloha.settings.contentHandler.sanitize, filter) > -1) {
			config = Aloha.defaults.sanitize[Aloha.settings.contentHandler.sanitize];
		} else if (typeof Aloha.settings.contentHandler.sanitize === 'object') {
			config = Aloha.settings.contentHandler.sanitize;
		} else if (!config) {
			// use relaxed filter by default
			config = Aloha.defaults.sanitize.relaxed;
		}

		if (configAllows) {
			config = configAllows;
		}

		// add a filter to stop cleaning elements with contentEditable "false"
		config.filters = [function (elem) {
			return elem.contentEditable != "false";
		}];
		sanitize = new Sanitize(config, jQuery);
	}

	var SanitizeContentHandler = ContentHandlerManager.createHandler({
		/**
		 * Handle the content from eg. paste action and sanitize the html
		 * @param content
		 */
		handleContent: function (content, options, editable)  {
			if (!editable) {
				return content;
			}

			var sanitizeConfig;
			var contentHandlerConfig;

			if (Aloha.settings.contentHandler &&
			    Aloha.settings.contentHandler.handler &&
			    Aloha.settings.contentHandler.handler.sanitize) {
				// individual sanitize config per editable -- should support merging of configs from other plugins ...
				if (Aloha.settings.contentHandler.handler.sanitize) {
					contentHandlerConfig = Aloha.settings.contentHandler.handler.sanitize;
				}
				var containerId = contentHandlerConfig['#' + editable.getId()];
				var containerClassAttr = editable.obj.attr('class');

				if (typeof containerId !== 'undefined') {
					sanitizeConfig = contentHandlerConfig;
				} else if (typeof containerClassAttr !== 'undefined') {
					var containerClasses = containerClassAttr.split(' ');
					for (var i = 0; i < containerClasses.length; i++) {
						if (typeof contentHandlerConfig['.' + containerClasses[i]] !== 'undefined') {
							sanitizeConfig = contentHandlerConfig['.' + containerClasses[i]];
						}
					}
				}
			}

			if ( typeof sanitize === 'undefined' || typeof sanitizeConfig !== 'undefined') {
				initSanitize(sanitizeConfig);
			}

			if (typeof content === 'string'){
				content = jQuery('<div>' + content + '</div>').get(0);
			} else if (content instanceof jQuery) {
				content = jQuery('<div>').append(content).get(0);
			}

			return jQuery('<div>').append(sanitize.clean_node(content)).html();
		}
	});

	return SanitizeContentHandler;
});

/*!
 * Aloha Editor
 * Author & Copyright (c) 2010-2013 Gentics Software GmbH
 * aloha-sales@gentics.com
 * Licensed unter the terms of http://www.aloha-editor.com/license.html
 *
 * @overview
 * Prepares block-level elements in contents of editables that are initialized
 * for editing ('initEditing'), or when exporting contents of editable for
 * saving ('getContents').
 */
define('contenthandler/blockelementcontenthandler',[
	'jquery',
	'aloha/core',
	'aloha/contenthandlermanager',
	'contenthandler/contenthandler-utils',
	'util/functions',
	'util/html',
	'util/arrays'

], function (
	$,
	Aloha,
	ContentHandlerManager,
	Utils,
	Functions,
	Html,
	Arrays
) {
	

	var blocksSelector = Html.BLOCKLEVEL_ELEMENTS.join();
	var nonVoidBlocksSelector = Arrays.subtract(
			Html.BLOCKLEVEL_ELEMENTS,
			Html.VOID_ELEMENTS
		).join();

	var NOT_ALOHA_BLOCK_FILTER = ':not(.aloha-block)';

	var isNotIgnorableWhitespace =
			Functions.complement(Html.isIgnorableWhitespace);

	/**
	 * Removes the <br> tag that is at the end of the given container.
	 * Invisible white spaces are ignored.
	 *
	 * @param {number} i Index of element in its collection. (Unused)
	 * @param {HTMLElement} element The container in which to remove the <br>.
	 */
	function removeTrailingBr(i, element) {
		var node = Html.findNodeRight(
			element.lastChild,
			isNotIgnorableWhitespace
		);
		if (node && 'br' === node.nodeName.toLowerCase()) {
			$(node).remove();
		}
	}

	/**
	 * Prepares this content for editing
	 *
	 * @param {number} i Index of element in its collection. (Unused)
	 * @param {HTMLElement} element
	 */
	function prepareForEditing(i, element) {
		var $element = $(element);

		$element.filter(nonVoidBlocksSelector).filter(':empty').remove();

		if ($.browser.msie) {
			// Because even though content edited by Aloha Editor is no longer
			// exported with propping <br>'s that are annotated with
			// "aloha-end-br" classes,  this clean-up still needs to be done for
			// content that was edited using legacy Aloha Editor.
			$element.filter('br.aloha-end-br').remove();

			// Because IE's Trident engine goes against W3C's HTML specification
			// by rendering empty block-level elements with height if they are
			// contentEditable.  Propping <br> elements therefore result in 2
			// lines being displayed rather than 1 (which was the intention of
			// having the propping <br> element is).  Because these empty
			// content editable block-level elements are not rendered invisibly
			// in IE, we can remove the propping <br> in otherwise empty
			// block-level elements.
			$element.filter(blocksSelector).each(removeTrailingBr);
		}

		$element.children(NOT_ALOHA_BLOCK_FILTER).each(prepareForEditing);
	}

	/**
	 * Prepares the content for editing in IE versions older than version 8.
	 *
	 * Ensure that all empty blocklevel elements must contain a zero-width
	 * whitespace.
	 *
	 * @param {number} i Unused
	 * @param {HTMLElement} element
	 */
	function prepareEditingInOldIE(i, element) {
		var $element = $(element);
		$element.filter(nonVoidBlocksSelector).append('\u200b');
		$element.children(NOT_ALOHA_BLOCK_FILTER).each(prepareEditingInOldIE);
	}

	/**
	 * For a given DOM element, will make sure that it, and every one of its
	 * child nodes, which is a block-level element ends with a <br> node.
	 *
	 * This ensures that a block is rendered visibly (with atleast one character
	 * height).
	 *
	 * @param {number} i Unused
	 * @param {HTMLElement} element
	 */
	function propBlockElements(i, element) {
		var $element = $(element);
		if ($.browser.msie) {
			$element.filter(nonVoidBlocksSelector).filter(':empty').append('<br/>');
			$element.children(NOT_ALOHA_BLOCK_FILTER).each(propBlockElements);
		}
	}

	return ContentHandlerManager.createHandler({
		handleContent: function handleBlockLevelContent(content, options) {
			if (!options) {
				return content;
			}
			var $content = Utils.wrapContent(content);
			if (!$content) {
				return content;
			}
			switch (options.command) {
			case 'initEditable':
				$content.children(NOT_ALOHA_BLOCK_FILTER)
				        .each(prepareForEditing);

				if ($.browser.msie && $.browser.version <= 7) {
					$content.children(NOT_ALOHA_BLOCK_FILTER)
					        .each(prepareEditingInOldIE);
				}
				break;
			case 'getContents':
				$content.children(NOT_ALOHA_BLOCK_FILTER)
				        .each(propBlockElements);
				break;
			}
			return $content.html();
		}
	});
});

/*global define: true */

/*!
* Aloha Editor
* Author & Copyright (c) 2010-2012 Gentics Software GmbH
* aloha-sales@gentics.com
* Licensed unter the terms of http://www.aloha-editor.com/license.html
*/

/**
 * @name contenthandler
 */
define('contenthandler/contenthandler-plugin',[
	'jquery',
	'aloha/plugin',
	'aloha/contenthandlermanager',
	'contenthandler/wordcontenthandler',
	'contenthandler/genericcontenthandler',
	'contenthandler/sanitizecontenthandler',
	'contenthandler/blockelementcontenthandler'
], function (
	$,
	Plugin,
	ContentHandlerManager,
	WordContentHandler,
	GenericContentHandler,
	SanitizeContentHandler,
	BlockelementContentHandler
) {
	

	/**
	 * The default content handlers which will always be loaded with Aloha
	 * when the contenthandler plugin is initialized.
	 *
	 * @object<string, ContentHandler>
	 * @const
	 */
	var DEFAULT_HANDLERS = {
		word: WordContentHandler,
		generic: GenericContentHandler,
		sanitize: SanitizeContentHandler,
		blockelement: BlockelementContentHandler
	};

	var ContentHandler = Plugin.create('contenthandler', {

		/**
		 * Will simply register the default content handlers.
		 *
		 * @override
		 */
		init: function () {
			var handler;
			for (handler in DEFAULT_HANDLERS) {
				if (DEFAULT_HANDLERS.hasOwnProperty(handler)) {
					ContentHandlerManager.register(handler,
							DEFAULT_HANDLERS[handler]);
				}
			}
		}
	});

	return ContentHandler;
});

/* inserthtml.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('commands/inserthtml',
['aloha/core', 'jquery', 'aloha/command', 'aloha/selection', 'util/dom', 'aloha/contenthandlermanager', 'aloha/console'],
function (Aloha, jQuery, command, selection, dom, ContentHandlerManager, console) {
	

	// Exported commands
	command.register('inserthtml', {
		action: function (value, range) {
			var 
				$editable = jQuery(dom.getEditingHostOf(range.startContainer)),
				cac = range.commonAncestorContainer,
				i,
				selectedRange,
				domNodes = [];
			
			/**
			 * Paste the given object into the current selection.
			 * If inserting fails (because the object is not allowed to be inserted), unwrap the contents and try with that.
			 * @param object object to be pasted
			 */
			function pasteElement(object) {
				var $object = jQuery(object),
					contents;

				// try to insert the element into the DOM with limit the editable host
				// this fails when an element is not allowed to be inserted
				if (!dom.insertIntoDOM($object, range, $editable, false)) {
					
					// if that is not possible, we unwrap the content and insert every child element
					contents = $object.contents();

					// when a block level element was unwrapped, we at least insert a break
					if (dom.isBlockLevelElement(object) || dom.isListElement(object)) {
						pasteElement(jQuery('<br/>').get(0));
					}

					// and now all children (starting from the back)
					for (var i = contents.length - 1; i >= 0; --i) {
						pasteElement(contents[i]);
					}
				}
			}

			// apply content handler to cleanup inserted data
			//if (typeof Aloha.settings.contentHandler.insertHtml === 'undefined') {
			// just use all registerd content handler or specity Aloha.defaults.contentHandler.insertHtml manually?
			//	Aloha.settings.contentHandler.insertHtml = Aloha.defaults.contentHandler.insertHtml;
			//}
			value = ContentHandlerManager.handleContent(value, { contenthandler: Aloha.settings.contentHandler.insertHtml});

			// allowed values are string or jQuery objects
			// add value to a container div
			if (typeof value === 'string') {
				value = jQuery('<div>' + value + '</div>');
			} else if (value instanceof jQuery) {
				value = jQuery('<div>').append(value);
			} else {
				throw "INVALID_VALUE_ERR";
			}
			
			// get contents of container div
			domNodes = value.contents();
			
			// check if range starts an ends in same editable host
//			if (!(dom.inSameEditingHost(range.startContainer, range.endContainer))) {
//				throw "INVALID_RANGE_ERR";
//			}
			
			// delete currently selected contents
			dom.removeRange(range);
			
			for (i = domNodes.length - 1; i >= 0; --i) {
				// insert the elements
				pasteElement(domNodes[i]);
			}

			// Call collapse() on the context object's Selection,
			// with last child's parent as the first argument and one plus its index as the second.
			if (domNodes.length > 0) {
				range = dom.setCursorAfter(domNodes.get(domNodes.length - 1));
			} else {
				// if nothing was pasted, just reselect the old range
				range.select();
			}

			dom.doCleanup({merge: true, removeempty: true}, range, cac);
			//In some cases selecting the range does not work properly 
			//e.g. when pasting from word in an h2 after the first character in IE
			//in these cases we should fail gracefully.
			//TODO check why the selection is failing
			try {
				range.select();
			} catch (e) {
				console.warn('Error:', e);
			}

		}
	});

});

/* commands-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('commands/commands-plugin',
['aloha/command', './inserthtml'],
function (command, commands) {
	
});

define('block/block-utils',[
	'jquery',
	'util/dom',
	'util/dom2',
	'util/html',
	'aloha/ephemera'
], function (
	$,
	DomLegacy,
	Dom,
	Html,
	Ephemera
) {
	

	/**
	 * Class name for landing element.
	 */
	var LANDING_ELEMENT_CLASS = 'aloha-caret-landing';

	/**
	 * Check if a white space span should be removed
	 *
	 * @param {DOMElement} node where the white space span has been inserted
	 * @returns {Boolean}
	 */
	function isObsoleteLandingNode(node) {
		return 'SPAN' === node.nodeName &&
			(node.childNodes.length === 0 || node.innerHTML === '&nbsp;');
	}

	/**
	 * Creates unique class name for `$blockELement`.
	 */
	function createLandingClassName ($blockElement) {
		return LANDING_ELEMENT_CLASS + '_' + $blockElement.attr('id');
	}

	/**
	 * Creates landing element.
	 * @return {HTMLElement}
	 */
	function createLandingElement($blockElement) {
		var node;

		// This is a bit of an ugly hack, but since we allow tags
		// consisting of inline-block DIVs we have to wrap those
		// with other DIVs to ensure valid HTML markup and not
		// disturb for example the autoparagraph plugin.
		if ($blockElement.is('div')) {
			node = document.createElement('div');
			node.style.display = 'inline-block';
		} else {
			node = document.createElement('span');
		}

		node.className = createLandingClassName($blockElement);
		node.appendChild(document.createTextNode('\u00A0'));

		Ephemera.markWhiteSpaceWrapper(node);

		return node;
	}

	/**
	 * Check if a node is an Aloha-Block
	 * @param  {HTMLElement}  node the HTML-node to check
	 * @return {boolean}      true if the node is a block
	 */
	function isAlohaBlock(node) {
		return $(node).data('aloha-block-type') || false;
	}

	function isVisibleNode(node) {
		return (Html.isBlock(node) || Dom.isTextNode(node)) &&
			!Html.isUnrenderedNode(node);
	}

	/**
	 * Go forward in the dom while ignoring the starting node completely
	 *
	 * @param  {HTMLElement} node the element to start from
	 * @return {HTMLElement}      the next element
	 */
	function skipNodeForward(node) {
		if (node.lastChild) {
			return skipNodeForward(node.lastChild);
		} else {
			return Dom.forward(node);
		}
	}

	/**
	 * When creating the padding for inline blocks this function is used to determine how far to go
	 * forward or backward in the dom structure.
	 *
	 * @param  {HTMLElement} node the HTML-node to check
	 * @return {boolean}      true if the node is line-break ('br'), a block element or the editing host itself
	 */
	function untilNode(node) {
		return /^(br|td|th)$/.test(node.nodeName.toLowerCase()) || Html.isBlock(node) || DomLegacy.isEditingHost(node);
	}

	/**
	 * When creating the padding for inline blocks this function is used to determine how far to go
	 * forward in the dom structure. This function calls untilNode() internally.
	 *
	 * @param  {HTMLElement} node the HTML-node to check
	 * @return {boolean}      true if untilNode() returns true or the node is an Aloha-Block
	 */
	function untilNodeForward(node) {
		return untilNode(node) || node.nodeName.toLowerCase() === 'li' || (node.previousSibling && DomLegacy.isEditingHost(node.previousSibling)) || isAlohaBlock(node);
	}

	/**
	 * Pads the given block element with landing areas at the beginning and end
	 * of the block. This allows the editor to place the caret next to an inline
	 * block elements.
	 *
	 * @param {jQuery<DOMElement>} $block
	 */
	function pad($block) {
		if (DomLegacy.isBlockNode($block[0])) {
			return;
		}

		// first check, whether the padding already exists (previous or next sibling with
		// the a class named landingClassName)
		var landingClassName = createLandingClassName($block), previous, next;

		if ($block.prev('.' + landingClassName).length === 0) {
			previous = Dom.findBackward(
					Dom.backward($block[0]),
					isVisibleNode,
					untilNode
			);
			if (!previous) {
				$block.before(createLandingElement($block));
			}
		}
		if ($block.next('.' + landingClassName).length === 0) {
			next = Dom.findForward(
					skipNodeForward($block[0]),
					isVisibleNode,
					untilNodeForward
			);
			if (!next) {
				$block.after(createLandingElement($block));
			}
		}
	}

	/**
	 * Removes the landing nodes inserted by the pad() function.
	 *
	 * @param {jQuery<DOMElement>} $block
	 */
	function unpad($block) {
		var className = createLandingClassName($block);
		$('.' + className).each(function (index, elem) {
			if (Html.hasOnlyWhiteSpaceChildren(elem)) {
				elem.parentNode.removeChild(elem);
			} else {
				$(elem.childNodes).unwrap();
			}
		});
	}

	/**
	 * Tests whether the given element is contained in an editable for
	 * which the block dragdrop feature is enabled.
	 *
	 * @param {!jQuery} $element
	 *        The element that may or may not be contained in an editable.
	 * @return {Boolean}
	 *        True, unless the given $element is contained in an
	 *        editable for which the dragdrop feature has been disabled.
	 */
	function isDragdropEnabledForElement($element) {
		var $editable = $element.closest('.aloha-editable');
		if ($editable.length) {
			return !!$editable.data('block-dragdrop');
		}
		// no editable specified, let's make drag & drop enabled by default.
		return true;
	}

    /**
     * Get the parent editable of the block $block
     *
     * @param {jQuery Element} $block
     * @returns {jQuery Element}
     */
    function getEditableByBlock($block) {
        return $block.parents('.aloha-editable').filter(':first');
    }

	/**
	 * Get table inside the block or null if this block is not for a table
	 *
	 * @param $block
	 * @returns {jQuery Element} jQuery table or null if this block
	 * is not for a Table
	 */
	function getTableByBlock($block) {
		return isTable($block) ? $block.find('table').filter(':first') : null;
	}

	/**
	 * Check if a block element is a table.
	 *
	 * @param {jQuery Element} $blockElement
	 * @returns {Boolean} true if it is a table, false otherwise
	 */
	function isTable($blockElement) {
		return $blockElement.hasClass('aloha-table-wrapper');
	}

	return {
		pad: pad,
		unpad: unpad,
		isDragdropEnabledForElement: isDragdropEnabledForElement,
        isTable: isTable,
        getEditableByBlock: getEditableByBlock,
        getTableByBlock: getTableByBlock
	};
});

/*global window:true, define:true, document:true */
/* blockmanager.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('block/blockmanager',[
	'aloha',
	'jquery',
	'ui/scopes',
	'aloha/observable',
	'aloha/registry',
	'util/class',
	'util/strings',
	'util/maps',
	'block/block-utils'
], function (
	Aloha,
	$,
	Scopes,
	Observable,
	Registry,
	Class,
	Strings,
	Maps,
    BlockUtils
) {
	

	var jQuery = $;

	var GENTICS = window.GENTICS;

	/**
	 * Selects the entire `block` for cut/copy.
	 * @param {Block} block
	 */
	function selectWholeBlockForCopy(block) {
		block.$element.attr('data-aloha-block-copy-only-block', 'true');
		GENTICS.Utils.Dom.selectDomNode(block.$element[0]);
	}

	/**
	 * Checks if actual range is collapsed.
	 * @return {boolean}
	 */
	function isRangeExpanded() {
		if (!Aloha.getSelection().getRangeCount()) {
			return false;
		}
		return !Aloha.getSelection().getRangeAt(0).collapsed;
	}

	/**
	 * This is the block manager, which is the central entity for maintaining the lifecycle of blocks.
	 *
	 * @name block.blockmanager
	 * @class Block manager singleton
	 */
	var BlockManager = new (Class.extend(Observable,
	/** @lends block.blockmanager */
	{

		/**
		 * @name block.blockmanager#block-selection-change
		 * @event
		 * @param {Array} selectedBlocks Array of AbstractBlock objects, containing selected blocks. The first element in the array is the innermost block, and the other elements are the parent blocks.
		 */

		/**
		 * @name block.blockmanager#block-delete
		 * @event fired directly before a block is deleted
		 * @param {AbstractBlock} the block to be deleted
		 */

		/**
		 * Default settings for blocks
		 */
		defaults: {
			'aloha-block-type': 'DefaultBlock'
		},

		/**
		 * Registry of block types
		 * @type Registry
		 */
		blockTypes: null,

		/**
		 * Registry of blocks
		 * @type Registry
		 */
		blocks: null,

		/**
		 * Array of currently highlighted blocks
		 * @type Array
		 */
		_highlightedBlocks: null,

		/**
		 * Reference to the currently active block, if any
		 * @type AbstractBlock
		 */
		_activeBlock: null,

		/**
		 * Flag that stores the drag & drop state
		 * @type boolean 
		 */
		_dragdropEnabled: true,

		/**************************
		 * SECTION: Initialization
		 **************************/
		/**
		 * Constructor. called immediately.
		 *
		 * @constructor
		 */
		_constructor: function () {
			Scopes.createScope('Aloha.Block');
			this.blockTypes = new Registry();
			this.blocks = new Registry();
			this._highlightedBlocks = {};
		},

		/**
		 * Register initial event handlers. Called from block-plugin when plugin
		 * is loaded.
		 *
		 * @private
		 */
		registerEventHandlers: function () {
			var that = this;
			this._registerEventHandlersForDeactivatingAlohaBlock();
			this._registerEventHandlersForDeterminingCurrentlyActiveBlock();
			this._registerEventHandlersForBlockDeletion();
			this._registerEventHandlersForCutCopyPaste();
			this._registerEventHandlersForEditableActivated();

			// TODO: not sure if we still need the code below. it is somehow related to caret handling
			Aloha.bind('aloha-selection-changed', function (evt, selection, originalEvent) {
				// the following line is needed to de-select blocks when navigating over them using the mouse cursors.
				// We only want to execute it though, if we are not inside a block, as it would otherwise
				// directly deselect the block we just selected. This is just a hotfix and not the final solution yet.
				if (selection && jQuery(selection.getCommonAncestorContainer()).parents('.aloha-block').length > 0) {
					return;
				}
				that._deactivateHighlightedBlocks();
			});
		},

		/**
		 * Register the event handlers which deactivate Aloha blocks when the
		 * user clicks outside a block.
		 */
		_registerEventHandlersForDeactivatingAlohaBlock: function () {
			var that = this;
			jQuery(document).bind('click', function (event) {
				if (Maps.isEmpty(that._highlightedBlocks)) {
					return;
				}
				if (jQuery(event.target)
				        .closest('.aloha-ui,.aloha-block-do-not-deactivate,.aloha-block')
				        .length > 0) {
					// A ui element has been clicked; ignore this event.
					return;
				}
				that._deactivateHighlightedBlocks();
			});
		},

		/**
		 * Register the event handler which listens to block-selection-change, and
		 * sets _activeBlock accordingly.
		 */
		_registerEventHandlersForDeterminingCurrentlyActiveBlock: function () {
			var that = this;
			this.bind('block-selection-change', function (highlightedBlocks) {
				if (highlightedBlocks.length > 0) {
					that._activeBlock = highlightedBlocks[0];
				} else {
					that._activeBlock = null;
				}
			});
		},

		/**
		 * Implementation of block deletions, both when the block is the only selected element,
		 * and when the block is part of a bigger selection which should be deleted.
		 */
		_registerEventHandlersForBlockDeletion: function () {
			var that = this;

			/**
			 * A block selection is a 'virtual selection' (ie: not a real
			 * browser selection on the document). We can be certain that we
			 * have the entire block selected when there is an activeBlock but
			 * no document selection.
			 *
			 * However, IE tends to set the selection to the document body when
			 * we programmatically deselect everything. We need to detect this
			 * situation and treat it as though there is no browser selection.
			 * This check is designed to double check against this false
			 * positive.
			 *
			 * @private
			 * @param  {!Selection} selection
			 * @return {boolean}
			 */
			function checkIsOnlyBlockSelected(selection) {
				if (selection.getRangeCount() === 0) {
					return true;
				}
				if (!$.browser.msie) {
					return false;
				}
				var range = selection.getRangeAt(0);
				var sc = range.startContainer;
				var ec = range.endContainer;
				if (sc !== ec) {
					return false;
				}
				// IE has placed the selection on the body element
				if ($('body').is(sc)) {
					return true;
				}
				// IE has placed the selection around the selected block?
				if (range.startOffset + 1 === range.endOffset && 1 === node.nodeType) {
					var node = node.childNodes[range.startOffset];
					return $(node).is('.aloha-block');
				}
				return false;
			}

			// This case executes in:
			// - Chrome
			// - Firefox
			// - IE9
			// - IE7+8 for inline blocks and for block-level blocks which are part of a bigger selection
			// it does NOT execute in the following cases:
			// - IE7+8 for block-level blocks which are NOT part of a bigger selection. This case is handled separately below.
			Aloha.bind('aloha-command-will-execute', function (e, data) {
				// workaround for selection problem in tables: we never delete table blocks this way
				if (that._activeBlock && that._activeBlock.$element.hasClass('aloha-table-wrapper')) {
					return true;
				}

				var cmd = data.commandId;
				var selection = Aloha.getSelection();
				var isOnlyBlockSelected = checkIsOnlyBlockSelected(selection);
				var isDeleteOperation = 'delete' === cmd || 'forwarddelete' === cmd;

				if (that._activeBlock && isDeleteOperation && isOnlyBlockSelected) {
					// In this case, the default command shall not be executed.
					data.preventDefault = true;
					that._activeBlock.destroy();
				} else if (
					!that._activeBlock &&
					isDeleteOperation &&
					selection.getRangeCount() === 1 &&
					selection.getRangeAt(0).collapsed === false
				) {
					// Deletion when a block is inside a bigger selection
					// currently In this case, we check if we find an
					// aloha-block. If yes, we delete it right away as the
					// browser does not delete it correctly by default
					(function traverseSelectionTree(selectionTree) {
						var el;
						for (var i = 0, l = selectionTree.length; i < l; i++) {
							el = selectionTree[i];
							if (el.domobj.nodeType === 1) { // DOM node
								var $el = jQuery(el.domobj);
								if (el.selection === 'full' && $el.is('.aloha-block')) {
									$el.remove();
								} else {
									traverseSelectionTree(el.children);
								}
							}
						}
					}(Aloha.Selection.getSelectionTree()));
				}
			});

			// - IE7/8 Workaround
			// - deletion of blocks inside block collection
			jQuery(window.document).keydown(function (e) {
				// Ignore events originating from the UI
				if ($(e.target).closest('.aloha-ui').length > 0) {
					return true;
				}

				// If a block is active AND DEL or BACKSPACE key pressed, AND we are not inside a nested editable (FIX for IE7/8)
				if (that._activeBlock && (e.which === 46 || e.which === 8) && that._activeBlock._isInsideNestedEditable === false) {
					// ...and active block is INSIDE editable

					// BROWSER QUIRK WORKAROUND
					// - IE7+IE8 for block-level blocks which are NOT part of a bigger selection.
					if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) <= 8 && that._activeBlock.$element.parents('.aloha-editable,.aloha-block').first().hasClass('aloha-editable')) {
						that._activeBlock.destroy();
						e.preventDefault();
						return false;
					} else if (that._activeBlock.shouldDestroy()) {
						// .. in this case, the block should be destroyed because it
						// is part of a block collection.

						that._activeBlock.destroy();
						e.preventDefault();
						return false;
					}
				}
			});
		},

		/**
		 * Implementation of cut/copy; selecting the currently active block.
		 *
		 * When pasting, the blockcontenthandler is triggered. This takes care of the pasting process.
		 */
		_registerEventHandlersForCutCopyPaste: function () {
			var that = this,
				currentlyCopying = false,
				currentlyCutting = false,
				selectionBeforeCopying = null;

			jQuery(window.document).keydown(function (e) {
				if (Aloha.activeEditable && that._activeBlock) {
					// when both an editable and a block are active, they must
					// be nested into each other.
					// if the editable is nested inside the block, we will not expand the
					// selection onto the whole block, because the user wants to copy/cut content
					// in the editable
					if (jQuery.contains(that._activeBlock.$element.get(0), Aloha.activeEditable.obj.get(0))) {
						return;
					}
				}

				// If the block is a table do not select whole table,
				// table has its own selection methods.
				if (that._activeBlock
					&& (e.ctrlKey || e.metaKey)
					&& !isRangeExpanded()
					&& !BlockUtils.isTable(that._activeBlock.$element)) {

					// IF: Ctrl/Command C pressed -- COPY
					if (e.which === 67) {
						currentlyCopying = true;
						selectWholeBlockForCopy(that._activeBlock);
					}
					// IF: Ctrl/Command X pressed -- CUT
					else if (e.which === 88) {
						currentlyCutting = true;
						selectWholeBlockForCopy(that._activeBlock);
					}
				}
			});

			jQuery(window.document).keyup(function (e) {
				// IF: Release of ctrl / command C
				if (!currentlyCutting && currentlyCopying && (e.which === 67 || e.which === 18 || e.which === 91)) {
					currentlyCopying = false;
					that._activeBlock.$element.removeAttr('data-aloha-block-copy-only-block');
					if (selectionBeforeCopying) {
						//selectionBeforeCopying.select();
						selectionBeforeCopying = null;
					}
				}
				// IF: Release of ctrl / command X
				if (currentlyCutting  && (e.which === 67 || e.which === 18 || e.which === 88)) {
					currentlyCutting = false;
				}
			});
		},

		/**
		 * When editables are activated (e.g. by moving the focus with Tab or Shift-Tab or programmatically),
		 * we need to activate the enclosing blocks. 
		 */
		_registerEventHandlersForEditableActivated: function () {
			var that = this;
			Aloha.bind('aloha-editable-activated', function (event, arg) {
				if (arg.editable) {
					var block = that.getBlock(arg.editable.obj.closest('.aloha-block'));
					if (block) {
						block.activate();
					}
				}
			});
		},

		/**
		 * Initialize Block Level Drag/Drop for all editables. We need to do this
		 * inside the Block Manager, as we want all editables to become possible
		 * drop targets for block-level aloha blocks.
		 */
		initializeBlockLevelDragDrop: function () {
			var blockmanager = this;
			jQuery.each(Aloha.editables, function (i, editable) {
				editable.obj.data('block-dragdrop', blockmanager._dragdropEnabled);
			});
			Aloha.bind('aloha-editable-created', function (e, editable) {
				editable.obj.data('block-dragdrop', blockmanager._dragdropEnabled);
			});
		},

		/**
		 * Turn the dragdrop feature globally on or off.
		 *
		 * Will only affect editables created after this call is made.
		 *
		 * @param {boolean} state
		 */
		setDragDropState: function (state) {
			this._dragdropEnabled = state;
		},

		/**
		 * Test whether the dragdrop feature is globally enabled.
		 *
		 * @return {boolean}
		 */
		getDragDropState: function () {
			return this._dragdropEnabled;
		},

		/**************************
		 * SECTION: Blockify / Block Access
		 **************************/

		/**
		 * Register the given block type
		 *
		 * @param {String} Identifier
		 * @param {Class} A class that extends block.block.AbstractBlock
		 * @api
		 */
		registerBlockType: function (identifier, blockType) {
			Scopes.createScope('Aloha.Block.' + identifier, 'Aloha.Block');
			this.blockTypes.register(identifier, blockType);
		},

		/**
		 * Blockify a given element with the instance defaults
		 * Directly called when one does jQuery.alohaBlock(instanceDefaults)
		 *
		 * @private
		 */
		_blockify: function (element, instanceDefaults) {
			var that = this,
				$element = jQuery(element),
				BlockPlugin = Aloha.require('block/block-plugin'),
				tagName = $element[0].tagName.toLowerCase(),
				attributes,
				block;

			if (jQuery.inArray(tagName, BlockPlugin.settings.rootTags) === -1) {
				Aloha.Log.error('block/blockmanager', 'Blocks can only be created from [' +
					BlockPlugin.settings.rootTags.join(', ') +
					'] element. You passed ' + tagName + '.');
				return;
			}

			// TODO: check if object is already Block-ified

			attributes = this.getConfig($element, instanceDefaults);

			if (!this.blockTypes.has(attributes['aloha-block-type'])) {
				Aloha.Log.error('block/blockmanager', 'Block Type ' + attributes['aloha-block-type'] + ' not found!');
				return;
			}

			block = new (this.blockTypes.get(attributes['aloha-block-type']))($element, attributes);
			block.$element.addClass('aloha-block-' + attributes['aloha-block-type']);
//			jQuery.each(attributes, function (k, v) {
//				// We use the private API here, as we need to be able to set internal properties as well, and we do not want to trigger renering.
//				block._setAttribute(k, v);
//			});

			// Register block
			this.blocks.register(block.getId(), block);
		},

		/**
		 * Unblockify the given element
		 * 
		 * @private
		 */
		_unblockify: function (element) {
			var block = this.getBlock(element);
			if (block) {
				block.unblock();
			}
		},

		/**
		 * Merges the config from different places, and return the merged config.
		 *
		 * @param {Object} jQuery Object
		 * @param {Object} default configuration
		 *
		 * @private
		 */
		getConfig: function (blockElement, instanceDefaults) {
			var
				data          = {},
				dataCamelCase = null,
				$clone        = null;

			if (blockElement.length > 0) {
				// Clone the element before getting the data to fix an IE7 crash.
				// We use the native cloneNode function, because jQuerys clone()
				// executes scripts also, what we don't want.
				// But since that one doesn't copy the the abritary jQuery data, we
				// manually merge both datas together into a new object.
				$clone = $(blockElement[0].cloneNode(false));
				// Merge the "data-" attributes from the clone and take the
				// arbitrary data from the original jQuery object
				dataCamelCase = $.extend({}, $.data(blockElement[0]), $clone.data());
				$clone.removeData();

				// jQuery.data() returns data attributes with names like
				// data-some-attr as dataSomeAttr which has to be reversed
				// so that they can be merged with this.defaults and
				// instanceDefaults which are expected to be in
				// data-some-attr form.
				for (var key in dataCamelCase) {
					if (dataCamelCase.hasOwnProperty(key)) {
						data[Strings.camelCaseToDashes(key)] = dataCamelCase[key];
					}
				}
			}

			return jQuery.extend(
				{},
				this.defaults,
				instanceDefaults,
				data
			);
		},

		/**
		 * Get a Block instance by id or DOM node. The DOM node can be either
		 * the DOM node of the wrapping element ($_element), the jQuery object of it,
		 * or the ID string.
		 *
		 * @param {String|DOMNode} idOrDomNode
		 * @return {block.block.AbstractBlock} Block instance
		 * @api
		 */
		getBlock: function (idOrDomNode) {
			var id, domNode;
			if (typeof idOrDomNode === 'object') {
				domNode = jQuery(idOrDomNode);
				if (domNode.hasClass('aloha-block-inner')) {
					// We are at the inner block wrapper, so we have to go up one level,
					// to find the block itself
					domNode = domNode.parent();
				}
				id = domNode.attr('id');
			} else {
				id = idOrDomNode;
			}

			return this.blocks.get(id);
		},

		/**
		 * Unregister (e.g. remove) the given block. Do not call directly,
		 * instead use .destroy() on the block.
		 *
		 * @param {Object|String} blockOrBlockId Block or block id
		 */
		_unregisterBlock: function (blockOrBlockId) {
			var id;
			if (typeof blockOrBlockId === 'object') {
				id = blockOrBlockId.getId();
			} else {
				id = blockOrBlockId;
			}
			this.blocks.unregister(id);
		},


		/**************************
		 * Internal helpers
		 **************************/

		/**
		 * Deactivate all highlighted blocks
		 *
		 * @private
		 */
		_deactivateHighlightedBlocks: function () {
			jQuery.each(jQuery.extend({}, this._highlightedBlocks), function (id) {
				var block = BlockManager.getBlock(id);
				if (block) {
					block.deactivate();
				}
			});
		},

		/**
		 * Get all highlighted blocks indexed by block id
		 *
		 * @return {Object}
		 */
		_getHighlightedBlocks: function () {
			var _highlightedBlocks = {};
			jQuery.each(this.blocks.getEntries(), function (blockId, block) {
				if (block.isActive()) {
					_highlightedBlocks[blockId] = block;
				}
			});
			return _highlightedBlocks;
		},

		_setHighlighted: function (block) {
			this._highlightedBlocks[block.id] = true;
		},

		_setUnhighlighted: function (block) {
			delete this._highlightedBlocks[block.id];
		}
	}))();

	Aloha.Block = Aloha.Block || {};
	Aloha.Block.BlockManager = BlockManager;

	return BlockManager;
});

/* editormanager.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('block/editormanager',
['aloha/registry'], function (Registry) {
	

	/**
	 * The Editor Manager maps data types to editor classes.
	 *
	 * @name block.editormanager
	 * @class Editor manager singleton
	 */
	return new (Registry.extend(
	/** @lends block.editormanager */
	{
		/**
		 * Create an editor from the given definition. Acts as a factory method
		 * for editors.
		 *
		 * @param {Object} definition
		 */
		createEditor: function (definition) {
			if (!this.has(definition.type)) {
				throw 'Editor for type "' + definition.type + '" not found.';
			}
			var Editor = this.get(definition.type);
			return new Editor(definition);
		}
	}))();
});
/* sidebarattributeeditor.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('block/sidebarattributeeditor',[ 'jquery', 'block/blockmanager', 'aloha/sidebar', 'block/editormanager', 'util/class'],
	function (jQuery, BlockManager, Sidebar, EditorManager, Class) {
	

	/**
	 * The Sidebar Attribute Editor is the connector which listens on Aloha Blocks and
	 * updates the sidebar accordingly. It builds the editors for the sidebar, initializes
	 * them with initial values and updates the block attributes as soon as a value changes.
	 *
	 * @name block.sidebarattributeeditor
	 * @class Sidebar attribute editor singleton
	 */
		return new (Class.extend(
		/** @lends block.sidebarattributeeditor */
		{

			_sidebar: null,

		/**
		 * Panels, which were added to the sidebar for blocks
		 */
		_blockPanels: [],

		/**
		 * Initialize the sidebar attribute editor and bind events
		 */
		init: function() {
			this._sidebar = Sidebar.right.show();

			BlockManager.bind('block-selection-change', this._onBlockSelectionChange, this);
		},

		/**
		 * @param {Array} selectedBlocks
		 */
		_onBlockSelectionChange: function(selectedBlocks) {
			var that = this, panel;
			if (!this._sidebar) {
				return;
			}

			// remove all panels from the sidebar, which were added for blocks
			for (panel in that._blockPanels) {
				if (that._blockPanels.hasOwnProperty(panel)) {
					panel = that._blockPanels[panel];
					jQuery(panel.element).remove();
					delete this._sidebar.panels[panel.id];
				}
			}
			this._blockPanels = [];

			jQuery.each(selectedBlocks, function() {
				var schema = this.getSchema(),
					block = this,
					editors = [];

				if (!schema) {
					// If no schema returned, we do not want to add panels.
					return;
				}

				that._blockPanels.push(that._sidebar.addPanel({
					title: block.getTitle(),
					expanded: true,
					onInit: function() {
						var $form = jQuery('<form />');
						$form.submit(function() {
							// Disable form submission
							return false;
						});
						jQuery.each(schema, function(attributeName, definition) {
							var editor = EditorManager.createEditor(definition);

							// Editor -> Block binding
							editor.bind('change', function(value) {
								block.attr(attributeName, value);
								});

								// Block -> Editor binding
								block.bind('change', function () {
									editor.setValue(block.attr(attributeName));
							});

								$form.append(editor.render());

								// Set initial value Block -> Editor
								editor.setValue(block.attr(attributeName));
	
								editors.push(editor);
							});
							this.setContent($form);
						},

						deactivate: function () {
							// On deactivating the panel, we need to tell each editor to deactivate itself,
							// so it can throw another change event if the value has been modified.
							jQuery.each(editors, function (index, editor) {
								editor._deactivate();
							});

						// This code is from the superclass
						this.isActive = false;
						// TODO check if this is needed in current block implementation
						// this.content.parent('li').hide();
						this.effectiveElement = null;
					}
				}));
			});
		}
	}))();
});

/* block.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Module which contains the base class for Blocks, and a Default/Debug block.
 *
 * @name block.block
 * @namespace block/block
 */
define('block/block',[
	'aloha',
	'jquery',
	'block/blockmanager',
	'aloha/observable',
	'ui/scopes',
	'util/class',
	'PubSub',
	'block/block-utils',
	'util/dom',
	'util/functions',
	'aloha/engine'
], function (
	Aloha,
	jQuery,
	BlockManager,
	Observable,
	Scopes,
	Class,
	PubSub,
	BlockUtils,
	Dom,
	Fn,
	Engine
) {
	

	var GENTICS = window.GENTICS;

	/**
	 * An aloha block has the following special properties, being readable through the
	 * "attr" function:
	 * - aloha-block-type -- TYPE of the AlohaBlock as registered by the BlockManager
	 *
	 * @name block.block.AbstractBlock
	 * @class An abstract block that must be used as a base class for custom blocks
	 */
	var AbstractBlock = Class.extend(Observable,
	/** @lends block.block.AbstractBlock */
	{

		/**
		 * Event which is triggered if the block attributes change.
		 *
		 * @name block.block.AbstractBlock#change
		 * @event
		 */

		/**
		 * Title of the block, used to display the name in the sidebar editor.
		 *
		 * @type String
		 * @api
		 */
		title: null,

		/**
		 * Id of the underlying $element, used to identify the block.
		 * @type String
		 */
		id: null,

		/**
		 * The wrapping element of the block.
		 * @type jQuery
		 * @api
		 */
		$element: null,

		/**
		 * if TRUE, the rendering is currently taking place. Used to prevent recursion
		 * errors.
		 * @type Boolean
		 */
		_currentlyRendering: false,

		/**
		 * set to TRUE once the block is fully initialized.
		 *
		 * @type Boolean
		 */
		_initialized: false,

		/**
		 * Set to TRUE if the last click activated a *nested editable*.
		 * If FALSE; the block itself is selected.
		 * This is needed when a block is deleted in IE7/8.
		 */
		_isInsideNestedEditable: false,

		/**************************
		 * SECTION: Initialization and Lifecycle
		 **************************/

		/**
		 * Initialize the basic block. Do not call directly; instead use jQuery(...).alohaBlock() to
		 * create new blocks.
		 *
		 * This function shall only be called through the BlockManager. See BlockManager::_blockify().
		 *
		 * When a block is fully initialized, an "aloha.block.initialized"
		 * message is published.
		 *
		 * @param {jQuery} $element Element that declares the block
		 * @param {Object} attributes that shall be set to the block
		 * @constructor
		 */
		_constructor: function ($element, attributes) {
			var that = this;

			this.$element = $element;

			if ($element.attr('id')) {
				this.id = $element.attr('id');
			} else {
				this.id = GENTICS.Utils.guid();
				$element.attr('id', this.id);
			}

			$element.contentEditable(false);

			$element.addClass('aloha-block');

			if (this.isDraggable()) {
				// Remove default drag/drop behavior of the browser
				$element.find('img').attr('draggable', 'false');

				try {
					$element.find('a').attr('draggable', 'false');
				} catch (e) {
					// If we get in here, it is most likely an issue with IE 10 in documentmode 7
					// and IE10 compatibility mode. It maybe happens in older versions too.
					// Error: Member not found
					// https://connect.microsoft.com/IE/feedback/details/774078
					// http://bugs.jquery.com/ticket/12577
					// Our fallback solution:
					$element.find('a').each(function () {
						this.setAttribute('draggable', 'false');
					});
				}
			}

			// set the attributes
			jQuery.each(attributes, function (k, v) {
				that._setAttribute(k, v);
			});

			// While the event handler is defined here, it is connected to the DOM element inside "_connectThisBlockToDomElement"
			this._onElementClickHandler = function (event) {
				// We only activate ourselves if we are the innermost aloha-block.
				// If we are not the innermost aloha-block, we get highlighted (but not activated) automatically
				// by the innermost block.
				if (jQuery(event.target).closest('.aloha-block').get(0) === that.$element.get(0)) {
					that._fixScrollPositionBugsInIE();
					that.activate(event.target, event);
					// remove the selection, if the clicked element itself is not editable, but don't do this, if the clicked
					// element is the active table cell, because in that case, the selection is in the editable wrapper of the
					// cell (which is ok). We also leave the selection alone if the target was a text input element.
					var $target = jQuery(event.target);

					if (!$target.is('textarea, input') && !$target.contentEditable() && !$target.hasClass("aloha-table-cell_active")) {
						Aloha.getSelection().removeAllRanges();
					}
				}
			};

			// Register event handlers on the block
			this._connectThisBlockToDomElement($element, function () {
				PubSub.pub('aloha.block.initialized', {block: that});
			});

			// This is executed when a block is selected through caret handling
			// TODO!
			//Aloha.bind('aloha-block-selected', function(event,obj) {
			//	if (that.$element.get(0) === obj) {
			//		that.activate();
			//	}
			//});

			// Only for inline element.
			// It is not possible to insert text after or before a Block span
			// when there are not elements after or before the Block.
			if (!Dom.isBlockNode($element[0])) {
				if ($element.closest('.aloha-editable-active').length > 0) {
					BlockUtils.pad(that.$element);
				}

				Aloha.bind('aloha-smart-content-changed', function (event, data) {
					if (data.editable.isActive) {
						var $block = data.editable.obj.find('#' + that.id);

						if ($block.length > 0) {
							BlockUtils.pad(that.$element);
						}
					}
				});

				Aloha.bind('aloha-editable-activated', function ($event, data) {
					if (data.editable) {
						var $block = data.editable.obj.find('#' + that.id);
						if ($block.length !== 0) {
							BlockUtils.pad(that.$element);
						}
					}
				});

				Aloha.bind('aloha-editable-deactivated', function ($event, data) {
					if (data.editable) {
						var $block = data.editable.obj.find('#' + that.id);
						if ($block.length !== 0) {
							BlockUtils.unpad(that.$element);
						}
					}
				});
			}

			this._initialized = true;
		},

		/**
		 * Is set inside the constructor to the event handler function
		 * which should be executed when the element is clicked.
		 *
		 * NOTE: Purely internal, "this" is not available inside this method!
		 */
		_onElementClickHandler: null,

		/**
		 * We need to tell Aloha that we handle the event already;
		 * else a selection of a nested editable will *not* select
		 * the block.
		 *
		 * This callback is bound to the mousedown, focus and dblclick events.
		 *
		 * NOTE: Purely internal, "this" is not available inside this method!
		 */
		_preventSelectionChangedEventHandler: function ($event) {
			var $editable = jQuery($event.target).closest('.aloha-editable');

			if ('dblclick' !== $event.type && !this.contains($editable[0])) {
				Aloha.Selection.preventSelectionChanged();
			}
		},

		/**
		 * This method connects this block object to the passed DOM element.
		 * In detail, this method does the following:
		 *
		 * - if this.$element is already set, remove all block event handlers
		 * - sets this.$element = jQuery(newElement)
		 * - initialize event listeners on this.$element
		 * - call init()
		 *
		 * The method is called in two contexts: First, when a block is constructed
		 * to initialize the event listeners etc. Second, it is ALSO called when
		 * a block inside a nested block with editable in between is detected
		 * as inconsistent.
		 */
		_connectThisBlockToDomElement: function (newElement, callback) {
			var that = this;
			var $newElement = jQuery(newElement);
			this._disconnectFromDomElement();
			this.$element = $newElement;

			this.$element.bind('click', this._onElementClickHandler);
			this.$element.bind('mousedown', this._preventSelectionChangedEventHandler);
			this.$element.bind('focus', this._preventSelectionChangedEventHandler);
			this.$element.bind('dblclick', this._preventSelectionChangedEventHandler);

			this.init(this.$element, function () {
				// WORKAROUND against loading order dependencies. If we have
				// nested Blocks inside each other (with no editables in between)
				// it could be that the *inner* block is initialized *before* the outer one.
				//
				// However, the inner block needs to know whether it shall render drag handles or not,
				// and this depends on whether it is inside an editable or a block.
				//
				// In order to fix this case, we delay the the drag-handle-rendering (and all the other
				// post-processing) to the next JavaScript Run Loop using a small timeout.
				window.setTimeout(function () {
					that._postProcessElementIfNeeded();
					if (callback) {
						callback();
					}
				}, 5);
			});
		},

		/**
		 * Disconnect the block from the DOM element
		 */
		_disconnectFromDomElement: function () {
			if (this.$element) {
				this.$element.unbind('click', this._onElementClickHandler);
				this.$element.unbind('mousedown', this._preventSelectionChangedEventHandler);
				this.$element.unbind('focus', this._preventSelectionChangedEventHandler);
				this.$element.unbind('dblclick', this._preventSelectionChangedEventHandler);
			}
		},

		/**
		 * IE HACK: Our beloved Internet Explorer sometimes scrolls to the top
		 * of the page when activating an aloha block, and on numerous other occasions
		 * like when an <span> block is moved via drag/drop.
		 *
		 * We can detect this and scroll right back; although this will flicker
		 * a little (but still a lot better than before)
		 */
		_fixScrollPositionBugsInIE: function () {
			var scrollPositionBefore = jQuery(window).scrollTop();
			window.setTimeout(function () {
				if (jQuery(window).scrollTop() !== scrollPositionBefore) {
					jQuery(window).scrollTop(scrollPositionBefore);
				}
			}, 10);
		},
		/**
		 * Template method to initialize the block. Can be used to set attributes
		 * on the block, depending on the block contents. You will most probably
		 * use $element and this.attr() inside this function.
		 *
		 * !!! This method can be called *multiple times*, as it is called each time
		 * when $element has been disconnected from the DOM (which can happen because of various reasons)
		 * and the block needs to re-initialize. So make sure this method can be called *MULTIPLE TIMES*
		 * and always returns predictable results. This method must be idempotent, same as update().
		 *
		 * Furthermore, always when this method is finished, you need to call postProcessFn() afterwards.
		 * This function adds drag handles and other controls if necessary.
		 *
		 * @param {jQuery} $element a shortcut to the block's DOM element (this.$element) for easy processing
		 * @param {Function} postProcessFn this function MUST be called at all times the $element has been updated; as it adds drag/drop/delete/... handles if necessary
		 * @api
		 */
		init: function ($element, postProcessFn) {
			postProcessFn();
		},

		/**
		 * Callback which is executed when somebody triggers destroy().
		 *
		 * This only allows destruction if the block is *inside* an aloha-editable and *not* inside an aloha-block.
		 *
		 * @return {Boolean} true of destruction should happen, false otherwise
		 */
		shouldDestroy: function () {
			var $closest = this.$element.parent().closest('.aloha-block,.aloha-editable,.aloha-table-cell-editable,.aloha-block-collection');
			if ($closest.hasClass('aloha-block-collection') && this.$element[0].tagName.toLowerCase() === 'div') {
				return true;
			} else {
				return $closest.hasClass('aloha-editable') || $closest.hasClass('aloha-table-cell-editable');
			}
		},

		/**
		 * Destroy this block instance completely. Removes the element from the DOM,
		 * unregisters it, and triggers a block-delete event on the BlockManager.
		 *
		 * @param {Boolean} force TRUE if you want to force deletion, despite shouldDestroy() returning false.
		 * @api
		 */
		destroy: function (force) {
			if (!this.shouldDestroy() && force !== true) {
				return;
			}

			var that = this;
			var newRange = new GENTICS.Utils.RangeObject();

			newRange.startContainer = newRange.endContainer = this.$element.parent()[0];
			newRange.startOffset = newRange.endOffset = GENTICS.Utils.Dom.getIndexInParent(this.$element[0]);
			
			BlockUtils.unpad(this.$element);

			BlockManager.trigger('block-delete', this);
			this.free();

			var isInlineElement = this.$element[0].tagName.toLowerCase() === 'span';

			this.$element.fadeOut('fast', function () {
				that.$element.remove();
				BlockManager.trigger('block-selection-change', []);
				window.setTimeout(function () {
					if (isInlineElement) {
						newRange.select();
					}
				}, 5);
			});
		},

		/**
		 * Remove this block, but leave the original DOM element
		 */
		unblock: function () {
			// TODO set old value of contentEditable
			// TODO set old values for draggable attributes

			BlockUtils.unpad(this.$element);
			// deactivate
			this.deactivate();
			// remove handlers
			this._disconnectFromDomElement();
			// remove block class
			this.$element.removeClass('aloha-block');
			// remove block handles
			this.$element.children('.aloha-block-handle').remove();
			// unregister the block
			this.free();
		},

		/**
		 * Free internal state associated with this block.
		 *
		 * Should be called when a block is not used any more to prevent
		 * memory leaks.
		 *
		 * Any invokations of instance methods after this method has
		 * been called will result in undefined behaviour.
		 *
		 * @api
		 */
		free: function () {
			BlockManager._unregisterBlock(this);
			this.unbindAll();
		},

		/**************************
		 * SECTION: Getters and Helpers
		 **************************/

		/**
		 * Get the id of the block
		 * @returns {String}
		 */
		getId: function () {
			return this.id;
		},

		/**
		 * Get a schema of attributes which shall be rendered / edited
		 * in the sidebar.
		 *
		 * @api
		 * @returns {Object}
		 */
		getSchema: function () {
			return null;
		},

		/**
		 * Template Method which should return the block title. Needed for editing sidebar.
		 * By default, the block title is returned.
		 *
		 * @api
		 */
		getTitle: function () {
			return this.title;
		},

		/**
		 * Returns true if the block is draggable because it is inside an aloha-editable, false otherwise.
		 *
		 * You cannot depend on this method's result during the *init* phase of the Aloha Block, as the
		 * outer block might not be initialized at that point yet. Thus, do not call this method inside init().
		 *
		 * @return Boolean
		 */
		isDraggable: function () {
			if (this.$element[0].nodeName === 'DIV' &&
				this.$element.parents('.aloha-editable,.aloha-block:not(.aloha-table-wrapper),.aloha-block-collection').first().hasClass('aloha-block-collection')) {
				// Here, we are inside an aloha-block-collection, and thus also need to be draggable.
				return true;
			}
			return this.$element.parents('.aloha-block:not(.aloha-table-wrapper),.aloha-editable').first().hasClass('aloha-editable');
		},

		/**************************
		 * SECTION: Activation / Deactivation
		 **************************/

		/**
		 * activates the block
		 * will select the block's contents, highlight it, update the floating menu and update the sidebar (if needed).
		 *
		 * When calling programmatically, do not set eventTarget or event arguments.
		 * @api
		 */
		activate: function (eventTarget, event) {
			var highlightedBlocks = [];

			// Deactivate currently highlighted blocks
			jQuery.each(BlockManager._getHighlightedBlocks(), function () {
				this.deactivate();
			});

			// Activate current block
			if (this.$element.attr('data-block-skip-scope') !== 'true') {
				Scopes.setScope('Aloha.Block.' + this.attr('aloha-block-type'));
			}
			this.$element.addClass('aloha-block-active');
			this._highlight();
			highlightedBlocks.push(this);

			// Highlight parent blocks
			this.$element.parents('.aloha-block').each(function () {
				var block = BlockManager.getBlock(this);
				if (block) {
					block._highlight();
					highlightedBlocks.push(block);
				}
			});

			// Browsers do not remove the cursor, so we enforce it when an editable is clicked.
			// However, when the user clicked inside a nested editable, we will not remove the cursor (as the user wants to start typing then)
			// small HACK: we also do not deactivate if we are inside an aloha-table-cell-editable.
			if (jQuery(eventTarget).closest('.aloha-editable,.aloha-block,.aloha-table-cell-editable,.aloha-table-cell_active').first().hasClass('aloha-block')) {
				var range = new Aloha.Selection.SelectionRange(true);
				// we check whether the start of the selection was inside an editable
				// this handles situations, where the user started selecting inside an editable (in a block)
				// and stopped selecting outside the editable but still inside the block
				if (jQuery(range.startContainer).closest('.aloha-editable,.aloha-block,.aloha-table-cell-editable,.aloha-table-cell_active').first().hasClass('aloha-editable') && event) {
					this._isInsideNestedEditable = true;
					Aloha.Selection.updateSelection(event);
				} else {
					this._isInsideNestedEditable = false;
				}
			} else {
				this._isInsideNestedEditable = true;
				if (event) {
					// We now update the selection, as you clicked *inside* an editable inside the block
					Aloha.Selection.updateSelection(event);
				}
			}

			// activate the editable host of the block
			if (!this._isInsideNestedEditable) {
				var editable = Aloha.getEditableHost(this.$element);
				if (editable) {
					editable.activate();
				}
			}

			// Trigger block activate & selection change events.
			BlockManager.trigger('block-activate', highlightedBlocks);
			BlockManager.trigger('block-selection-change', highlightedBlocks);
		},

		/**
		 * Deactive the block
		 */
		deactivate: function () {
			var that = this;
			var deactivatedBlocks = [this];
			this._unhighlight();
			this.$element.parents('.aloha-block').each(function () {
				deactivatedBlocks.push(this);
				that._unhighlight();
			});

			this.$element.removeClass('aloha-block-active');

			// Trigger block deactivate & selection change events.
			BlockManager.trigger('block-deactivate', deactivatedBlocks);
			BlockManager.trigger('block-selection-change', []);
		},

		/**
		 * @returns {Boolean} True if this block is active
		 */
		isActive: function () {
			return this.$element.hasClass('aloha-block-active');
		},

		/**
		 * Internal helper which sets a block as highlighted, because the block itself
		 * or a child block has been activated.
		 */
		_highlight: function () {
			this.$element.addClass('aloha-block-highlighted');
			BlockManager._setHighlighted(this);
		},

		/**
		 * Internal helper which sets a block as un-highlighted.
		 */
		_unhighlight: function () {
			this.$element.removeClass('aloha-block-highlighted');
			BlockManager._setUnhighlighted(this);
		},

		/**************************
		 * SECTION: Block Rendering
		 **************************/

		/**
		 * Internal _update method, which needs to be called internally if a property
		 * changed. This is just a wrapper around update().
		 */
		_update: function () {
			var that = this;
			if (this._currentlyRendering) {
				return;
			}

			if (!this._initialized) {
				return;
			}

			this._currentlyRendering = true;

			this.update(this.$element, function () {
				that._postProcessElementIfNeeded();
			});

			this._currentlyRendering = false;
		},

		/**
		 * Template method to render contents of the block, must be implemented by specific block type.
		 * $element can be augumented by additional DOM elements like drag/drop handles. If you do
		 * any jQuery selection, you need to ignore all results which have a "aloha-block-handle" class
		 * set.
		 *
		 * Furthermore, always when you update $element, you need to call postProcessFn() afterwards.
		 * This function adds drag handles and other controls if necessary.
		 *
		 * This method should *only* be called from the internal _update method.
		 *
		 * @param {jQuery} $element a shortcut to the block's DOM element (this.$element) for easy processing
		 * @param {Function} postProcessFn this function MUST be called at all times the $element has been updated; as it adds drag/drop/delete/... handles if necessary
		 *
		 * @api
		 */
		update: function ($element, postProcessFn) {
			postProcessFn();
		},


		/**
		 * Post processor, being called to augument the Block Element's DOM by drag handles etc.
		 *
		 * This method must be idempotent. I.e. it must produce the same results
		 * when called once or twice.
		 */
		_postProcessElementIfNeeded: function () {
			this.createEditablesIfNeeded();
			this._checkThatNestedBlocksAreStillConsistent();

			this.renderBlockHandlesIfNeeded();
			if (this.isDraggable()) {
				var nodeName = this.$element[0].nodeName;
				if (nodeName === 'SPAN' && !this.$element.data('dd-setup-done')) {
					// Unfortunately _setupDragDropForInlineElements() is not
					// idempotent because $.draggable() isn't.
					this._setupDragDropForInlineElements();
					this._disableUglyInternetExplorerDragHandles();
					this.$element.data('dd-setup-done', true);
				} else if (nodeName === 'DIV') {
					this._setupDragDropForBlockElements();
					this._disableUglyInternetExplorerDragHandles();
				}
			}
			this._hideDragHandlesIfDragDropDisabled();
			this._attachDropzoneHighlightEvents();
		},

		/**
		 * Due to indeterminate initialization order of nested blocks,
		 * it can happen that blockifying a parent block deconnects $element inside
		 * child blocks.
		 *
		 * This is the case we detect here; and if it happens, we reconnect the
		 * block to its currently visible DOM element.
		 */
		_checkThatNestedBlocksAreStillConsistent: function () {
			this.$element.find('.aloha-block').each(function () {
				var block = BlockManager.getBlock(this);
				if (block && block.$element[0] !== this) {
					block._connectThisBlockToDomElement(this);
				}
			});
		},

		/**
		 * Helper which disables the ugly IE drag handles. They are still shown, but at
		 * least they do not work anymore
		 */
		_disableUglyInternetExplorerDragHandles: function () {
			if (Aloha.browser.msie) {
				var $elem = this.$element.get(0);
				$elem.onresizestart = Fn.returnFalse;
				$elem.oncontrolselect = Fn.returnFalse;
				// We do NOT abort the "ondragstart" event as it is required for drag/drop.
				$elem.onmovestart = Fn.returnFalse;
				// We do NOT abort the "onselectstart" event because this would disable selection in nested editables
			}
		},

        /**
         * Removes the draghandle class from block handle,
         * if drag & drop is disabled for the editable
         */
        _hideDragHandlesIfDragDropDisabled: function () {
			if (!this._dd_isDragdropEnabled()) {
				this.$element.find('.aloha-block-draghandle').each(function () {
					var $draghandle = jQuery(this);
					if (!BlockUtils.isDragdropEnabledForElement($draghandle)) {
						$draghandle.removeClass('aloha-block-draghandle');
					}
				});
			} 
        },

        /**
         * Attach mousedown/up events to block's draghandle 
         * to toggle dropzones when dragging starts and ends.
         */
        _attachDropzoneHighlightEvents: function () {
            var that = this;

            this.$element.delegate(".aloha-block-draghandle", "mousedown", function () {
                var dropzones = that.$element.parents(".aloha-editable").first().data("block-dropzones") || [];
                jQuery.each(dropzones, function (i, editable_selector) {
                    var editables = jQuery(editable_selector);
                    jQuery(editables).each(function () {
                        if (jQuery(this).data("block-dragdrop")) {
                            jQuery(this).addClass("aloha-block-dropzone");
                        }
                    });
                });

                // Remove the dropzones as soon as the mouse is released,
                // irrespective of where the drop took place.
                jQuery(document).one("mouseup.aloha-block-dropzone", function () {
                    var dropzones = that.$element.parents(".aloha-editable").first().data("block-dropzones") || [];
                    jQuery.each(dropzones, function (i, editable_selector) {
                        jQuery(editable_selector).removeClass("aloha-block-dropzone");
                    });
                });
            });
        },

		/**************************
		 * SECTION: Drag&Drop for INLINE elements
		 **************************/
		_setupDragDropForInlineElements: function () {
			var that = this;

			// Here, we store the character DOM element which has been hovered upon recently.
			// This is needed as somehow, the "drop" event on the character is not fired.
			// Furthermore, we use it to know whether we need to "revert" the draggable to the original state or not.
			var lastHoveredCharacter = null;

            // Unless this flag is set to true, drag operation should be reverted.
            // Firing of "drop" event will set this to true.
            var blockDroppedProperly = false;

			// HACK for IE7: Internet Explorer 7 has a very weird behavior in
			// not always firing the "drop" callback of the inner droppable... However,
			// the "over" and "out" callbacks are fired correctly.
			// Because of this, we handle the "drop" inside the "stop" callback in IE7
			// instead of the "drop" callback (where it is handled in all other browsers)

			// This $currentDraggable is also needed as part of the IE 7 hack.
			// $currentDraggable contains a reference to the current draggable, but
			// only makes sense to read when lastHoveredCharacter !== NULL.
			var $currentDraggable = null;

            // We need to store the droppables created at the start of the drag,
            // they should be destroyed when the drag stops.
            var $createdDroppables = null;

			// This dropFn is the callback which handles the actual moving of
			// nodes. We created a separate function for it, as it is called inside the "stop" callback
			// in IE7 and inside the "drop" callback in all other browsers.
			var dropFn = function () {
				if (lastHoveredCharacter) {
					// the user recently hovered over a character
					var $dropReferenceNode = jQuery(lastHoveredCharacter), oldParent = $currentDraggable[0].parentNode;

					if ($dropReferenceNode.is('.aloha-block-dropInlineElementIntoEmptyBlock')) {
						// the user wanted to drop INTO an empty block!
						$dropReferenceNode.prepend($currentDraggable);
					} else if ($dropReferenceNode.is('.aloha-block-droppable-right')) {
						$dropReferenceNode.html($dropReferenceNode.html() + ' ');

						// Move draggable after drop reference node
						$dropReferenceNode.after($currentDraggable);
					} else {
						// Insert space in the beginning of the drop reference node
						if ($dropReferenceNode.prev('[data-i]').length > 0) {
							// If not the last element, insert space in front of next element (i.e. after the moved block)
							$dropReferenceNode.prev('[data-i]').html($dropReferenceNode.prev('[data-i]').html() + ' ');
						}
						$dropReferenceNode.html(' ' + $dropReferenceNode.html());

						// Move draggable before drop reference node
						$dropReferenceNode.before($currentDraggable);
					}

					Engine.ensureContainerEditable(oldParent);

					$currentDraggable.removeClass('ui-draggable').css({'left': 0, 'top': 0}); // Remove "draggable" options... somehow "Destroy" does not work
					that._fixScrollPositionBugsInIE();
				}
				jQuery('.aloha-block-dropInlineElementIntoEmptyBlock').removeClass('aloha-block-dropInlineElementIntoEmptyBlock');

				// clear the created droppables
				if ($createdDroppables) {
					$createdDroppables.filter(":data(droppable)").droppable("destroy");
					$createdDroppables = null;
				}

				blockDroppedProperly = true;
			};
			var editablesWhichNeedToBeCleaned = [];

			// Prevent the prevention of drag inside a cell
			var element = this.$element.get(0);
			element.ondragstart = function (e) {
				if (e) {
					if (typeof e.stopPropagation === 'function') {
						e.stopPropagation();
					} else {
						e.cancelBubble = true;
					}
				} else {
					window.event.cancelBubble = true;
				}
			};

			this.$element.draggable({
				handle: '.aloha-block-draghandle',
				scope: 'aloha-block-inlinedragdrop',
				disabled: !this._dd_isDragdropEnabled(),
				revert: function () {
					return (lastHoveredCharacter === null || !blockDroppedProperly);
				},
				revertDuration: 250,
				stop: function () {
					if (jQuery.browser.msie && 7 === parseInt(jQuery.browser.version, 10)) {
						dropFn();
					}
					jQuery.each(editablesWhichNeedToBeCleaned, function () {
						that._dd_traverseDomTreeAndRemoveSpans(this);
					});

					if ($currentDraggable) {
						BlockUtils.unpad($currentDraggable);
					}

					$currentDraggable = null;

					editablesWhichNeedToBeCleaned = [];

					// deactivate the current editable and activate the editable,
					// the block has been dropped into. This will do necessary initializations that
					// happen on activation of the editable
					Aloha.deactivateEditable();
					var editable = Aloha.getEditableHost(that.$element);
					if (editable) {
						editable.activate();
					}
				},
				start: function () {
					lastHoveredCharacter = null;
					blockDroppedProperly = false;
					editablesWhichNeedToBeCleaned = [];

					// In order to make Inline Blocks droppable into empty paragraphs, we insert a &nbsp; manually before the placeholder-br.
					// -> for IE
					jQuery('.aloha-editable').children('p:empty').html('&nbsp;');


					// Make **ALL** editables on the page droppable, such that it is possible
					// to drag/drop *across* editable boundaries
					var droppableCfg = {
						// make block elements droppable
						tolerance: 'pointer',
						addClasses: false, // performance optimization
						scope: 'aloha-block-inlinedragdrop',

						/**
						 * When hovering over a paragraph, we make convert its contents into spans, to make
						 * them droppable.
						 */
						over: function (event, ui) {
							if (jQuery.inArray(this, editablesWhichNeedToBeCleaned) === -1) {
								editablesWhichNeedToBeCleaned.push(this);
							}

							var hasOnlyProppingBr = (
								1 === jQuery(this).contents().length &&
								1 === jQuery(this).children('br').length
							);
							$currentDraggable = ui.draggable;

							if (jQuery(this).is(':empty') ||
								hasOnlyProppingBr ||
								jQuery.trim(jQuery(this).text()) === '') {
								// The user is hovering over an empty
								// container; simply highlight the container.
								jQuery(this).addClass(
									'aloha-block-dropInlineElementIntoEmptyBlock');
								lastHoveredCharacter = this;
								return;
							}

							that._dd_traverseDomTreeAndWrapCharactersWithSpans(this);
							jQuery('span[data-i]', this).droppable({
								tolerance: 'pointer',
								addClasses: false,
								scope: 'aloha-block-inlinedragdrop',
								over: function () {
									if (lastHoveredCharacter) {
										// Just to be sure, we remove the css class of the last hovered character.
										// This is needed such that spans are deselected which contain multiple
										// lines.
										jQuery(lastHoveredCharacter).removeClass('aloha-block-droppable');
									}
									lastHoveredCharacter = this;
									jQuery(this).addClass('aloha-block-droppable');
								},
								out: function () {
									jQuery(this).removeClass('aloha-block-droppable');
									if (lastHoveredCharacter === this) {
										lastHoveredCharacter = null;
									}
								}
							});
							// Now that we updated the droppables in the system, we need to recalculate
							// the Drag Drop offsets.
							jQuery.ui.ddmanager.prepareOffsets(ui.draggable.data('draggable'), event);
						},
						out: function () {
							jQuery(this).removeClass('aloha-block-dropInlineElementIntoEmptyBlock');
						},

						/**
						 * When dropping over a paragraph, we use the "lastHoveredCharacter"
						 * as drop target.
						 */
						drop: function () {
							if (!(jQuery.browser.msie && 7 === parseInt(jQuery.browser.version, 10))) {
								dropFn();
							}
						}
					};

					$createdDroppables = jQuery(".aloha-editable.aloha-block-dropzone").children(":not(.aloha-block)");

					// add empty editables
					$createdDroppables = $createdDroppables.add(".aloha-editable:not(:has(*))");

					// Small HACK: Also make table cells droppable
					$createdDroppables = $createdDroppables.add('.aloha-table-cell-editable');
					$createdDroppables = $createdDroppables.add(jQuery('.aloha-editable.aloha-block-dropzone .aloha-table-cell-editable').children(":not(.aloha-block)"));

					$createdDroppables.droppable(droppableCfg);
				}
			});
		},

		/**
		 * Helper which traverses the DOM tree starting from el and wraps all non-empty texts with spans,
		 * such that they can act as drop target.
		 *
		 * @param {DomElement} el
		 */
		_dd_traverseDomTreeAndWrapCharactersWithSpans: function (el) {
			var child;
			for (var i = 0, l = el.childNodes.length; i < l; i++) {
				child = el.childNodes[i];
				if (child.nodeType === 1) { // DOM Nodes
					if (!~child.className.indexOf('aloha-block') && child.attributes['data-i'] === undefined) {
						// We only recurse if child does NOT have the class "aloha-block", and is NOT data-i
						this._dd_traverseDomTreeAndWrapCharactersWithSpans(child);
					} else if (child.attributes['data-i']) {
						// data-i set -> we have converted this hierarchy level already --> early return!
						return;
					}
				} else if (child.nodeType === 3) { // Text Nodes
					var numberOfSpansInserted = this._dd_insertSpans(child);
					i += numberOfSpansInserted;
					l += numberOfSpansInserted;
				}
			}
		},

		/**
		 * Helper which splits text on word boundaries, adding whitespaces to the following element.
		 * Examples:
		 * - "Hello world" -> ["Hello", " world"]
		 * - " Hello world" -> [" Hello", " world"]
		 * --> see the unit tests for the specification
		 */
		_dd_splitText: function (text) {
			var textParts = text.split(/(?=\b)/);
			var cleanedTextParts = [];

			var isWhitespace = false;
			for (var i = 0, l = textParts.length; i < l; i++) {
				if (!/[^\t\n\r ]/.test(textParts[i])) {
					// if the current text part is just whitespace, we add a flag...
					isWhitespace = true;
				} else {
					if (isWhitespace) {
						// we have a whitespace to add
						cleanedTextParts.push(' ' + textParts[i]);
						isWhitespace = false;
					} else {
						cleanedTextParts.push(textParts[i]);
					}
				}
			}
			if (isWhitespace) {
				cleanedTextParts[cleanedTextParts.length - 1] += ' ';
			}
			return cleanedTextParts;
		},

		/**
		 * This is a helper for _dd_traverseDomTreeAndWrapCharactersWithSpans,
		 * performing the actual conversion.
		 *
		 * This function returns the number of additional DOM elements inserted.
		 * This is "numberOfSpansCreated - 1" (because one text node has been initially there)
		 */
		_dd_insertSpans: function (el) {
			var text = el.nodeValue;

			// If node just contains empty strings, we do not do anything.
			// Use ECMA-262 Edition 3 String and RegExp features
			if (!/[^\t\n\r ]/.test(text)) {
				return 0;
			}
			var newNodes = document.createDocumentFragment();

			var splitText = this._dd_splitText(text);

			var l = splitText.length;
			var x, word, leftWordPartLength, t;
			var numberOfSpansInserted = 0;

			for (var i = 0; i < l; i++) {
				// left half of word
				word = splitText[i];
				if (word.length === 0) {
					continue;
				}
				// We use "floor" here such that sentence delimiters like "!" can have a block placed afterwards
				leftWordPartLength = Math.floor(word.length / 2);

				// For Internet Explorer, we only make dropping AFTER words possible to improve performance
				var browserMajorVersion = parseInt(jQuery.browser.version, 10);
				if (jQuery.browser.msie && (7 === browserMajorVersion || 8 === browserMajorVersion)) {
					leftWordPartLength = 0;
				}

				if (leftWordPartLength > 0) {
					x = document.createElement('span');
					x.appendChild(document.createTextNode(word.substr(0, leftWordPartLength)));
					x.setAttribute('data-i', i);

					newNodes.appendChild(x);
					numberOfSpansInserted++;
				}

				// right half of word
				x = document.createElement('span');
				t = word.substr(leftWordPartLength);
				x.appendChild(document.createTextNode(t));
				x.setAttribute('data-i', i);
				x.setAttribute('class', 'aloha-block-droppable-right');

				newNodes.appendChild(x);
				numberOfSpansInserted++;
			}
			el.parentNode.replaceChild(newNodes, el);
			return numberOfSpansInserted - 1;
		},

		/**
		 * After the Drag/Drop operation, we need to remove the SPAN elements
		 * again.
		 */
		_dd_traverseDomTreeAndRemoveSpans: function (el) {
			var nodesToDelete = [], convertBack;
			convertBack = function (el) {
				var currentlyTraversingExpandedText = false, currentText, lastNode;
				var child;
				for (var i = 0, l = el.childNodes.length; i < l; i++) {
					child = el.childNodes[i];
					if (child.nodeType === 1) { // Node
						if (child.attributes['data-i'] !== undefined) {
							if (!currentlyTraversingExpandedText) {
								// We did not traverse expanded text before, and just entered an expanded text section
								// thus, we reset all variables to their initial state
								currentlyTraversingExpandedText = true;
								currentText = '';
								lastNode = undefined;
							}
							if (currentlyTraversingExpandedText) {
								// We are currently traversing the expanded text nodes, so we collect their data
								// together in the currentText variable. We know that they only
								// have one TextNode child, as this is the way we constructed them.
								//
								// Note: we do NOT use child.innerHTML here, as this returns HTML entities;
								// but we need the HTML entities already processed:
								// - child.innerHTML returns "Hello&nbsp;World"
								// - child.firstChild.nodeValue returns "Hello World"
								currentText += child.firstChild.nodeValue;

								if (lastNode) {
									nodesToDelete.push(lastNode);
								}
								lastNode = child;
							}
						} else {
							if (currentlyTraversingExpandedText) {
								currentlyTraversingExpandedText = false;
								// We just left a region with data-i elements set.
								// so, we need to store the currentText back to the region.
								// We do this by using the last visited node as anchor.
								lastNode.parentNode.replaceChild(document.createTextNode(currentText), lastNode);
							}
							// Recursion
							if (!~child.className.indexOf('aloha-block')) {
								// If child does not have the class "aloha-block", we iterate into it
								convertBack(child);
							}
						}
					}
				}
				if (currentlyTraversingExpandedText) {
					// Special case: the last child node *is* a wrapped text node and we are at the end of the collection.
					// In this case, we convert the text as well.
					lastNode.parentNode.replaceChild(document.createTextNode(currentText), lastNode);
				}
			};

			convertBack(el);

			for (var i = 0, l = nodesToDelete.length; i < l; i++) {
				nodesToDelete[i].parentNode.removeChild(nodesToDelete[i]);
			}
		},

        /**
         * Helper method to check whether the drag & drop is enabled
         * for the editable, which this block belongs to.
         */
        _dd_isDragdropEnabled: function () {
			return BlockUtils.isDragdropEnabledForElement(this.$element.parent());
        },

		/**************************
		 * SECTION: Drag&Drop for Block elements
		 **************************/

		_setupDragDropForBlockElements: function () {
			// Mark the drag handle with an extra CSS class, such that it is picked up by BlockManager.initializeBlockLevelDragDrop()
			this.$element.find('.aloha-block-draghandle').addClass('aloha-block-draghandle-blocklevel');
		},


		/**************************
		 * SECTION: Other Rendering Helpers
		 **************************/

		/**
		 * Create editables from the inner content that was
		 * rendered for this block.
		 *
		 * This method must be idempotent. I.e. it must produce the same results
		 * when called once or twice.
		 *
		 * Override to use a custom implementation and to pass
		 * special configuration to .aloha()
		 */
		createEditablesIfNeeded: function () {
			// TODO: only create them if they are no aloha element yet...
			// TODO: should only happen inside Aloha
			this.$element.find('.aloha-editable').aloha();
		},

		/**
		 * Render block toolbar elements
		 *
		 * This method must be idempotent. I.e. it must produce the same results
		 * when called once or twice.
		 *
		 * Template method to render custom block UI.
		 * @api
		 */
		renderBlockHandlesIfNeeded: function () {
			if (this.isDraggable()) {
				if (this.$element.children('.aloha-block-draghandle').length === 0) {
					this.$element.prepend('<span class="aloha-block-handle aloha-block-draghandle aloha-cleanme"></span>');
				}
			}
		},

		/**************************
		 * SECTION: Attribute Handling
		 **************************/

		/**
		 * Get or set one or many attribute, similar to the jQuery attr() function.
		 *
		 * The attribute keys are converted internally to lowercase,
		 * so attr('foo', 'bar') and attr('FoO', 'bar') are the same internally.
		 * The same applies to reading.
		 *
		 * It is not allowed to set internal attributes (starting with aloha-block-) through this API.
		 *
		 * @api
		 * @param {String|Object} attributeNameOrObject
		 * @param {String} attributeValue
		 * @param {Boolean} Optional. If true, we do not fire change events.
		 */
		attr: function (attributeNameOrObject, attributeValue, suppressEvents) {
			var that = this, attributeChanged = false;

			if (arguments.length >= 2) {
				if (attributeNameOrObject.substr(0, 12) === 'aloha-block-') {
					Aloha.Log.error('block/block', 'It is not allowed to set internal block attributes (starting with aloha-block-) through Block.attr() (You tried to set ' + attributeNameOrObject + ')');
					return;
				}
				if (this._getAttribute(attributeNameOrObject) !== attributeValue) {
					attributeChanged = true;
				}
				this._setAttribute(attributeNameOrObject, attributeValue);
			} else if (typeof attributeNameOrObject === 'object') {
				jQuery.each(attributeNameOrObject, function (key, value) {
					if (key.substr(0, 12) === 'aloha-block-') {
						Aloha.Log.error('block/block', 'It is not allowed to set internal block attributes (starting with aloha-block-) through Block.attr() (You tried to set ' + key + ')');
						return;
					}
					if (that._getAttribute(key) !== value) {
						attributeChanged = true;
					}
					that._setAttribute(key, value);
				});
			} else if (typeof attributeNameOrObject === 'string') {
				return this._getAttribute(attributeNameOrObject);
			} else {
				return this._getAttributes();
			}
			if (attributeChanged && !suppressEvents) {
				this._update();
				this.trigger('change');
				if (Aloha.activeEditable) {
					Aloha.activeEditable.smartContentChange({type: 'block-change'});
				}
			}
			return null;
		},

		/**
		 * Internal helper for setting  a single attribute.
		 */
		_setAttribute: function (name, value) {
			this.$element.attr('data-' + name.toLowerCase(), value);
		},

		/**
		 * Internal helper for getting an attribute
		 */
		_getAttribute: function (name) {
			return this.$element.attr('data-' + name.toLowerCase());
		},

		/**
		 * Internal helper for getting all attributes
		 */
		_getAttributes: function () {
			var attributes = {};

			// element.data() not always up-to-date, that's why we iterate over the attributes directly.
			jQuery.each(this.$element[0].attributes, function (i, attribute) {
				if (attribute.name.substr(0, 5) === 'data-') {
					attributes[attribute.name.substr(5).toLowerCase()] = attribute.value;
				}
			});

			return attributes;
		}
	});

	/**
	 * @name block.block.DefaultBlock
	 * @class A default block that renders the initial content
	 * @extends block.block.AbstractBlock
	 */
	var DefaultBlock = AbstractBlock.extend(
	/** @lends block.block.DefaultBlock */
	{
		update: function ($element, postProcessFn) {
			postProcessFn();
		}
	});

	/**
	 * @name block.block.DebugBlock
	 * @class A debug block outputs its attributes in a table
	 * @extends block.block.AbstractBlock
	 */
	var DebugBlock = AbstractBlock.extend(
	/** @lends block.block.DebugBlock */
	{
		title: 'Debugging',
		init: function ($element, postProcessFn) {
			this.update($element, postProcessFn);
		},
		update: function ($element, postProcessFn) {
			$element.css({display: 'block'});
			var renderedAttributes = '<table class="debug-block">';
			jQuery.each(this.attr(), function (k, v) {
				renderedAttributes += '<tr><th>' + k + '</th><td>' + v + '</td></tr>';
			});

			renderedAttributes += '</table>';

			$element.html(renderedAttributes);
			postProcessFn();
		}
	});
	
	/**
	 * @name block.block.EmptyBlock
	 * @class An empty block doesn't render any tag fill icons or borders (no Aloha tags)
	 * @extends block.block.AbstractBlock
	 */
	var EmptyBlock = AbstractBlock.extend(
	/** @lends block.block.EmptyBlock */
	{
		title: 'EmptyBlock',
		init: function () {},
		activate: function () {},
		deactivate: function () {},
		renderBlockHandlesIfNeeded: function () {},
		_preventSelectionChangedEventHandler: function () {}
	});

	return {
		AbstractBlock: AbstractBlock,
		DefaultBlock: DefaultBlock,
		DebugBlock: DebugBlock,
		EmptyBlock: EmptyBlock
	};
});

/* blockcontenthandler.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('block/blockcontenthandler',
['jquery', 'aloha/contenthandlermanager', 'block/blockmanager'],
function (jQuery, ContentHandlerManager, BlockManager) {

	/**
	 * @name block.BlockContentHandler
	 * @class Special block content handler
	 *
	 * The blog content handler handles pasting of blocks in editables. Pasted
	 * block markup will be replaced by a newly created block instance.
	 */
	var BlockContentHandler = ContentHandlerManager.createHandler(
	/** @lends block.BlockContentHandler */
	{
		/**
		 * Handle the pasting. Remove all unwanted stuff.
		 *
		 * There are two main cases which we need to distinguish:
		 * 1) Aloha Blocks are selected, and crtl+c is pressed then. In this case, *only the block* is copied / pasted.
		 * 2) Text is selected, but the selection also spans an aloha block.
		 *
		 * Generally, case 2) seems to work without bigger problems in Webkit / Firefox, while
		 * case 1) results in very much undesired and inconsistent behavior. If 1) happens,
		 * the property "data-aloha-block-copy-only-block" is set to "true"; so we can kick in and
		 * do additional cleanups.
		 * @param {jQuery} content
		 */
		handleContent: function (content) {
			if (typeof content === 'string') {
				content = jQuery('<div>' + content + '</div>');
			} else if (content instanceof jQuery) {
				content = jQuery('<div>').append(content);
			}

			if (content.find('.aloha-block[data-aloha-block-copy-only-block="true"]').length > 0) {
				// We are in case 1; so some more cleanup is needed (at least in webkit and firefox).

				// Webkit seems to *duplicate* the block when copying. The duplicated
				// block has *no ID property* set, that's how we can find and discard it.
				// Very ugly!
				content.find('.aloha-block:not([id])').remove();
				// Further cleanup for Webkit, removing empty nodes. Quite hacky!
				content.find('.aloha-block + span:empty').remove();
				content.find('div:empty').remove();
				// (another) Hack for Webkit, removing superfluous BR
				content.find('br.Apple-interchange-newline').remove();

				// Firefox adds a <br> directly before the .aloha-block...
				content.find('.aloha-block').prev('br').remove();

				// Chrome (at least) sometimes adds an empty <br> inside an (otherwise empty) span
				content.find('div > br:only-child').parent().remove();

			}

			content.find('.aloha-block').each(function () {
				var oldBlock = jQuery(this);

				var elementAttributes = {}; // all attributes except data-*
				var blockAttributes = {}; // all data* attributes
				jQuery.each(oldBlock[0].attributes, function (k, v) {
					if (v.nodeName === 'id') {
						return;
					}

					if (v.nodeName.match(/^data-/)) {
						blockAttributes[v.nodeName.substr(5)] = v.nodeValue;
					} else {
						elementAttributes[v.nodeName] = v.nodeValue;
					}
				});

				var newBlockId = GENTICS.Utils.guid();

				var newBlock = jQuery('<' + this.tagName + '/>')
					.attr(elementAttributes)
					.attr('id', newBlockId)
					.removeClass('aloha-block-active')
					.removeClass('aloha-block')
					.html(oldBlock.html());

				oldBlock.replaceWith(newBlock);

				// We need to blockify the contents with a timeout, as we need the connected DOM node for it.
				window.setTimeout(function () {
					BlockManager._blockify(jQuery('#' + newBlockId), blockAttributes);
				}, 50);
			});

			return content.html();
		}
	});
	return BlockContentHandler;
});

/* editor.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * @name block.editor
 * @namespace Block attribute editors
 */
define('block/editor',['jquery', 'aloha/observable', 'util/class'],
function (jQuery, Observable, Class) {
	

	/**
	 * This is the base class for all editors in the sidebar. You need to extend
	 * this class if you need to write your own editor. In most cases, however,
	 * it is sufficent to subclass the AbstractFormElementEditor.
	 *
	 * @name block.editor.AbstractEditor
	 * @class An abstract editor
	 */
	var AbstractEditor = Class.extend(Observable,
	/** @lends block.editor.AbstractEditor */
	{
		/**
		 * Schema of the current element
		 *
		 * @param {Object}
		 * @api
		 */
		schema: null,

		/**
		 * @constructor
		 */
		_constructor: function (schema) {
			this.schema = schema;
		},

		/**
		 * Template method to render the editor elements. Override it
		 * in your subclass! Needs to return the jQuery element which
		 * should be added to the DOM
		 *
		 * @return {jQuery}
		 * @api
		 */
		render: function () {
			// Implement in subclass!
		},

		/**
		 * Template method to get the current editor value
		 *
		 * Override it in your subclass!
		 *
		 * @return {String}
		 * @api
		 */
		getValue: function () {
			// Implement in subclass!
		},

		/**
		 * Method which is called at initialization time, to set the current value.
		 *
		 * Override it in your subclass!
		 *
		 * You should not throw any change event here, as we need to break the loop "Block" -> "Editor" -> "Block"
		 *
		 * @param {String} value
		 * @api
		 */
		setValue: function (value) {
			// Implement in subclass!
		},

		/**
		 * Destroy the editor elements and unbind events
		 * @api
		 */
		destroy: function () {
			// Implement in subclass!
		},

		/**
		 * On deactivating, we still need to trigger a change event if the value has been modified.
		 *
		 * @private
		 */
		_deactivate: function () {
			this.trigger('change', this.getValue());
			this.destroy();
		}
	});

	/**
	 * This is a more specialized FormElementEditor which should be used
	 * for form-based editors.
	 *
	 * @name block.editor.AbstractFormElementEditor
	 * @class An abstract form editor with label
	 * @extends block.editor.AbstractEditor
	 */
	var AbstractFormElementEditor = AbstractEditor.extend(
	/** @lends block.editor.AbstractFormElementEditor */
	{

		/**
		 * Input element HTML definition
		 *
		 * You need to override this in your subclass.
		 *
		 * @type String
		 *
		 * @api
		 */
		formInputElementDefinition: null,

		/**
		 * The jQuery element of the form input element.
		 *
		 * @type {jQuery}
		 */
		_$formInputElement: null,

		/**
		 * Render the label and form element
		 *
		 * @return {jQuery}
		 */
		render: function () {
			var $wrapper = jQuery('<div class="aloha-block-editor" />');
			var guid = GENTICS.Utils.guid();
			var $label = this.renderLabel().attr('id', guid);
			var $formElement = this.renderFormElement().attr('id', guid);

			if (this.schema.fieldsetLabel) {
				// When using a fieldset, the actual input element has to be
				// placed in that instead of the wrapper element.
				$label.append($formElement).appendTo($wrapper);
			} else {
				$wrapper.append($label).append($formElement);
			}

			return $wrapper;
		},

		/**
		 * Render the label for the editor, by using the "label" property
		 * from the schema.
		 *
		 * When <code>this.schema.fieldsetLabel</code> is true the
		 * label will be rendered as a <code>legend</code> tag inside
		 * a <code>fieldset</code>.
		 *
		 * @return {jQuery}
		 */
		renderLabel: function () {
			var element;

			if (this.schema.fieldsetLabel) {
				element = jQuery('<fieldset>').append(jQuery('<legend>', { text: this.schema.label }));
			} else {
				element = jQuery('<label>', { text: this.schema.label });
			}

			return element;
		},

		/**
		 * Render the form input element
		 * @return {jQuery}
		 */
		renderFormElement: function () {
			var that = this;
			this._$formInputElement = jQuery(this.formInputElementDefinition);

			this.afterRenderFormElement(this._$formInputElement);

			this._$formInputElement.change(function () {
				that.trigger('change', that.getValue());
			});

			return this._$formInputElement;
		},

		/**
		 * Callback which can be implemented by subclasses to adjust the rendered
		 * form input element
		 *
		 * @param {jQuery} $formElement the form element being rendered
		 * @api
		 */
		afterRenderFormElement: function ($formElement) {

		},

		/**
		 * @return {String}
		 */
		getValue: function () {
			return this._$formInputElement.val();
		},

		/**
		 * We do not throw any change event here, as we need to break the loop "Block" -> "Editor" -> "Block"
		 */
		setValue: function (value) {
			this._$formInputElement.val(value);
		},

		/**
		 * Cleanup and remove the input element
		 */
		destroy: function () {
			this._$formInputElement.remove();
		}

	});

	/**
	 * @name block.editor.StringEditor
	 * @class An editor for string input
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var StringEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.StringEditor */
	{
		formInputElementDefinition: '<input type="text" />'
	});

	/**
	 * @name block.editor.NumberEditor
	 * @class An editor for numbers
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var NumberEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.NumberEditor */
	{
		// TODO Range should be an option
		formInputElementDefinition: '<input type="range" />',

		afterRenderFormElement: function ($formElement) {
			if (!this.schema.range) {
				return;
			}

			if (this.schema.range.min) {
				$formElement.attr('min', this.schema.range.min);
			}

			if (this.schema.range.max) {
				$formElement.attr('max', this.schema.range.max);
			}

			if (this.schema.range.step) {
				$formElement.attr('step', this.schema.range.step);
			}
		}
	});

	/**
	 * @name block.editor.UrlEditor
	 * @class An editor for URLs
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var UrlEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.UrlEditor */
	{
		formInputElementDefinition: '<input type="url" />'
	});

	/**
	 * @name block.editor.EmailEditor
	 * @class An editor for email addresses
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var EmailEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.EmailEditor */
	{
		formInputElementDefinition: '<input type="email" />'
	});

	/**
	 * @name block.editor.SelectEditor
	 * @class An editor for select fields
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var SelectEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.SelectEditor */
	{
		formInputElementDefinition: '<select />',

		afterRenderFormElement: function ($formElement) {
			jQuery.each(this.schema.values, function () {
				var el = this;
				$formElement.append(jQuery('<option />').attr('value', el.key).html(el.label));
			});
		}
	});

	/**
	 * @name block.editor.RadioButtonEditor
	 * @class An editor for a radio button group
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var RadioButtonEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.RadioButtonEditor */
	{
			formInputElementDefinition: '<ul />',
			afterRenderFormElement: function ($formElement) {
				var groupName = 'rb_' + GENTICS.Utils.guid();

				jQuery.each(this.schema.values, function (idx, el) {
					var inputId = groupName + '_' + el.key;
					var $input = $('<input>', {
						type: 'radio',
						value: el.key,
						name: groupName,
						id: inputId
					});
					var $label = $('<label>', {
						'for': inputId,
						'class': 'input-label',
						text: el.label
					});

					$('<li>').append($input).append($label).appendTo($formElement);
				})
			},
			getValue: function () {
				return this._$formInputElement.find(':checked').val();
			},
			setValue: function (value) {
				this._$formInputElement.find('[value=' + value + ']').prop('checked', true);
			}
	});
	/**
	 * @name block.editor.ButtonEditor
	 * @class An editor for buttons, executing a custom supplied callback "callback"
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var ButtonEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.SelectEditor */
	{
		formInputElementDefinition: '<button />',

		afterRenderFormElement: function ($formElement) {
			var that = this;
			$formElement.html(this.schema.buttonLabel);
			$formElement.click(function () {
				that.schema.callback();
			})
		}
	});

	return {
		AbstractEditor: AbstractEditor,
		AbstractFormElementEditor: AbstractFormElementEditor,
		StringEditor: StringEditor,
		NumberEditor: NumberEditor,
		UrlEditor: UrlEditor,
		EmailEditor: EmailEditor,
		SelectEditor: SelectEditor,
		RadioButtonEditor: RadioButtonEditor,
		ButtonEditor: ButtonEditor
	}
});

/*global window:true, define:true, document:true */
/* dragbehavior.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('block/dragbehavior',[
	'aloha/jquery',
	'aloha',
	'PubSub',
	'aloha/copypaste',
	'block/block-utils',
	'aloha/console',
	'ui/scopes'
], function (
	$,
	Aloha,
	PubSub,
	CopyPaste,
	BlockUtils,
	Console,
	Scopes
) {
	

	/**
	 * Tags that is not allowed to wrap BlockElements. For this elements the
	 * mouseover event is not listened
	 * @type {Array.<String>}
	 */
	var notAllowedOverTags = ['TABLE', 'TR',
		'HR', 'TBODY', 'UL', 'OL', 'DL', 'B', 'STRONG', 'A', 'EM', 'I', 'PRE',
		'CODE', 'SUP', 'SUB'],
	/**
	 * This element are suitable to listen the mouseover event, but can´t wrap
	 * a BlockElement
	 * @type {Array.<String>}
	 */
		notAllowedDropSelector = ['.aloha-block', '.aloha-block-handle',
			'.aloha-table-selectrow', 'p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5',
			'h6', 'br'],
	/**
	 * Parents of element that can´t wrap BlockElements
	 * @type {Array.<String>}
	 */
		notAllowedDropParentsSelector = [
			'.aloha-table-selectcolumn',
			'.aloha-ui',
			'.ui-draggable-dragging'
		];


	/**
	 * Checks if the drag and drop is for a nested Table.
	 *
	 * @param  {jQuery<Element>} $hovering
	 * @param  {jQuery<Element>} $dragging
	 * @return {boolean}
	 */
	function isNestedTable ($hovering, $dragging) {
		return $hovering
			&& $dragging
			&& $hovering.parents('table').length !== 0
			&& BlockUtils.isTable($dragging);
	}

	/**
	 * Checks whether or not the element over which we are hovering should allow
	 * drop a region, in or around it?
	 *
	 * @param {jQuery.<HTMLElement> $hovering
	 * @param {jQuery.<HTMLElement> $dragging
	 * @return {boolean}
	 */
	function allowDropRegions($hovering, $dragging) {
		return !$hovering || !(
			$hovering.is('.ui-draggable-dragging') ||
			$hovering.closest($dragging).length > 0
		);
	}

	/**
	 * @private
	 * @class Aloha.plugins.extra.DragBehavior
	 * @param {block.block.AbstractBlock} blockObject An AbstractBlock child
	 *                                    instance
	 */
	function DragBehavior(blockObject) {
		this.blockObject = blockObject;
		this.$element = blockObject.$element;
		this.insertBeforeOrAfterMode = false;
		if (this.$element[0].nodeName === 'DIV') {
			// this drag/drop behaviour is only suitable for DIV-blocks
			// inline drag/drop is initialized somewhere else (block.js)
			this.setDraggable();
		}
	}

	PubSub.sub('aloha.block.initialized', function (data) {
		DragBehavior.follow(data.block);
	});

	/**
	 * Because the Dom.isEditable don't work well I need to rewrite it.
	 * checks if a HTMLElement is editable, if the element have inherited
	 * behavior check the parents behaivor until get a true or false behavior.
	 *
	 * @param {HTMLElement} domElm
	 * @return {Boolean}
	 */
	function isEditable(domElm) {
		if (domElm.nodeType === 1) {
			if (domElm.contentEditable === 'inherit') {
				while (domElm.contentEditable === 'inherit') {
					domElm = domElm.parentNode;
				}
			}

			return (domElm.contentEditable === 'true');
		}
		return false;
	}

	/**
	 * Creates an instance of DragBehavior following a BlockElement
	 * only if the BlockElement given is suitable to be draggable.
	 *
	 * @static
	 * @param {block.block.AbstractBlock} blockObject An AbstractBlock child
	 *                                    instance
	 */
	DragBehavior.follow = function (blockObject) {
		if ($('>.aloha-block-draghandle', blockObject.$element).length > 0) {
			var jsLintMustBeHappyNotMe = new DragBehavior(blockObject);
		}
	};

	/**
	 * Internet Explorer loses the selection and scroll position between
	 * dragging and dropping.
	 *
	 * So we need to save the selection and scroll position before, and restore
	 * it after each drag and drop cycle.
	 */
	var IESelectionState = (function () {
		var range = null;
		var x = 0;
		var y = 0;
		return $.browser.msie ? {

				/**
				 * Remember the selection state.
				 */
				save: function saveSelection() {
					x = window.scrollX || document.documentElement.scrollLeft;
					y = window.scrollY || document.documentElement.scrollTop;
					range = CopyPaste.getRange();
				},

				/**
				 * Set the selection to the given range and focus on the editable
				 * inwhich the selection is in (if any).
				 *
				 * This function is used to restore the selection to what it was
				 * before dragging is started.
				 */
				restore: function restoreSelection() {
					if (!range) {
						return;
					}
					var editable = CopyPaste.getEditableAt(range);
					if (editable) {
						editable.obj.focus();
					}
					try {
						CopyPaste.setSelectionAt(range);
					} catch (e) {
						Console.warn(e);
					}
					window.scrollTo(x, y);
				}
			}
			: {
				save: function () {},
				restore: function () {}
			};
	}());

	/**
	 * Setup the draggable behavior to the BlockElement
	 */
	DragBehavior.prototype.setDraggable = function () {
		var dragBehavior = this;
		var $handle = $('>.aloha-block-draghandle', this.$element);
		var element = this.$element.get(0);

		element.onselectstart = function () {
			window.event.cancelBubble = true;
		};

		// Prevent the prevention of drag inside a cell
		element.ondragstart = function (e) {
			if (e) {
				if (typeof e.stopPropagation === 'function') {
					e.stopPropagation();
				} else {
					e.cancelBubble = true;
				}
			} else {
				window.event.cancelBubble = true;
			}
		};

		$handle
			.on('mousedown', function () {
				IESelectionState.save();
			})
			.on('mouseup', function () {
				dragBehavior._getHiglightElement().appendTo('body').css({position: 'absolute'}).hide();
				dragBehavior.stopListenMouseOver();
			});

		this.$element.draggable({
			handle: '>.aloha-block-draghandle',
			helper: 'clone',
			cursorAt: {
				left: -10,
				top: -10
			},
			start: function (event, ui) {
				// set the empty scope, so that the toolbar will be hidden while dragging
				Scopes.setScope('Aloha.empty');
				ui.helper.css('zIndex', 100000);
				dragBehavior._fillEmptyEditables();
				dragBehavior.listenMouseOver();
				event.stopImmediatePropagation();
			},
			drag: function (event) {
				event.stopImmediatePropagation();
			},
			stop: function (event, ui) {
				dragBehavior._getHiglightElement().appendTo('body').css({position: 'absolute'}).hide();
				dragBehavior.stopListenMouseOver();
				dragBehavior.onDragStop();
				dragBehavior._removeFillers();
				ui.helper.remove();
				IESelectionState.restore();
				return true;
			}
		});
	};

	/**
	 * Returns all the childs in all editables registered in Aloha
	 *
	 * @return jQuery.<HTMLElement>
	 */
	DragBehavior.prototype._getEditableChilds = function () {
		var i, editable, elms = [];
		for (i = 0; i < Aloha.editables.length; i++) {
			editable = Aloha.editables[i];
			elms = elms.concat(
				$('*:not(' + notAllowedOverTags.join(',') + ')', editable.obj).toArray()
			);
		}
		return $(elms);
	};

	/**
	 * Fill a "filler" element into every completely empty editable.
	 * This enables dropping blocks into empty editables (before or after the filler)
	 */
	DragBehavior.prototype._fillEmptyEditables = function () {
		var i, editable, filler;
		for (i = 0; i < Aloha.editables.length; i++) {
			editable = Aloha.editables[i];
			if (editable.obj.contents().length === 0) {
				filler = $("<div class='aloha-block aloha-dragdrop-filler' style='height:inherit; min-height:inherit'></div>");
				filler.contentEditable(false);
				editable.obj.append(filler);
			}
		}
	};

	/**
	 * Remove the "filler" elements
	 */
	DragBehavior.prototype._removeFillers = function () {
		var i, editable;
		for (i = 0; i < Aloha.editables.length; i++) {
			editable = Aloha.editables[i];
			editable.obj.children('.aloha-dragdrop-filler').remove();
		}
	};

	/**
	 * Listen the mouseOver events over all elements in the editables, since the
	 * drag action start until the drag action stop, to know in which element
	 * the user may drop the block element
	 */
	DragBehavior.prototype.listenMouseOver = function () {
		var dragBehavior = this,
			$elms = this._getEditableChilds();
		// @todo use the DOM Events for this, jQuery is very slow

		$elms.bind('mouseover.blockbehavior', function (e) {
			return dragBehavior.onMouseover(this, e);
		});
	};

	/**
	 * Stop listen the mouseover event, this method is called when the drag
	 * operation is ended (droped)
	 */
	DragBehavior.prototype.stopListenMouseOver = function () {
		var $elms = this._getEditableChilds();
		$elms.unbind('.blockbehavior');
	};

	/**
	 * Checks the element that is below of the draggable element in a drag
	 * operation, if is a valid element, this method call to highlight methods
	 *
	 * @param {HTMLElement} elm
	 * @param {jqEvent} event
	 *
	 * @return {Boolean}
	 */
	DragBehavior.prototype.onMouseover = function (elm, event) {
		if (this.$element.find(elm).length || this.$element.is(elm)) {
			// The mouse is still over the element which is
			// being dragged.
			event.stopImmediatePropagation();

			return false;
		}

		var $elm = $(elm);
		var $srcEditable = this.$element.closest('.aloha-editable');
		var $dstEditable = $elm.closest('.aloha-editable');
		var dropzones = ($srcEditable.data('block-dropzones') || ['.aloha-editable']).join();

		if (!$dstEditable.is(dropzones)) {
			if (Aloha.Log.isDebugEnabled()) {
				Aloha.Log.debug(
					'block-plugin',
					'Preventing drop because of defined dropzones: [ ' + dropzones + ' ]');
			}

			event.stopImmediatePropagation();

			return false;
		}

		if (!this._isAllowedOverElement(elm)) {
			this.disableInsertBeforeOrAfter(this.$overElement);
			this.enableInsertBeforeOrAfter(elm);
			this.$overElement = $elm;

			// Let the event bubble up to find a better place for a drop.
			return true;
		}

		if (!$elm.is('.aloha-editable, .aloha-table-cell-editable') || !$elm.children().length) {
			// We only do this for non-editables, because otherwise
			// the whole editable would be marked as a drop zone.
			this.disableInsertBeforeOrAfter(this.$overElement);
			this.$overElement = $elm;
			this.highlightElement(elm);
		}

		event.stopImmediatePropagation();

		return false;
	};

	/**
	 * Highlight an entirely element to be a container of the BlockElement
	 *
	 * @param {HTMLElement} elm
	 */
	DragBehavior.prototype.highlightElement = function (elm) {

		if (elm.nodeName === 'DIV' &&
				elm.parentNode.nodeName === 'TD' &&
				elm.parentNode.firstChild === elm &&
				elm.parentNode.lastChild === elm) {

			elm = elm.parentNode;
		}

		var $elm = $(elm),
			$hElm = this._getHiglightElement().appendTo('body').css({position: 'absolute'}).show();


		$hElm
			.css('zIndex', parseInt($elm.zIndex(), 10) + 1)
			.offset($elm.offset())
			.width($elm.outerWidth())
			.height($elm.outerHeight());

	};

	/**
	 * Activate the functionality to highlight the top or bottom border of an
	 * element to inform to the user that the block must be inserted before or
	 * after
	 *
	 * @param {HTMLElement} elm
	 */
	DragBehavior.prototype.enableInsertBeforeOrAfter = function (elm) {
		var $elm = $(elm),
			dragBehavior = this,
			elmTop = $elm.offset().top,
			halfHeight = $elm.outerHeight() / 2;

		if (!allowDropRegions($elm, this.$element)) {
			return;
		}

		$elm.bind('mousemove.brIBOA', function (event) {
			var top = event.pageY - elmTop;
			if (top >= halfHeight) {
				dragBehavior.insertBeforeOrAfterMode = 'AFTER';
			} else {
				dragBehavior.insertBeforeOrAfterMode = 'BEFORE';
			}
			dragBehavior.highlightEdge(this);
		});

	};

	/**
	 * Disables the InsertBeforeOrAfter functionality.
	 *
	 * @param {jQuery.<HTMLElement>} $elm
	 */
	DragBehavior.prototype.disableInsertBeforeOrAfter = function ($elm) {
		this.insertBeforeOrAfterMode = false;
		if ($elm) {
			$elm.unbind('.brIBOA');
		}
	};

	/**
	 * Highlites only a edge of the element recived, depending the value of
	 * this.insertBeforeOrAfterMode
	 *
	 * @param {HTMLElement} elm
	 */
	DragBehavior.prototype.highlightEdge = function (elm) {
		var $elm = $(elm),
			$hElm = this._getHiglightElement().css({position: 'relative', top: 'auto', left: 'auto'}).show(),
			edgeHeight = 4;

		if (this.insertBeforeOrAfterMode === 'BEFORE') {
			$hElm
				.css('zIndex', parseInt($elm.zIndex(), 10) + 1)
				.width($elm.outerWidth())
				.height(edgeHeight);
			$elm.before($hElm);
		} else {
			$hElm
				.css('zIndex', parseInt($elm.zIndex(), 10) + 1)
				.width($elm.outerWidth())
				.height(edgeHeight);
			$elm.after($hElm);
		}
	};

	/**
	 * Returns true or false depending if the element given is allowed to
	 * contain BlockElements
	 *
	 * @param {HTMLElement} elm
	 * 
	 * @return {Boolean}
	 */
	DragBehavior.prototype._isAllowedOverElement = function (elm) {
		if ($.inArray(elm.nodeName, notAllowedOverTags) < 0) {
			if (!isEditable(elm)) {
				return false;
			}

			var $elm = $(elm);
			if ($elm.parents(notAllowedDropParentsSelector.join(',')).length > 0) {
				return false;
			}

			if ($elm.is(notAllowedDropSelector.join(','))) {
				return false;
			}

			if (isNestedTable($elm, this.$element)) {
				return false;
			}

			return true;
		} else {
			return false;
		}
	};

	/**
	 * Check if the element in the user want to drop the block elements is
	 * suitable to contain the BlockElement or is suitable to insert the
	 * element before or after this
	 */
	DragBehavior.prototype.onDragStop = function () {
		// @todo check if the $overElement is a Valid element to drop the block
		if (allowDropRegions(this.$overElement, this.$element)) {
			if (this.$overElement &&
				!this._isAllowedOverElement(this.$overElement[0])) {
				this.enableInsertBeforeOrAfter(this.$overElement[0]);
				// prevent dragging the block into a position, where it should not be
				// when _isAllowedOverElement() return false, insertBeforeOrAfterMode must not be false
				if (this.insertBeforeOrAfterMode === false) {
					this.insertBeforeOrAfterMode = 'AFTER';
				}
			}

			if (this.insertBeforeOrAfterMode !== false) {
				if (this.insertBeforeOrAfterMode === 'BEFORE') {
					this.$overElement.before(this.$element);
				} else {
					this.$overElement.after(this.$element);
				}
			} else {
				this.$element.appendTo(this.$overElement);
			}

			// deactivate the current editable and activate the editable,
			// the block has been dropped into. This will do necessary initializations that
			// happen on activation of the editable
			Aloha.deactivateEditable();
			var editable = Aloha.getEditableHost(this.$element);
			if (editable) {
				editable.activate();
			}
		}

		this.disableInsertBeforeOrAfter(this.$overElement);
	};

	/**
	 * Returns a reference to the element used to highlight over elements,
	 * if not exist is created, this element is shared for all instances of
	 * DragBehavior
	 * 
	 * @return {jQuery.<HTMLElement>}
	 */
	DragBehavior.prototype._getHiglightElement = function () {
		if (undefined === this.$hElm) {
			if ($('#aloha-highlight-element').length === 0) {

				this.$hElm = $('<div>')
					.attr('id', 'aloha-highlight-element')
					.appendTo('body')
					.css({
						position: 'absolute',
						opacity: 0.5,
						background: 'blue'
					})
					.hide();
			} else {
				this.$hElm = $('#aloha-highlight-element');
			}
		}

		return this.$hElm;
	};

});

define('block/nls/i18n',{
	"root":  {
		"button.toggledragdrop.tooltip": "Toggle Drag & Drop"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* block-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * @name block
 * @namespace Block plugin
 */
define('block/block-plugin',[
	'aloha',
	'aloha/plugin',
	'aloha/ephemera',
	'jquery',
	'aloha/contenthandlermanager',
	'block/blockmanager',
	'block/sidebarattributeeditor',
	'block/block',
	'block/editormanager',
	'block/blockcontenthandler',
	'block/editor',
	'block/dragbehavior',
	'ui/ui',
	'ui/toggleButton',
	'i18n!block/nls/i18n',
	'i18n!aloha/nls/i18n',
	'jqueryui'
], function (
	Aloha,
	Plugin,
	Ephemera,
	jQuery,
	ContentHandlerManager, 
	BlockManager,
	SidebarAttributeEditor,
	block,
	EditorManager,
	BlockContentHandler,
	editor,
	dragBehavior,
	Ui,
	ToggleButton, 
	i18n,
	i18nCore
) {
	

	var defaultRootTags = ['div', 'span'];

	/**
	 * Register the 'block' plugin
	 */
	var BlockPlugin = Plugin.create('block', {
		
		/**
		 * default button configuration
		 */
		config: [], 

		settings: {},

		init: function () {
			var that = this;

			// set default root tags
			if (!this.settings.rootTags) {
				this.settings.rootTags = defaultRootTags;
			}

			Ephemera.classes('aloha-block-active');
			Ephemera.classes('aloha-block-highlighted');

			// Register default block types			
			BlockManager.registerBlockType('DebugBlock', block.DebugBlock);
			BlockManager.registerBlockType('DefaultBlock', block.DefaultBlock);
			BlockManager.registerBlockType('EmptyBlock', block.EmptyBlock);

			// Register default editors
			EditorManager.register('string', editor.StringEditor);
			EditorManager.register('number', editor.NumberEditor);
			EditorManager.register('url', editor.UrlEditor);
			EditorManager.register('email', editor.EmailEditor);
			EditorManager.register('select', editor.SelectEditor);
			EditorManager.register('radio', editor.RadioButtonEditor);
			EditorManager.register('button', editor.ButtonEditor);

			// register content handler for block plugin
			ContentHandlerManager.register('block', BlockContentHandler);

			BlockManager.setDragDropState(that.isDragDropEnabled());
			BlockManager.registerEventHandlers();
			BlockManager.initializeBlockLevelDragDrop();

			Aloha.bind('aloha-plugins-loaded', function () {
				that._createBlocks();
				if (false !== that.settings['sidebarAttributeEditor']) {
					SidebarAttributeEditor.init();
				}
			});

			// create the toolbar buttons
			this.createButtons();

			// set the dropzones for the initialized editable
			Aloha.bind('aloha-editable-created', function (e, editable) {
				that.setDropzones(editable.obj);

				// Because we don't want non-contentEditable regions inside of
				// editables to be resizable through the browser-built
				// interfaces. IE 11 requires this additional muzzling to be
				// done on the body in order to thoroughly prevent
				// objectResizing.
				// @see Block._disableUglyInternetExplorerDragHandles
				// @see https://connect.microsoft.com/IE/feedback/details/742593/please-respect-execcommand-enableobjectresizing-in-contenteditable-elements
				editable.obj.on('mscontrolselect', function (event) {
					event.preventDefault();
				});
			});

			// apply specific configuration if an editable has been activated
			Aloha.bind('aloha-editable-activated', function (e, params) {
				that.applyButtonConfig(params.editable);
			});

		},

		/**
		 * Applies a configuration specific for an editable buttons not
		 * available in this configuration are hidden
		 *
		 * @param {!Object}
		 *        The editable which has become active and for which to
		 *        apply the button config.
		 */
		applyButtonConfig: function (editable) {
			if (this._isDragdropToggleEnabled(editable)) {
				this._toggleDragDropButton.show();
				this._toggleDragDropButton.setState(this._getDragdropState(editable));
			} else {
				this._toggleDragDropButton.hide();
			}
		},

		/**
		 * Test whether toggleability of the dragndrop feature is turned on.
		 *
		 * @param {!Aloha.Editable} editable
		 *        The editable for which toggling may be turned on, if
		 *        toggling of the dragndrop feature is configured
		 *        per-editable.
		 *        Only used if !toggleDragdropGlobal.
		 * @return {boolean}
		 *        Whether the toggleability of the dragndrop feature is
		 *        turned on either globally, or for the given editable.
		 */
		_isDragdropToggleEnabled: function (editable) {
			var config = this.getEditableConfig(editable.obj);

			// toggle drag & drop option can be set as
			// config: {'toggeleDragdrop': true} or
			// config: ['toggleDragdrop']
			var toggleDragdropConfigured = function () {
				return (config[0] === "toggleDragdrop") ||
								config.toggleDragdrop == true	||
								config.toggleDragdrop == 'true'	||
								config.toggleDragdrop == 1		||
								config.toggleDragdrop == '1';
			};

			var toggleGloballyOrPerEditable =
				((this.settings && this.settings.config && this.settings.config.toggleDragdropGlobal) ||
				 toggleDragdropConfigured());
			return toggleGloballyOrPerEditable && this.isDragDropEnabled();
		},

		/**
		 * Gets the current toggle state of the dragndrop feature.
		 *
		 * @param {!Aloha.Editable} editable
		 *        The editable that is used to track the per-editable
		 *        dragdrop toggled state.
		 *        Only used if !toggleDragdropGlobal.
		 * @return {boolean}
		 *        Whether the dragdrop feature is turned on either
		 *        globally, or for the given editable.
		 */
		_getDragdropState: function (editable) {
			return ((this.settings && this.settings.config && this.settings.config.toggleDragdropGlobal) ?
					BlockManager.getDragDropState() :
					editable.obj.data("block-dragdrop"));
		},

		/**
		 * Toggles the dragndrop state.
		 *
		 * If toggleDragdropGlobal is turned on, will toggle the
		 * dragndrop state on each existing editable.
		 *
		 * If toggleDragdropGlobal is not turned on, will toggle the
		 * dragndrop state only for the given editable.
		 *
		 * @param {!Aloha.Editable} editable
		 *        The editable that is used to track the per-editable
		 *        dragdrop toggled state.
		 *        Only used if !toggleDragdropGlobal.
		 */
		_toggleDragdropState: function (editable) {
			if (this.settings && this.settings.config && this.settings.config.toggleDragdropGlobal) {
				var dragdropState = !BlockManager.getDragDropState();
				// Setting the dragdrop state in the block manager
				// ensures that newly created editables will receive the
				// correct "block-dragdrop" data attribute.
				BlockManager.setDragDropState(dragdropState);
				for (var i = 0; i < Aloha.editables.length; i++) {
					var editable = Aloha.editables[i];
					this._setDragDropStateForEditable(editable.obj, dragdropState);
				}
			} else {
				var toggleState = !editable.obj.data("block-dragdrop");
				this._setDragDropStateForEditable(editable.obj, toggleState);
			}
		},

		createButtons: function () {
			var that = this;

			this._toggleDragDropButton = Ui.adopt("toggleDragDrop", ToggleButton, {
				tooltip: i18n.t('button.toggledragdrop.tooltip'),
				icon: 'aloha-icon aloha-icon-toggledragdrop',
				scope: 'Aloha.continuoustext',
				click: function () {
					that._toggleDragdropState(Aloha.activeEditable);
				}
			});
		},

		/**
		 * Set available dropzones for the given editable.
		 * @return void 
		 */
		setDropzones: function (editable) {
			var that = this;
			var config = that.getEditableConfig(editable);
			var dropzones = (config && config.dropzones) || that.settings.dropzones;

			if (dropzones) {
				editable.data('block-dropzones', dropzones);	
			} else {
				// if dropzones are undefined all editables should be dropzones
				editable.data('block-dropzones', [".aloha-editable"]);	
			}
		},

		/**
		 * Checks whether drag & drop is enabled for blocks.
		 * @return boolean 
		 */
		isDragDropEnabled: function () {
			if (this.settings && typeof this.settings.dragdrop !== "undefined") {
				// Normalize config
				return (
					this.settings.dragdrop === true   ||
					this.settings.dragdrop === 'true' ||
					this.settings.dragdrop === 1      ||
					this.settings.dragdrop === '1'
				);
			} else {
				return true; // by default dragdrop is activated 
			}
		},

		/**
		 * Create blocks from default settings
		 */
		_createBlocks: function () {
			if (!this.settings.defaults) {
				this.settings.defaults = {};
			}
			jQuery.each(this.settings.defaults, function (selector, instanceDefaults) {
				jQuery(selector).alohaBlock(instanceDefaults);
			});
		},

		/**
		 * Set the drag & drop state for the given editable.
		 */
		_setDragDropStateForEditable: function ($editable, state) {
			$editable.data("block-dragdrop", state);

			if ($editable.hasClass("ui-sortable")) {
				$editable.sortable("option", "disabled", !state);	
			}

			$editable.find(".aloha-block.ui-draggable").each(function () {
				jQuery(this).draggable("option", "disabled", !state);
			});

			$editable.find(".aloha-block-handle").each(function () {
				if (state) {
					jQuery(this).addClass("aloha-block-draghandle");
				} else {
					jQuery(this).removeClass("aloha-block-draghandle");
				}
			});
		},

		/**
		 * Make the given jQuery object (representing an editable) clean for saving
		 * Remove classes marking draggable aloha blocks
		 */
		makeClean: function (obj) {
			// these classes are not made ephemeral, because this would remove them from every DOM node
			// we only want to remove those classes from root elements of blocks.
			obj.find(".aloha-block").removeClass('ui-draggable ui-draggable-disabled');
		}
	});

	/**
	 * See (http://jquery.com/).
	 * @name jQuery.fn
	 * @class
	 * See the jQuery Library  (http://jquery.com/) for full details.  This just
	 * documents the function and classes that are added to jQuery by this plug-in.
	 */

	/**
	 * Create Aloha blocks from the matched elements
	 * @api
	 * @param {Object} instanceDefaults
	 */
	jQuery.fn.alohaBlock = function (instanceDefaults) {
		instanceDefaults = instanceDefaults || {};
		jQuery(this).each(function (index, element) {
			BlockManager._blockify(element, instanceDefaults);
		});

		// Chain
		return jQuery(this);
	};

	/**
	 * Un"block" the matched elements. If matched elements were made blocks
	 * (by calling alohaBlock() on them), they will no longer be blocks.
	 * 
	 * @api
	 */
	jQuery.fn.mahaloBlock = function () {
		jQuery(this).each(function (index, element) {
			BlockManager._unblockify(element);
		});
	};

	// jQuery.fn.mahaloBlock = TODO
	return BlockPlugin;
});

/* align-table-utils.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('align/align-table-utils',[
	'util/arrays',
	'util/dom',
	'jquery'
], function(
	Arrays,
	DomLegacy,
	jQuery
) {
	

	/**
	 * Gets cells selected in the table.
	 *
	 * @param {Element} tableElement
	 * @return {Array}
	 */
	function getCellsInSelection(tableElement) {
		return Arrays.coerce(jQuery(tableElement).find('.aloha-cell-selected'));
	}

	/**
	 * Checks if `tableElement` has any cells selected.
	 * @param {Element} tableElement
	 * @return {boolean}
	 */
	function hasCellSelection(tableElement) {
		return jQuery(tableElement).find('.aloha-cell-selected').length !== 0;
	}

	/**
	 * Checks if node is an editing host, ignoring table cells editing host.
	 * @param {Node} node
	 * @returns {boolean}
	 */
	function isEditingHostIgnoreEditableTableCells(node) {
		return DomLegacy.isEditingHost(node)
			&& (node.className.match("aloha-table-cell-editable") == null);
	}

	/**
	 * Gets the cell node which contains `node` if exists. Otherwise null.
	 * @param {Node} node
	 * @return {node|null}
	 */
	function getParentCellElement(node) {
		while (node && !isEditingHostIgnoreEditableTableCells(node)) {
			if (node.nodeName === 'TD' || node.nodeName === 'TH') {
				return node;
			}
			node = node.parentNode;
		}

		return null;
	}

	/**
	 * Gets the parent Table if exists. Otherwise null.
	 * @param {Node} node
	 * @return {node|null}
	 */
	function getTable(node) {
		while (node) {
			if (node.nodeName === 'TABLE') {
				return node;
			}
			node = node.parentNode;
		}

		return null;
	}

	/**
	 * Checks if `node` is inside a table.
	 * @param {Node} node
	 * @return {boolean}
	 */
	function isInsideTable (node) {
		return getParentCellElement(node) != null;
	}

	/**
	 * Get selected cells inside a Table.
	 * @param {Node} node
	 * @param {Array.<Element> elements
	 * @returns {Array.<Element>}
	 */
	function getSelectedTableCells(node, elements) {
		var cellElement = getParentCellElement(node);
		var table;

		table = getTable(cellElement);

		if (hasCellSelection(table)) {
			return getCellsInSelection(table);
		}

		if (Arrays.isEmpty(elements)) {
			// If there is nothing selected, we add the cell.
			return [cellElement];
		}

		return elements;
	}

	return {
		isInsideTable: isInsideTable,
		getSelectedTableCells: getSelectedTableCells
	};
});

define('align/nls/i18n',{
	"root":  {
		"button.alignright.tooltip": "Align to the right",
		"button.alignleft.tooltip": "Align to the left",
		"button.aligncenter.tooltip": "Center",
		"button.alignjustify.tooltip": "Justify",
		"button.aligntop.tooltip": "Align to the top",
		"button.alignmiddle.tooltip": "Align to the middle",
		"button.alignbottom.tooltip": "Align to the bottom"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* align-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('align/align-plugin',[
	'aloha',
	'aloha/plugin',
	'align/align-table-utils',
	'util/arrays',
	'util/html',
	'util/dom',
	'ui/ui',
	'ui/toggleButton',
	'i18n!align/nls/i18n',
	'i18n!aloha/nls/i18n',
	'jquery',
	'PubSub'
], function (
	Aloha,
	Plugin,
	AlignTableUtils,
	Arrays,
	Html,
	DomLegacy,
	Ui,
	ToggleButton,
	i18n,
	i18nCore,
	jQuery,
	PubSub
) {
	

	/**
	 * Gets block elements inside `range`.
	 * @return {Array}
	 */
	function getCurrentSelectedBlockElements() {
		var range = Aloha.Selection.getRangeObject();
		var selection = range.getRangeTree(),
			cac = range.getCommonAncestorContainer(),
			elements = [];
		var cells, i, len;

		jQuery.each(selection, function () {
			var node = this.domobj;
			if (this.type === 'none' || !node) {
				return;
			}
			if (Html.isBlock(node)) {
				elements.push(node);
				return;
			}
			// Because the align-text property needs to be set on a block-level
			// element in order for it to have visual effect
			while (node && !Html.isBlock(node)) {
				if ((Html.isBlock(cac) && cac === node.parentNode) ||
					DomLegacy.isEditingHost(node.parentNode)) {
					break;
				}
				node = node.parentNode;
			}
			if (Html.isBlock(node)) {
				elements.push(node);
			}
		});

		if (elements.length === 0 && selection.length > 0 && Html.isBlock(cac) && !DomLegacy.isEditingHost(cac)) {
			elements.push(cac);
		}

		if (AlignTableUtils.isInsideTable(cac)) {
			elements = AlignTableUtils.getSelectedTableCells(cac, elements);
		}
		return elements;
	}

	/**
	 * register the plugin with unique name
	 */
	return Plugin.create('align', {
		_constructor: function () {
			this._super('align');
		},
		/**
		 * Configuration (available align options)
		 */
		config: {
			alignment: ['right', 'left', 'center', 'justify', 'top', 'middle', 'bottom']
		},

		/**
		 * Alignment wanted by the user
		 */
		alignment: '',

		verticalAlignment: '',

		/**
		 * Alignment of the selection before modification
		 */
		lastAlignment: '',

		lastVerticalAlignment: '',

		/**
		 * Initialize the plugin and set initialize flag on true
		 */
		init: function () {
			this.createButtons();

			var that = this;

			// apply specific configuration if an editable has been activated
			Aloha.bind('aloha-editable-activated', function (e, params) {
				that.applyButtonConfig(params.editable.obj);
			});

			PubSub.sub('aloha.selection.context-change', function (message) {
				var rangeObject = message.range;

				if (Aloha.activeEditable) {
					that.buttonPressed(rangeObject);
				}
			});
		},

		buttonPressed: function (rangeObject) {
			this.horizontalButtonPressed(rangeObject);
			this.verticalButtonPressed(rangeObject);
		},

		horizontalButtonPressed: function (rangeObject) {
			var that = this;

			this.lastAlignment = this.alignment;

			//reset current alignment
			this.alignment = '';

			rangeObject.findMarkup(function () {
				// try to find explicitly defined text-align style property
				if (this.style.textAlign !== "") {
					that.alignment = this.style.textAlign;
					return true;
				}

				that.alignment = jQuery(this).css('text-align');
			}, Aloha.activeEditable.obj);

			// set horizontal button states
			if (this.alignment != this.lastAlignment) {
				// reset all button states -- it can only be one active...
				this._alignRightButton.setState(false);
				this._alignLeftButton.setState(false);
				this._alignCenterButton.setState(false);
				this._alignJustifyButton.setState(false);

				switch (this.alignment) {
				case 'right':
					this._alignRightButton.setState(true);
					break;
				case 'center':
					this._alignCenterButton.setState(true);
					break;
				case 'justify':
					this._alignJustifyButton.setState(true);
					break;
				default:
					this._alignLeftButton.setState(true);
					this.alignment = 'left';
					break;
				}
			}
		},

		verticalButtonPressed: function (rangeObject) {
			var that = this;

			this.lastVerticalAlignment = this.verticalAlignment;

			//reset current alignment
			this.verticalAlignment = '';

			rangeObject.findMarkup(function () {
				// try to find explicitly defined vertical-align style property
				if (this.style.verticalAlign !== "") {
					that.verticalAlignment = this.style.verticalAlign;
					return true;
				}

				that.verticalAlignment = jQuery(this).css('vertical-align');
			}, Aloha.activeEditable.obj);

			// set vertical button states
			if (this.verticalAlignment != this.lastVerticalAlignment) {
				// reset all button states -- it can only be one active...
				this._alignTopButton.setState(false);
				this._alignMiddleButton.setState(false);
				this._alignBottomButton.setState(false);

				switch (this.verticalAlignment) {
				case 'top':
					this._alignTopButton.setState(true);
					break;
				case 'middle':
					this._alignMiddleButton.setState(true);
					break;
				case 'bottom':
					this._alignBottomButton.setState(true);
					break;
				default:
					this._alignTopButton.setState(true);
					this.verticalAlignment = 'top';
					break;
				}
			}
		},

		/**
		 * applies a configuration specific for an editable
		 * buttons not available in this configuration are hidden
		 * @param {Object} id of the activated editable
		 * @return void
		 */
		applyButtonConfig: function (obj) {
			var config = this.getEditableConfig(obj);

			if ( config && config.alignment && !this.settings.alignment ) {
				config = config;
			} else if ( config[0] && config[0].alignment) {
				config = config[0];
			} else if (this.settings.alignment) {
				config.alignment = this.settings.alignment;
			}

			if (typeof config.alignment === 'undefined') {
				config = this.config;
			}

			if (jQuery.inArray('right', config.alignment) != -1) {
				this._alignRightButton.show(true);
			} else {
				this._alignRightButton.show(false);
			}

			if (jQuery.inArray('left', config.alignment) != -1) {
				this._alignLeftButton.show(true);
			} else {
				this._alignLeftButton.show(false);
			}

			if (jQuery.inArray('center', config.alignment) != -1) {
				this._alignCenterButton.show(true);
			} else {
				this._alignCenterButton.show(false);
			}

			if (jQuery.inArray('justify', config.alignment) != -1) {
				this._alignJustifyButton.show(true);
			} else {
				this._alignJustifyButton.show(false);
			}

			if (jQuery.inArray('top', config.alignment) != -1) {
				this._alignTopButton.show(true);
			} else {
				this._alignTopButton.show(false);
			}

			if (jQuery.inArray('middle', config.alignment) != -1) {
				this._alignMiddleButton.show(true);
			} else {
				this._alignMiddleButton.show(false);
			}

			if (jQuery.inArray('bottom', config.alignment) != -1) {
				this._alignBottomButton.show(true);
			} else {
				this._alignBottomButton.show(false);
			}
		},

		createButtons: function () {
			var that = this;

			this._alignLeftButton = Ui.adopt("alignLeft", ToggleButton, {
				tooltip: i18n.t('button.alignleft.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-left',
				scope: 'Aloha.continuoustext',
				click: function () {
					that.align('left');
				}
			});

			this._alignCenterButton = Ui.adopt("alignCenter", ToggleButton, {
				tooltip: i18n.t('button.aligncenter.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-center',
				scope: 'Aloha.continuoustext',
				click: function () {
					that.align('center');
				}
			});

			this._alignRightButton = Ui.adopt("alignRight", ToggleButton, {
				tooltip: i18n.t('button.alignright.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-right',
				scope: 'Aloha.continuoustext',
				click: function () {
					that.align('right');
				}
			});

			this._alignJustifyButton = Ui.adopt("alignJustify", ToggleButton, {
				tooltip: i18n.t('button.alignjustify.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-justify',
				scope: 'Aloha.continuoustext',
				click: function () {
					that.align('justify'); 
				}
			});

			this._alignTopButton = Ui.adopt("alignTop", ToggleButton, {
				tooltip: i18n.t('button.aligntop.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-top',
				scope: 'table.cell',
				click: function () {
					that.verticalAlign('top'); 
				}
			});

			this._alignMiddleButton = Ui.adopt("alignMiddle", ToggleButton, {
				tooltip: i18n.t('button.alignmiddle.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-middle',
				scope: 'table.cell',
				click: function () {
					that.verticalAlign('middle');
				}
			});

			this._alignBottomButton = Ui.adopt("alignBottom", ToggleButton, {
				tooltip: i18n.t('button.alignbottom.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-bottom',
				scope: 'table.cell',
				click: function () {
					that.verticalAlign('bottom');
				}
			});

		},

		verticalAlign: function (tempAlignment) {

			var that = this;
			var range = Aloha.Selection.getRangeObject();

			this.lastVerticalAlignment = this.verticalAlignment;
			this.verticalAlignment = tempAlignment;

			// check if the selection range is inside a table
			var selectedCells = this.getSelectedCells(range);

			if (selectedCells) {
				that.toggleAlign(selectedCells, 'vertical-align');
			}

			// reset previous button states
			if (this.verticalAlignment != this.lastVerticalAlignment) {
				switch (this.lastVerticalAlignment) {
				case 'top':
					this._alignTopButton.setState(false);
					break;

				case 'middle':
					this._alignMiddleButton.setState(false);
					break;

				case 'bottom':
					this._alignBottomButton.setState(false);
					break;
				}
			}

			// select the (possibly modified) range
			range.select();

		},

		/**
		 * Sets or removes the alignment on the selected range.
		 *
		 * @param {string} alignment
		 */
		align: function (alignment) {
			this.lastAlignment = this.alignment;
			this.alignment = alignment;

			var elements = getCurrentSelectedBlockElements();
			this.toggleAlign(elements);

			// reset previous button states
			if (this.alignment !== this.lastAlignment) {
				switch (this.lastAlignment) {
				case 'right':
					this._alignRightButton.setState(false);
					break;
				case 'left':
					this._alignLeftButton.setState(false);
					break;
				case 'center':
					this._alignCenterButton.setState(false);
					break;
				case 'justify':
					this._alignJustifyButton.setState(false);
					break;
				}
			}
		},

		getSelectedCells: function (range) {

			var selectedCell;

			var activeTable = range.findMarkup(function () {
				if (jQuery(this).is('td,th')) {
					selectedCell = this;
				}
				return jQuery(this).is('table.aloha-table');
			}, Aloha.activeEditable.obj);

			var selectedCells = jQuery(activeTable).find('.aloha-cell-selected');

			return selectedCells.length ? selectedCells : selectedCell;

		},

		/**
		 * Toggle the align property of given DOM object(s)
		 */
		toggleAlign: function (domObj, property) {

			var that = this;

			property = property || 'text-align';

			var newAlignment = (property === 'vertical-align') ? that.verticalAlignment : that.alignment;

			var shouldRemoveAlignment = true;

			jQuery(domObj).each(function () {

				var currentAlignment = jQuery(this).css(property);

				if (currentAlignment != newAlignment) {
					shouldRemoveAlignment = false;
					return false;
				}

			});


			jQuery(domObj).each(function () {

				var currentAlignment = jQuery(this).css(property);

				if ((currentAlignment == newAlignment) && shouldRemoveAlignment) {
					jQuery(this).css(property, '');
				} else {
					jQuery(this).css(property, newAlignment);
				}

			});

		}

	});

});

/* paste-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 *
 * @overview:
 * The paste plugin intercepts all browser paste events that target aloha
 * editables, and redirects the events into a hidden div.  Once pasting is done
 * into this div, its contents will be processed by registered content handlers
 * before being copied into the active editable, at the current range.
 */
define('paste/paste-plugin',[
	'jquery',
	'aloha/core',
	'aloha/plugin',
	'aloha/command',
	'contenthandler/contenthandler-utils',
	'aloha/console',
	'aloha/copypaste',
	'aloha/contenthandlermanager',
	'util/browser'
], function (
	$,
	Aloha,
	Plugin,
	Commands,
	ContentHandlerUtils,
	Console,
	CopyPaste,
	ContentHandlerManager,
	Browser
) {
	

	/**
	 * Reference to global window object, for quicker lookup.
	 *
	 * @type {jQuery.<window>}
	 * @const
	 */
	var $WINDOW = $(window);

	/**
	 * Whether or not the user-agent is Internet Explorer.
	 *
	 * @type {boolean}
	 * @const
	 */
	var IS_IE = !!Aloha.browser.msie;

	/**
	 * Matches as string consisting of a single white space character.
	 *
	 * '%A0' is used instead of '&nbsp;' because it seems that IE transforms
	 * non-breaking spaces into atomic tokens.
	 *
	 * @type {RegExp}
	 * @const
	 */
	var PROPPING_SPACE = /^(\s|%A0)$/;

	/**
	 * An invisible editable element used to intercept incoming pasted content
	 * so that it can be processed before being placed into real editables.
	 *
	 * In order to hide the editable div we use clip:rect for WebKit (Chrome,
	 * Safari) and Trident (IE), and width/height for Gecko (FF).
	 *
	 * @type {jQuery.<HTMLElement>}
	 * @const
	 */
	var $CLIPBOARD = $('<div style="position:absolute; ' +
	                   'clip:rect(0px,0px,0px,0px); ' +
	                   'width:1px; height:1px;"></div>').contentEditable(true);

	/**
	 * Stored range, use to accomplish IE hack.
	 *
	 * @type {WrappedRange}
	 */
	var ieRangeBeforePaste = null;

	/**
	 * The window's scroll position at the moment just before pasting is done
	 * (beforepaste and paste events).
	 *
	 * @type {object}
	 * @property {Number} x
	 * @property {Number} y
	 **/
	var scrollPositionBeforePaste = {
		x: 0,
		y: 0
	};

	/**
	 * Set the selection to the given range and focus on the editable in which
	 * the selection is in (if any).
	 *
	 * This function is used to restore the selection to what it was before
	 * calling redirectPaste() at the offset of the pasting process.
	 *
	 * @param {WrappedRange} range The range to restore.
	 */
	function restoreSelection(range) {
		var editable = CopyPaste.getEditableAt(range);

		// setting the focus is needed for mozilla to have a working rangeObject.select()
		if (editable && Aloha.browser.mozilla && document.activeElement !== editable.obj[0]) {
			editable.obj.focus();
		}
		CopyPaste.setSelectionAt(range);
		window.scrollTo(
			scrollPositionBeforePaste.x,
			scrollPositionBeforePaste.y
		);
	}

	/**
	 * Redirects a paste event from the given range into a specified target
	 * element.
	 *
	 * This function is used to cause paste events that are targeting to
	 * editables to instead land in an invisible clipboard div that serves as a
	 * staging area to handle the incoming content before actually placing it
	 * into the intended editable.
	 *
	 * @param {WrappedRange} range The range at the time that the paste event
	 *                             was initiated.
	 * @param {jQuery.<HTMLElement>} $target A jQuery object containing the DOM
	 *                                       element to which the paste event
	 *                                       is to be directed to.
	 */
	function redirect(range, $target) {
		var width = 200;
		// Because moving the target element to the current scroll position
		// avoids jittering the viewport when the pasted content moves between
		// where the range is and target.
		$target.css({
			top: $WINDOW.scrollTop(),
			left: $WINDOW.scrollLeft() - width,
			width: width,
			overflow: 'hidden'
		}).contents().remove();

		var from = CopyPaste.getEditableAt(range);
		if (from) {
			from.obj.blur();
		}

		// Because the selection should end up inside the target element.
		CopyPaste.setSelectionAt({
			startContainer: $target[0],
			endContainer: $target[0],
			startOffset: 0,
			endOffset: 0
		});
		$target.focus();
	}

	/**
	 * Detects a situation where paste is about to be done into a selection
	 * beginning inside markup that looks exactly like this:
	 *
	 * '<p> </p>'
	 *
	 * or roughly like this:
	 *
	 * '<p><br/></p>'
	 *
	 * Both markups denote a "propped" paragraph.  A propped paragraph is one
	 * which contains content that has been placed in it for the sole purpose
	 * of forcing the layout engine to render the node visibly.  HTML5 standard
	 * conformance requires that empty block elements like <p> be rendered
	 * invisibly, and comformant browsers like WebKit would place <br> nodes
	 * inside content-editable paragraphs so that they can be visible for
	 * editing.
	 *
	 * IE is _not_ standard comformant however, because it renders empty <p>
	 * with a line-height of 1.  Adding a <br> elements inside it results in
	 * the <p> appearing with 2 lines.
	 *
	 * If we detect this situation, the white space is removed so that after
	 * pasting a new paragraph into the paragraph, it will not be split leaving
	 * an empty paragraph on top of the pasted content.  Therefore when working
	 * in IE, a space is placed inside an empty paragraph rather than a <br>.
	 * Hence markup like '<p> </p>'.
	 *
	 * @param {WrappedRange} range
	 * @return {boolean} True if range starts in propping node.
	 */
	function rangeStartsAtProppedParagraph(range) {
		var start = range.startContainer;
		if (1 === start.nodeType) {
			return ('p' === start.nodeName.toLowerCase() &&
					ContentHandlerUtils.isProppedParagraph(start.outerHTML));
		}
		return (3 === start.nodeType &&
				'p' === start.parentNode.nodeName.toLowerCase() &&
					1 === start.parentNode.childNodes.length &&
						PROPPING_SPACE.test(window.escape(start.data)));
	}

	/**
	 * Prepare the nodes around where pasted content is to land.
	 *
	 * @param {WrappedRange} range
	 */
	function prepRangeForPaste(range) {
		if (rangeStartsAtProppedParagraph(range)) {
			if (3 === range.startContainer.nodeType) {
				range.startContainer.data = '';
			} else {
				range.startContainer.innerHTML = ' ';
			}
			range.startOffset = 0;

			// Because of situations like <p>[ ]</p> or <p>[<br/>]</p>
			if (range.endContainer === range.startContainer) {
				range.endOffset = 0;
			}
		}
	}

	/**
	 * Delete the first match in a string
	 *
	 * @param {String} string String to modify
	 * @param {String} match Match string must be replaced
	 * @returns {string} Original string with the first match replaced.
	 */
	function deleteFirstMatch(string, match) {
		return string.replace(match, '');
	}

	/**
	 * Delete the first Header tag if exists.
	 *
	 * @param htmlString
	 * @returns {XML|string}
	 */
	function deleteFirstHeaderTag(htmlString) {
		var matchFirstHeaderTag = /^<h\d+.*?>/i.exec(htmlString),
		    startHeaderTag,
		    endHeaderTag;

		if (matchFirstHeaderTag === null) {
			return htmlString;
		}

		startHeaderTag = matchFirstHeaderTag[0];
		endHeaderTag = '</' + startHeaderTag.substr(1);

		return deleteFirstMatch(
			deleteFirstMatch(htmlString, startHeaderTag),
			endHeaderTag
		);
	}

	/**
	 * Checks if browser and document mode are 9 or above versions.
	 * @param  {Document} doc
	 * @return {boolean}
	 */
	function isIEorDocModeGreater9(doc) {
		return Browser.ie && doc.documentMode >= 9;
	}

	/**
	 * Gets the pasted content and inserts them into the current active
	 * editable.
	 *
	 * @param {jQuery.<HTMLElement>} $clipboard A jQuery object containing an
	 *                                          element holding the copied
	 *                                          content that will be placed at
	 *                                          the given range.
	 * @param {WrappedRange} range The range at which to place the contents
	 *                             from $clipboard.
	 *
	 * @param {function=} callback An optional callback function to call after
	 *                             pasting is completed.
	 */
	function paste($clipboard, range, callback) {
		if (!range) {
			return;
		}

		var content = deleteFirstHeaderTag($clipboard.html());
		var handler = ContentHandlerManager.get('formatless');

		content = handler ? handler.handleContent(content) : content;

		// Because IE inserts an insidious nbsp into the content during pasting
		// that needs to be removed.  Leaving it would otherwise result in an
		// empty paragraph being created right before the pasted content when
		// the pasted content is a paragraph.
		if (IS_IE && /^&nbsp;/.test(content)) {
			content = content.substring(6);
		}

		restoreSelection(range);
		prepRangeForPaste(range);

		if (Aloha.queryCommandSupported('insertHTML')) {
			Aloha.execCommand('insertHTML', false, content);
		} else {
			Console.error(
				'Common.Paste',
				'Command "insertHTML" not available. Enable the plugin "common/commands".'
			);
		}

		$clipboard.contents().remove();

		if (typeof callback === 'function') {
			callback();
		}
	}

	/**
	 * Handles the "paste" event initiating from the $CLIPBOARD element.
	 *
	 * @param {jQuery.Event} $event Event at paste.
	 * @param {WrappedRange} range The range to where to direct the contents
	 *                             of the $CLIPBOARD element.
	 * @param {function=} onInsert Optional callback to be invoked after pasting
	 *                            is completed.
	 */
	function onPaste($event, range, onInsert) {
		// Because we do not want the smartContentChange method to process this
		// event if the metaKey property had been set.
		$event.metaKey = null;
		$event.stopPropagation();

		// Because yeiling here allows for a small execution window to ensure
		// that the pasted content has been inserted into the paste div before
		// we attempt to retrieve it.
		window.setTimeout(function () {
			paste($CLIPBOARD, range, onInsert);
			Aloha.activeEditable.smartContentChange($event);
		}, 10);
	}

	/**
	 * Prepare each editable that is created to handle its paste events via the
	 * invisible paste div.
	 *
	 * Bind appropriate events handlers to the given editable element to be
	 * able to intercept paste events target tot it.
	 *
	 *  TODO: Move to paste command?
	 *  http://support.mozilla.com/en-US/kb/Granting%20JavaScript%20access%20to%20the%20clipboard
	 *  https://code.google.com/p/zeroclipboard/
	 *
	 * @param {jQuery.<HTMLElement>} $editable jQuery object containing an
	 *                                         editable DOM element.
	 */
	function prepare($editable) {
		// Clipboard in IE can no be used, because it does not return HTML content, just text
		// (http://msdn.microsoft.com/en-us/library/ie/ms536436(v=vs.85).aspx).
		// We relay on range.execCommand('paste') for the paste, but for IE9 and above the pasted content
		// is treated differently (it replaces '\n' by '<br>').
		var doc = $editable[0].ownerDocument;
		if (isIEorDocModeGreater9(doc)) {
			$editable.bind('beforepaste', function ($event) {
				if ($event.target.nodeName === 'INPUT' ||
						$event.target.nodeName === 'TEXTAREA') {
					// We have to let the browser handle most events concerning
					// text input telements.
					return;
				}

				scrollPositionBeforePaste.x = window.scrollX ||
					document.documentElement.scrollLeft;
				scrollPositionBeforePaste.y = window.scrollY ||
					document.documentElement.scrollTop;

				ieRangeBeforePaste = CopyPaste.getRange();
				redirect(ieRangeBeforePaste, $CLIPBOARD);
				$event.stopPropagation();
			});
		} else {
			$editable.bind('paste', function ($event) {
				if ($event.target.nodeName === 'INPUT' ||
						$event.target.nodeName === 'TEXTAREA') {
					return;
				}

				scrollPositionBeforePaste.x = window.scrollX ||
					document.documentElement.scrollLeft;
				scrollPositionBeforePaste.y = window.scrollY ||
					document.documentElement.scrollTop;

				var range = CopyPaste.getRange();
				redirect(range, $CLIPBOARD);
				if (IS_IE) {
					$event.preventDefault();
					var tmpRange = document.selection.createRange();
					tmpRange.execCommand('paste');
				}
				onPaste($event, range);
			});
		}
	}

	return Plugin.create('paste', {

		settings: {},

		init: function () {
			$('body').append($CLIPBOARD);

			Aloha.bind('aloha-editable-created', function ($event, editable) {
				prepare(editable.obj);
			});

			if (isIEorDocModeGreater9($CLIPBOARD[0].ownerDocument)) {
				// Bind a handler to the paste event of the pasteDiv to get the
				// pasted content (but do this only once, not for every editable)
				$CLIPBOARD.bind('paste', function ($event) {
					onPaste($event, ieRangeBeforePaste, function () {
						ieRangeBeforePaste = null;
					});
				});
			}
		},

		/**
		 * Register the given paste handler
		 * @deprecated
		 * @param pasteHandler paste handler to be registered
		 */
		register: function (pasteHandler) {
			Console.deprecated('Plugins.Paste', 'register() for pasteHandler' +
			                                    ' is deprecated.  Use the ' +
			                                    'ContentHandler Plugin ' +
			                                    'instead.');
		}
	});
});

/* autoparagraph-plugin.js is part of the Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2014 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * License http://aloha-editor.org/license.php
 */
/**
 * @name autoparagraph
 * @namespace Autoparagraph plugin
 */
define('autoparagraph/autoparagraph-plugin',[
	'jquery',
	'PubSub',
	'aloha/plugin',
	'aloha/core',
	'aloha/content-rules',
	'util/html',
	'util/dom'
], function (
	$,
	PubSub,
	Plugin,
	Aloha,
	ContentRules,
	Html,
	Dom
) {
	

	/**
	 * Name of this plugin
	 */
	var pluginName = 'autoparagraph';

	var configurations = {};

	/**
	 * Auto-generate missing paragraphs in the given editable, when the editable allows insertion of paragraphs.
	 * If the editable is the currently active one, the current selection will be modified according to the content
	 * changes and the corrected range will be selected.
	 * This means that the visual selection should remain in its original state
	 * 
	 * @param {Editable} editable
	 */
	function autogenerateParagraphs(editable) {
		if (!editable) {
			return;
		}
		var $obj = editable.obj;
		if (!$obj) {
			return;
		}
		var obj = $obj[0], i, j, selectionRange = Aloha.Selection.rangeObject, contentChanged = false;

		// check whether nesting of paragraphs inside the editable is allowed
		if (!Dom.allowsNesting(obj, $('<p></p>')[0])) {
			return;
		}

		// collect lists of subsequent child elements of the editable,
		// that are no block level elements (and thus need to be wrapped
		// into a paragraph)
		var nonBlockRanges = [];
		var current;
		$obj.contents().each(function () {
			if (!Html.isBlock(this)) {
				if (!current) {
					// start a new list
					current = {
						objs: []
					};
					nonBlockRanges.push(current);
				}

				// add the DOM element to the current list
				current.objs.push(this);
			} else {
				// we found a block element, so we are done with the current list
				current = null;
			}
		});

		// wrap all non-block lists into p Tags
		// in other words: replace the list of sibling DOM elements with
		// a single (new) paragraph, that will contain the list of DOM
		// elements as children
		for (i = 0; i < nonBlockRanges.length; i++) {
			var range = nonBlockRanges[i];
			var indexStart = Dom.getIndexInParent(range.objs[0]);
			var indexEnd = Dom.getIndexInParent(range.objs[range.objs.length - 1]);
			var $p = $('<p></p>');

			// correct the start of the selection range, if necessary
			if (selectionRange.startContainer === obj) {
				if (selectionRange.startOffset > indexStart && selectionRange.startOffset <= indexEnd) {
					selectionRange.startContainer = $p[0];
					selectionRange.startOffset -= indexStart;
				} else if (selectionRange.startOffset > indexEnd) {
					selectionRange.startOffset -= (indexEnd - indexStart);
				}
			}
			// correct the end of the selection range, if necessary
			if (selectionRange.endContainer === obj) {
				if (selectionRange.endOffset > indexStart && selectionRange.endOffset <= indexEnd) {
					selectionRange.endContainer = $p[0];
					selectionRange.endOffset -= indexStart;
				} else if (selectionRange.endOffset > indexEnd) {
					selectionRange.endOffset -= (indexEnd - indexStart);
				}
			}

			// insert the paragraph right before the old dom elements
			$(range.objs[0]).before($p);
			// move all old dom elements into the paragraph
			for (j = 0; j < range.objs.length; j++) {
				$p[0].appendChild(range.objs[j]);
			}
			contentChanged = true;
		}

		// select the corrected selection, but only if we changed
		// something in the content and the editable is the active one
		if (contentChanged && editable.isActive) {
			selectionRange.select();
		}
	}

	/**
	 * Checks whether or not pluginName is activated for an editable.
	 *
	 * @param {object} The plugin/editable configuration.
	 * @return {boolean} True if activated.
	 */
	function isPluginActivated(config) {
		return (
			$.type(config) === 'array' && $.inArray(pluginName, config) !== -1
		);
	}

	/**
	 * @type {Aloha.Plugin}
	 */
	var autoParagraphPlugin = Plugin.create(pluginName, {
		/**
		 * Default config: plugin active for all editables
		 */
		config: [pluginName],

		/**
		 * Initialize the plugin
		 */
		init: function () {
			var plugin = this;

			// autogenerate paragraphs when a new editable is created
			PubSub.sub('aloha.editable.created', function (message) {
				var editable = message.editable;
				var config = plugin.getEditableConfig(editable.obj);
				var enabled = config
				           && ($.inArray(pluginName, config) > -1)
				           && ContentRules.isAllowed(editable.obj[0], 'p');
				configurations[editable.getId()] = !!enabled;
				if (enabled) {
					autogenerateParagraphs(editable);
				}
			});

			// autogenerate paragraphs upon smart content change
			Aloha.bind('aloha-smart-content-changed', function (event, data) {
				if (configurations[data.editable.getId()]) {
					autogenerateParagraphs(data.editable);
				}
			});
		}
	});

	return autoParagraphPlugin;
});

/* formatlesshandler.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('formatlesspaste/formatlesshandler',[
	'aloha',
	'jquery',
	'aloha/contenthandlermanager'
], function (
	Aloha,
	$,
	ContentHandlerManager
) {
	

	/**
	 * Remove unwanted formatting in the pasted element.
	 *
	 * @param {jQuery.<HTMLElement>} $content Element to process.
	 * @param {Array.<string>} toStip A list of tags to strip from the content.
	 */
	function removeFormatting($content, toStrip) {
		$content.find(toStrip.join(',')).each(function () {
			if ($(this).contents().length === 0) {
				$(this).remove();
			} else {
				$(this).contents().unwrap();
			}
		});
	}

	/**
	 * Register the formatless paste handler.
	 *
	 * @class {FormatlessPasteHandler}
	 */
	var FormatlessPasteHandler = ContentHandlerManager.createHandler({

		/**
		 * Whether or not formatless copying and pasting is enable.
		 *
		 * @type {boolean}
		 */
		enabled: false,

		/**
		 * Handles the copying and pasting. Removes all content that have been
		 * configured to be striooed away.
		 *
		 * @param {string|jQuery.<HTMLElement>} content The content to
		 *                                              transform.
		 * @return {string} Transformed markup.
		 */
		handleContent: function (content) {
			var $content;

			if (typeof content === 'string') {
				$content = $('<div>' + content + '</div>');
			} else if (content instanceof $) {
				$content = $('<div>').append(content);
			}

			// If an aloha-block is found inside the pasted content, nothing
			// should be modified as it most probably comes from Aloha and does
			// not need to be cleaned up.
			if ($content.find('.aloha-block').length) {
				return;
			}

			if (this.enabled) {
				removeFormatting($content, this.strippedElements);
			}

			return $content.html();
		}
	});

	return FormatlessPasteHandler;
});

define('formatlesspaste/nls/i18n',{
	"root":  {
		"button.formatlessPaste.tooltip": "Keep/Discard formatting"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* formatlesspaste-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('formatlesspaste/formatlesspaste-plugin',[
	'aloha/core',
	'aloha/plugin',
	'jquery',
	'ui/ui',
	'ui/toggleButton',
	'formatlesspaste/formatlesshandler',
	'aloha/contenthandlermanager',
	'i18n!formatlesspaste/nls/i18n',
	'util/html'
], function (
	Aloha,
	Plugin,
	$,
	Ui,
	ToggleButton,
	FormatlessPasteHandler,
	ContentHandlerManager,
	i18n,
    Html
) {
	

	/**
	 * Normalizes the given string value to either boolean true or false.
	 *
	 * @param {*} value The value which is to be normalized.
	 * @return {boolean} True if the value if truthy and not the string "false"
	 *                   or "0".
	 */
	function normalizeToBoolean(value) {
		if (!value) {
			return false;
		}
		if (typeof value === 'string') {
			return '0' !== value && 'false' !== value.toLowerCase();
		}
		return true;
	}

	function parseConfiguration(config) {
		var parsed = {};
		if (typeof config.formatlessPasteOption !== 'undefined') {
			parsed.formatlessPasteOption =
					normalizeToBoolean(config.formatlessPasteOption);
		}
		if (typeof config.strippedElements !== 'undefined') {
			parsed.strippedElements = config.strippedElements;
		}
		if (typeof config.button !== 'undefined') {
			parsed.button = normalizeToBoolean(config.button);
		}
		return parsed;
	}

	function applyConfiguration(obj, config) {
		$.extend(obj, config);
	}

	function registerFormatlessPasteHandler(plugin, config) {
		ContentHandlerManager.register('formatless', FormatlessPasteHandler);
		FormatlessPasteHandler.strippedElements = config.strippedElements || Html.TEXT_LEVEL_SEMANTIC_ELEMENTS;

		plugin._toggleFormatlessPasteButton =
			Ui.adopt('toggleFormatlessPaste', ToggleButton, {
				tooltip : i18n.t('button.formatlessPaste.tooltip'),
				icon    : 'aloha-icon aloha-icon-formatless-paste',
				scope   : 'Aloha.continuoustext',
				click   : function () {
					// Toggle the value of allowFormatless
					FormatlessPasteHandler.enabled =
						!FormatlessPasteHandler.enabled;
				}
			});

		plugin._toggleFormatlessPasteButton.show(plugin.button);

		if (true === plugin.formatlessPasteOption) {
			plugin._toggleFormatlessPasteButton.setState(true);
			FormatlessPasteHandler.enabled = true;
		} else if (false === plugin.formatlessPasteOption) {
			plugin._toggleFormatlessPasteButton.setState(false);
			FormatlessPasteHandler.enabled = false;
		}
	}

	var configLookup = {};

	function getEditableConfig(plugin, editable) {
		var id = editable.getId();
		if (!id) {
			return null;
		}
		if (configLookup[id]) {
			return configLookup[id];
		}
		var config = plugin.getEditableConfig(editable.obj);
		if (!config) {
			return null;
		}
		configLookup[id] = parseConfiguration(config);
		return configLookup[id];
	}

	function clearEditableConfig(editable) {
		var id = editable.getId();
		if (id && configLookup[id]) {
			delete configLookup[id];
		}
	}

	Aloha.bind('aloha-editable-destroyed', function ($event, data) {
		clearEditableConfig(data);
	});

	return Plugin.create('formatlesspaste', {

		/**
		 * Whether or not formatless pasting is enabled.
		 *
		 * Configurable.
		 *
		 * @type {boolean}
		 */
		formatlessPasteOption: false,

		/**
		 * Whether or not to show the formatless paste button.
		 *
		 * Configurable.
		 *
		 * @type {boolean}
		 */
		button: true,

		/**
		 * Initializes formatless copying and pasting.
		 * Parses configuration.
		 */
		init: function () {
			var plugin = this;
			var config = plugin.settings.config || plugin.settings;
			var parsedConfig = parseConfiguration(config);

			applyConfiguration(plugin, parsedConfig);
			registerFormatlessPasteHandler(plugin, parsedConfig);

			Aloha.bind('aloha-editable-activated', function ($event, data) {
				var config = getEditableConfig(plugin, data.editable);
				if (!config) {
					return;
				}

				var pasteButton = plugin._toggleFormatlessPasteButton;

				if (true === config.formatlessPasteOption) {
					pasteButton.setState(true);
					FormatlessPasteHandler.enabled = true;
				} else if (false === config.formatlessPasteOption) {
					pasteButton.setState(false);
					FormatlessPasteHandler.enabled = false;
				}

				FormatlessPasteHandler.strippedElements = config.strippedElements || Html.TEXT_LEVEL_SEMANTIC_ELEMENTS;

				pasteButton.show(false !== config.button);
			});
		}
	});
});
	if (Aloha._load) {
		Aloha._load(); // initialized in aloha.js
	}
}()); // bye Aloha Editor ;-)
