src/pyams_skin/resources/js/ext/tinymce/dev/classes/EditorCommands.js
changeset 69 a361355b55c7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/tinymce/dev/classes/EditorCommands.js	Wed Jun 17 10:00:10 2015 +0200
@@ -0,0 +1,999 @@
+/**
+ * EditorCommands.js
+ *
+ * Copyright, Moxiecode Systems AB
+ * Released under LGPL License.
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+/**
+ * This class enables you to add custom editor commands and it contains
+ * overrides for native browser commands to address various bugs and issues.
+ *
+ * @class tinymce.EditorCommands
+ */
+define("tinymce/EditorCommands", [
+	"tinymce/html/Serializer",
+	"tinymce/Env",
+	"tinymce/util/Tools",
+	"tinymce/dom/ElementUtils",
+	"tinymce/dom/RangeUtils",
+	"tinymce/dom/TreeWalker"
+], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker) {
+	// Added for compression purposes
+	var each = Tools.each, extend = Tools.extend;
+	var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
+	var isGecko = Env.gecko, isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11;
+	var TRUE = true, FALSE = false;
+
+	return function(editor) {
+		var dom, selection, formatter,
+			commands = {state: {}, exec: {}, value: {}},
+			settings = editor.settings,
+			bookmark;
+
+		editor.on('PreInit', function() {
+			dom = editor.dom;
+			selection = editor.selection;
+			settings = editor.settings;
+			formatter = editor.formatter;
+		});
+
+		/**
+		 * Executes the specified command.
+		 *
+		 * @method execCommand
+		 * @param {String} command Command to execute.
+		 * @param {Boolean} ui Optional user interface state.
+		 * @param {Object} value Optional value for command.
+		 * @return {Boolean} true/false if the command was found or not.
+		 */
+		function execCommand(command, ui, value, args) {
+			var func, customCommand, state = 0;
+
+			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) {
+				editor.focus();
+			}
+
+			args = extend({}, args);
+			args = editor.fire('BeforeExecCommand', {command: command, ui: ui, value: value});
+			if (args.isDefaultPrevented()) {
+				return false;
+			}
+
+			customCommand = command.toLowerCase();
+			if ((func = commands.exec[customCommand])) {
+				func(customCommand, ui, value);
+				editor.fire('ExecCommand', {command: command, ui: ui, value: value});
+				return true;
+			}
+
+			// Plugin commands
+			each(editor.plugins, function(p) {
+				if (p.execCommand && p.execCommand(command, ui, value)) {
+					editor.fire('ExecCommand', {command: command, ui: ui, value: value});
+					state = true;
+					return false;
+				}
+			});
+
+			if (state) {
+				return state;
+			}
+
+			// Theme commands
+			if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) {
+				editor.fire('ExecCommand', {command: command, ui: ui, value: value});
+				return true;
+			}
+
+			// Browser commands
+			try {
+				state = editor.getDoc().execCommand(command, ui, value);
+			} catch (ex) {
+				// Ignore old IE errors
+			}
+
+			if (state) {
+				editor.fire('ExecCommand', {command: command, ui: ui, value: value});
+				return true;
+			}
+
+			return false;
+		}
+
+		/**
+		 * Queries the current state for a command for example if the current selection is "bold".
+		 *
+		 * @method queryCommandState
+		 * @param {String} command Command to check the state of.
+		 * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
+		 */
+		function queryCommandState(command) {
+			var func;
+
+			// Is hidden then return undefined
+			if (editor._isHidden()) {
+				return;
+			}
+
+			command = command.toLowerCase();
+			if ((func = commands.state[command])) {
+				return func(command);
+			}
+
+			// Browser commands
+			try {
+				return editor.getDoc().queryCommandState(command);
+			} catch (ex) {
+				// Fails sometimes see bug: 1896577
+			}
+
+			return false;
+		}
+
+		/**
+		 * Queries the command value for example the current fontsize.
+		 *
+		 * @method queryCommandValue
+		 * @param {String} command Command to check the value of.
+		 * @return {Object} Command value of false if it's not found.
+		 */
+		function queryCommandValue(command) {
+			var func;
+
+			// Is hidden then return undefined
+			if (editor._isHidden()) {
+				return;
+			}
+
+			command = command.toLowerCase();
+			if ((func = commands.value[command])) {
+				return func(command);
+			}
+
+			// Browser commands
+			try {
+				return editor.getDoc().queryCommandValue(command);
+			} catch (ex) {
+				// Fails sometimes see bug: 1896577
+			}
+		}
+
+		/**
+		 * Adds commands to the command collection.
+		 *
+		 * @method addCommands
+		 * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
+		 * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
+		 */
+		function addCommands(command_list, type) {
+			type = type || 'exec';
+
+			each(command_list, function(callback, command) {
+				each(command.toLowerCase().split(','), function(command) {
+					commands[type][command] = callback;
+				});
+			});
+		}
+
+		function addCommand(command, callback, scope) {
+			command = command.toLowerCase();
+			commands.exec[command] = function(command, ui, value, args) {
+				return callback.call(scope || editor, ui, value, args);
+			};
+		}
+
+		/**
+		 * Returns true/false if the command is supported or not.
+		 *
+		 * @method queryCommandSupported
+		 * @param {String} cmd Command that we check support for.
+		 * @return {Boolean} true/false if the command is supported or not.
+		 */
+		function queryCommandSupported(command) {
+			command = command.toLowerCase();
+
+			if (commands.exec[command]) {
+				return true;
+			}
+
+			// Browser commands
+			try {
+				return editor.getDoc().queryCommandSupported(command);
+			} catch (ex) {
+				// Fails sometimes see bug: 1896577
+			}
+
+			return false;
+		}
+
+		function addQueryStateHandler(command, callback, scope) {
+			command = command.toLowerCase();
+			commands.state[command] = function() {
+				return callback.call(scope || editor);
+			};
+		}
+
+		function addQueryValueHandler(command, callback, scope) {
+			command = command.toLowerCase();
+			commands.value[command] = function() {
+				return callback.call(scope || editor);
+			};
+		}
+
+		function hasCustomCommand(command) {
+			command = command.toLowerCase();
+			return !!commands.exec[command];
+		}
+
+		// Expose public methods
+		extend(this, {
+			execCommand: execCommand,
+			queryCommandState: queryCommandState,
+			queryCommandValue: queryCommandValue,
+			queryCommandSupported: queryCommandSupported,
+			addCommands: addCommands,
+			addCommand: addCommand,
+			addQueryStateHandler: addQueryStateHandler,
+			addQueryValueHandler: addQueryValueHandler,
+			hasCustomCommand: hasCustomCommand
+		});
+
+		// Private methods
+
+		function execNativeCommand(command, ui, value) {
+			if (ui === undefined) {
+				ui = FALSE;
+			}
+
+			if (value === undefined) {
+				value = null;
+			}
+
+			return editor.getDoc().execCommand(command, ui, value);
+		}
+
+		function isFormatMatch(name) {
+			return formatter.match(name);
+		}
+
+		function toggleFormat(name, value) {
+			formatter.toggle(name, value ? {value: value} : undefined);
+			editor.nodeChanged();
+		}
+
+		function storeSelection(type) {
+			bookmark = selection.getBookmark(type);
+		}
+
+		function restoreSelection() {
+			selection.moveToBookmark(bookmark);
+		}
+
+		// Add execCommand overrides
+		addCommands({
+			// Ignore these, added for compatibility
+			'mceResetDesignMode,mceBeginUndoLevel': function() {},
+
+			// Add undo manager logic
+			'mceEndUndoLevel,mceAddUndoLevel': function() {
+				editor.undoManager.add();
+			},
+
+			'Cut,Copy,Paste': function(command) {
+				var doc = editor.getDoc(), failed;
+
+				// Try executing the native command
+				try {
+					execNativeCommand(command);
+				} catch (ex) {
+					// Command failed
+					failed = TRUE;
+				}
+
+				// Present alert message about clipboard access not being available
+				if (failed || !doc.queryCommandSupported(command)) {
+					var msg = editor.translate(
+						"Your browser doesn't support direct access to the clipboard. " +
+						"Please use the Ctrl+X/C/V keyboard shortcuts instead."
+					);
+
+					if (Env.mac) {
+						msg = msg.replace(/Ctrl\+/g, '\u2318+');
+					}
+
+					editor.windowManager.alert(msg);
+				}
+			},
+
+			// Override unlink command
+			unlink: function() {
+				if (selection.isCollapsed()) {
+					var elm = selection.getNode();
+					if (elm.tagName == 'A') {
+						editor.dom.remove(elm, true);
+					}
+
+					return;
+				}
+
+				formatter.remove("link");
+			},
+
+			// Override justify commands to use the text formatter engine
+			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
+				var align = command.substring(7);
+
+				if (align == 'full') {
+					align = 'justify';
+				}
+
+				// Remove all other alignments first
+				each('left,center,right,justify'.split(','), function(name) {
+					if (align != name) {
+						formatter.remove('align' + name);
+					}
+				});
+
+				toggleFormat('align' + align);
+				execCommand('mceRepaint');
+			},
+
+			// Override list commands to fix WebKit bug
+			'InsertUnorderedList,InsertOrderedList': function(command) {
+				var listElm, listParent;
+
+				execNativeCommand(command);
+
+				// WebKit produces lists within block elements so we need to split them
+				// we will replace the native list creation logic to custom logic later on
+				// TODO: Remove this when the list creation logic is removed
+				listElm = dom.getParent(selection.getNode(), 'ol,ul');
+				if (listElm) {
+					listParent = listElm.parentNode;
+
+					// If list is within a text block then split that block
+					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
+						storeSelection();
+						dom.split(listParent, listElm);
+						restoreSelection();
+					}
+				}
+			},
+
+			// Override commands to use the text formatter engine
+			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
+				toggleFormat(command);
+			},
+
+			// Override commands to use the text formatter engine
+			'ForeColor,HiliteColor,FontName': function(command, ui, value) {
+				toggleFormat(command, value);
+			},
+
+			FontSize: function(command, ui, value) {
+				var fontClasses, fontSizes;
+
+				// Convert font size 1-7 to styles
+				if (value >= 1 && value <= 7) {
+					fontSizes = explode(settings.font_size_style_values);
+					fontClasses = explode(settings.font_size_classes);
+
+					if (fontClasses) {
+						value = fontClasses[value - 1] || value;
+					} else {
+						value = fontSizes[value - 1] || value;
+					}
+				}
+
+				toggleFormat(command, value);
+			},
+
+			RemoveFormat: function(command) {
+				formatter.remove(command);
+			},
+
+			mceBlockQuote: function() {
+				toggleFormat('blockquote');
+			},
+
+			FormatBlock: function(command, ui, value) {
+				return toggleFormat(value || 'p');
+			},
+
+			mceCleanup: function() {
+				var bookmark = selection.getBookmark();
+
+				editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
+
+				selection.moveToBookmark(bookmark);
+			},
+
+			mceRemoveNode: function(command, ui, value) {
+				var node = value || selection.getNode();
+
+				// Make sure that the body node isn't removed
+				if (node != editor.getBody()) {
+					storeSelection();
+					editor.dom.remove(node, TRUE);
+					restoreSelection();
+				}
+			},
+
+			mceSelectNodeDepth: function(command, ui, value) {
+				var counter = 0;
+
+				dom.getParent(selection.getNode(), function(node) {
+					if (node.nodeType == 1 && counter++ == value) {
+						selection.select(node);
+						return FALSE;
+					}
+				}, editor.getBody());
+			},
+
+			mceSelectNode: function(command, ui, value) {
+				selection.select(value);
+			},
+
+			mceInsertContent: function(command, ui, value) {
+				var parser, serializer, parentNode, rootNode, fragment, args;
+				var marker, rng, node, node2, bookmarkHtml, merge;
+				var textInlineElements = editor.schema.getTextInlineElements();
+
+				function trimOrPaddLeftRight(html) {
+					var rng, container, offset;
+
+					rng = selection.getRng(true);
+					container = rng.startContainer;
+					offset = rng.startOffset;
+
+					function hasSiblingText(siblingName) {
+						return container[siblingName] && container[siblingName].nodeType == 3;
+					}
+
+					if (container.nodeType == 3) {
+						if (offset > 0) {
+							html = html.replace(/^&nbsp;/, ' ');
+						} else if (!hasSiblingText('previousSibling')) {
+							html = html.replace(/^ /, '&nbsp;');
+						}
+
+						if (offset < container.length) {
+							html = html.replace(/&nbsp;(<br>|)$/, ' ');
+						} else if (!hasSiblingText('nextSibling')) {
+							html = html.replace(/(&nbsp;| )(<br>|)$/, '&nbsp;');
+						}
+					}
+
+					return html;
+				}
+
+				// Removes &nbsp; from a [b] c -> a &nbsp;c -> a c
+				function trimNbspAfterDeleteAndPaddValue() {
+					var rng, container, offset;
+
+					rng = selection.getRng(true);
+					container = rng.startContainer;
+					offset = rng.startOffset;
+
+					if (container.nodeType == 3 && rng.collapsed) {
+						if (container.data[offset] === '\u00a0') {
+							container.deleteData(offset, 1);
+
+							if (!/[\u00a0| ]$/.test(value)) {
+								value += ' ';
+							}
+						} else if (container.data[offset - 1] === '\u00a0') {
+							container.deleteData(offset - 1, 1);
+
+							if (!/[\u00a0| ]$/.test(value)) {
+								value = ' ' + value;
+							}
+						}
+					}
+				}
+
+				function markInlineFormatElements(fragment) {
+					if (merge) {
+						for (node = fragment.firstChild; node; node = node.walk(true)) {
+							if (textInlineElements[node.name]) {
+								node.attr('data-mce-new', "true");
+							}
+						}
+					}
+				}
+
+				function reduceInlineTextElements() {
+					if (merge) {
+						var root = editor.getBody(), elementUtils = new ElementUtils(dom);
+
+						each(dom.select('*[data-mce-new]'), function(node) {
+							node.removeAttribute('data-mce-new');
+
+							for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
+								if (elementUtils.compare(testNode, node)) {
+									dom.remove(node, true);
+								}
+							}
+						});
+					}
+				}
+
+				if (typeof value != 'string') {
+					merge = value.merge;
+					value = value.content;
+				}
+
+				// Check for whitespace before/after value
+				if (/^ | $/.test(value)) {
+					value = trimOrPaddLeftRight(value);
+				}
+
+				// Setup parser and serializer
+				parser = editor.parser;
+				serializer = new Serializer({}, editor.schema);
+				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#x200B;</span>';
+
+				// Run beforeSetContent handlers on the HTML to be inserted
+				args = {content: value, format: 'html', selection: true};
+				editor.fire('BeforeSetContent', args);
+				value = args.content;
+
+				// Add caret at end of contents if it's missing
+				if (value.indexOf('{$caret}') == -1) {
+					value += '{$caret}';
+				}
+
+				// Replace the caret marker with a span bookmark element
+				value = value.replace(/\{\$caret\}/, bookmarkHtml);
+
+				// If selection is at <body>|<p></p> then move it into <body><p>|</p>
+				rng = selection.getRng();
+				var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
+				var body = editor.getBody();
+				if (caretElement === body && selection.isCollapsed()) {
+					if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) {
+						rng = dom.createRng();
+						rng.setStart(body.firstChild, 0);
+						rng.setEnd(body.firstChild, 0);
+						selection.setRng(rng);
+					}
+				}
+
+				// Insert node maker where we will insert the new HTML and get it's parent
+				if (!selection.isCollapsed()) {
+					editor.getDoc().execCommand('Delete', false, null);
+					trimNbspAfterDeleteAndPaddValue();
+				}
+
+				parentNode = selection.getNode();
+
+				// Parse the fragment within the context of the parent node
+				var parserArgs = {context: parentNode.nodeName.toLowerCase()};
+				fragment = parser.parse(value, parserArgs);
+
+				markInlineFormatElements(fragment);
+
+				// Move the caret to a more suitable location
+				node = fragment.lastChild;
+				if (node.attr('id') == 'mce_marker') {
+					marker = node;
+
+					for (node = node.prev; node; node = node.walk(true)) {
+						if (node.type == 3 || !dom.isBlock(node.name)) {
+							if (editor.schema.isValidChild(node.parent.name, 'span')) {
+								node.parent.insert(marker, node, node.name === 'br');
+							}
+							break;
+						}
+					}
+				}
+
+				// If parser says valid we can insert the contents into that parent
+				if (!parserArgs.invalid) {
+					value = serializer.serialize(fragment);
+
+					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
+					node = parentNode.firstChild;
+					node2 = parentNode.lastChild;
+					if (!node || (node === node2 && node.nodeName === 'BR')) {
+						dom.setHTML(parentNode, value);
+					} else {
+						selection.setContent(value);
+					}
+				} else {
+					// If the fragment was invalid within that context then we need
+					// to parse and process the parent it's inserted into
+
+					// Insert bookmark node and get the parent
+					selection.setContent(bookmarkHtml);
+					parentNode = selection.getNode();
+					rootNode = editor.getBody();
+
+					// Opera will return the document node when selection is in root
+					if (parentNode.nodeType == 9) {
+						parentNode = node = rootNode;
+					} else {
+						node = parentNode;
+					}
+
+					// Find the ancestor just before the root element
+					while (node !== rootNode) {
+						parentNode = node;
+						node = node.parentNode;
+					}
+
+					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
+					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
+					value = serializer.serialize(
+						parser.parse(
+							// Need to replace by using a function since $ in the contents would otherwise be a problem
+							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
+								return serializer.serialize(fragment);
+							})
+						)
+					);
+
+					// Set the inner/outer HTML depending on if we are in the root or not
+					if (parentNode == rootNode) {
+						dom.setHTML(rootNode, value);
+					} else {
+						dom.setOuterHTML(parentNode, value);
+					}
+				}
+
+				reduceInlineTextElements();
+
+				marker = dom.get('mce_marker');
+				selection.scrollIntoView(marker);
+
+				// Move selection before marker and remove it
+				rng = dom.createRng();
+
+				// If previous sibling is a text node set the selection to the end of that node
+				node = marker.previousSibling;
+				if (node && node.nodeType == 3) {
+					rng.setStart(node, node.nodeValue.length);
+
+					// TODO: Why can't we normalize on IE
+					if (!isIE) {
+						node2 = marker.nextSibling;
+						if (node2 && node2.nodeType == 3) {
+							node.appendData(node2.data);
+							node2.parentNode.removeChild(node2);
+						}
+					}
+				} else {
+					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
+					rng.setStartBefore(marker);
+					rng.setEndBefore(marker);
+				}
+
+				// Remove the marker node and set the new range
+				dom.remove(marker);
+				selection.setRng(rng);
+
+				// Dispatch after event and add any visual elements needed
+				editor.fire('SetContent', args);
+				editor.addVisual();
+			},
+
+			mceInsertRawHTML: function(command, ui, value) {
+				selection.setContent('tiny_mce_marker');
+				editor.setContent(
+					editor.getContent().replace(/tiny_mce_marker/g, function() {
+						return value;
+					})
+				);
+			},
+
+			mceToggleFormat: function(command, ui, value) {
+				toggleFormat(value);
+			},
+
+			mceSetContent: function(command, ui, value) {
+				editor.setContent(value);
+			},
+
+			'Indent,Outdent': function(command) {
+				var intentValue, indentUnit, value;
+
+				// Setup indent level
+				intentValue = settings.indentation;
+				indentUnit = /[a-z%]+$/i.exec(intentValue);
+				intentValue = parseInt(intentValue, 10);
+
+				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
+					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
+					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
+						formatter.apply('div');
+					}
+
+					each(selection.getSelectedBlocks(), function(element) {
+						if (element.nodeName != "LI") {
+							var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';
+
+							indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';
+
+							if (command == 'outdent') {
+								value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
+								dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
+							} else {
+								value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
+								dom.setStyle(element, indentStyleName, value);
+							}
+						}
+					});
+				} else {
+					execNativeCommand(command);
+				}
+			},
+
+			mceRepaint: function() {
+				if (isGecko) {
+					try {
+						storeSelection(TRUE);
+
+						if (selection.getSel()) {
+							selection.getSel().selectAllChildren(editor.getBody());
+						}
+
+						selection.collapse(TRUE);
+						restoreSelection();
+					} catch (ex) {
+						// Ignore
+					}
+				}
+			},
+
+			InsertHorizontalRule: function() {
+				editor.execCommand('mceInsertContent', false, '<hr />');
+			},
+
+			mceToggleVisualAid: function() {
+				editor.hasVisual = !editor.hasVisual;
+				editor.addVisual();
+			},
+
+			mceReplaceContent: function(command, ui, value) {
+				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
+			},
+
+			mceInsertLink: function(command, ui, value) {
+				var anchor;
+
+				if (typeof value == 'string') {
+					value = {href: value};
+				}
+
+				anchor = dom.getParent(selection.getNode(), 'a');
+
+				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
+				value.href = value.href.replace(' ', '%20');
+
+				// Remove existing links if there could be child links or that the href isn't specified
+				if (!anchor || !value.href) {
+					formatter.remove('link');
+				}
+
+				// Apply new link to selection
+				if (value.href) {
+					formatter.apply('link', value, anchor);
+				}
+			},
+
+			selectAll: function() {
+				var root = dom.getRoot(), rng;
+
+				if (selection.getRng().setStart) {
+					rng = dom.createRng();
+					rng.setStart(root, 0);
+					rng.setEnd(root, root.childNodes.length);
+					selection.setRng(rng);
+				} else {
+					// IE will render it's own root level block elements and sometimes
+					// even put font elements in them when the user starts typing. So we need to
+					// move the selection to a more suitable element from this:
+					// <body>|<p></p></body> to this: <body><p>|</p></body>
+					rng = selection.getRng();
+					if (!rng.item) {
+						rng.moveToElementText(root);
+						rng.select();
+					}
+				}
+			},
+
+			"delete": function() {
+				execNativeCommand("Delete");
+
+				// Check if body is empty after the delete call if so then set the contents
+				// to an empty string and move the caret to any block produced by that operation
+				// this fixes the issue with root blocks not being properly produced after a delete call on IE
+				var body = editor.getBody();
+
+				if (dom.isEmpty(body)) {
+					editor.setContent('');
+
+					if (body.firstChild && dom.isBlock(body.firstChild)) {
+						editor.selection.setCursorLocation(body.firstChild, 0);
+					} else {
+						editor.selection.setCursorLocation(body, 0);
+					}
+				}
+			},
+
+			mceNewDocument: function() {
+				editor.setContent('');
+			},
+
+			InsertLineBreak: function(command, ui, value) {
+				// We load the current event in from EnterKey.js when appropriate to heed
+				// certain event-specific variations such as ctrl-enter in a list
+				var evt = value;
+				var brElm, extraBr, marker;
+				var rng = selection.getRng(true);
+				new RangeUtils(dom).normalize(rng);
+
+				var offset = rng.startOffset;
+				var container = rng.startContainer;
+
+				// Resolve node index
+				if (container.nodeType == 1 && container.hasChildNodes()) {
+					var isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
+
+					container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
+					if (isAfterLastNodeInContainer && container.nodeType == 3) {
+						offset = container.nodeValue.length;
+					} else {
+						offset = 0;
+					}
+				}
+
+				var parentBlock = dom.getParent(container, dom.isBlock);
+				var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
+				var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
+				var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
+
+				// Enter inside block contained within a LI then split or insert before/after LI
+				var isControlKey = evt && evt.ctrlKey;
+				if (containerBlockName == 'LI' && !isControlKey) {
+					parentBlock = containerBlock;
+					parentBlockName = containerBlockName;
+				}
+
+				// Walks the parent block to the right and look for BR elements
+				function hasRightSideContent() {
+					var walker = new TreeWalker(container, parentBlock), node;
+					var nonEmptyElementsMap = editor.schema.getNonEmptyElements();
+
+					while ((node = walker.next())) {
+						if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
+							return true;
+						}
+					}
+				}
+
+				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
+					// Insert extra BR element at the end block elements
+					if (!isOldIE && !hasRightSideContent()) {
+						brElm = dom.create('br');
+						rng.insertNode(brElm);
+						rng.setStartAfter(brElm);
+						rng.setEndAfter(brElm);
+						extraBr = true;
+					}
+				}
+
+				brElm = dom.create('br');
+				rng.insertNode(brElm);
+
+				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
+				var documentMode = dom.doc.documentMode;
+				if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
+					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
+				}
+
+				// Insert temp marker and scroll to that
+				marker = dom.create('span', {}, '&nbsp;');
+				brElm.parentNode.insertBefore(marker, brElm);
+				selection.scrollIntoView(marker);
+				dom.remove(marker);
+
+				if (!extraBr) {
+					rng.setStartAfter(brElm);
+					rng.setEndAfter(brElm);
+				} else {
+					rng.setStartBefore(brElm);
+					rng.setEndBefore(brElm);
+				}
+
+				selection.setRng(rng);
+				editor.undoManager.add();
+
+				return TRUE;
+			}
+		});
+
+		// Add queryCommandState overrides
+		addCommands({
+			// Override justify commands
+			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
+				var name = 'align' + command.substring(7);
+				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
+				var matches = map(nodes, function(node) {
+					return !!formatter.matchNode(node, name);
+				});
+				return inArray(matches, TRUE) !== -1;
+			},
+
+			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
+				return isFormatMatch(command);
+			},
+
+			mceBlockQuote: function() {
+				return isFormatMatch('blockquote');
+			},
+
+			Outdent: function() {
+				var node;
+
+				if (settings.inline_styles) {
+					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
+						return TRUE;
+					}
+
+					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
+						return TRUE;
+					}
+				}
+
+				return (
+					queryCommandState('InsertUnorderedList') ||
+					queryCommandState('InsertOrderedList') ||
+					(!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
+				);
+			},
+
+			'InsertUnorderedList,InsertOrderedList': function(command) {
+				var list = dom.getParent(selection.getNode(), 'ul,ol');
+
+				return list &&
+					(
+						command === 'insertunorderedlist' && list.tagName === 'UL' ||
+						command === 'insertorderedlist' && list.tagName === 'OL'
+					);
+			}
+		}, 'state');
+
+		// Add queryCommandValue overrides
+		addCommands({
+			'FontSize,FontName': function(command) {
+				var value = 0, parent;
+
+				if ((parent = dom.getParent(selection.getNode(), 'span'))) {
+					if (command == 'fontsize') {
+						value = parent.style.fontSize;
+					} else {
+						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
+					}
+				}
+
+				return value;
+			}
+		}, 'value');
+
+		// Add undo manager logic
+		addCommands({
+			Undo: function() {
+				editor.undoManager.undo();
+			},
+
+			Redo: function() {
+				editor.undoManager.redo();
+			}
+		});
+	};
+});