src/pyams_skin/resources/js/ext/tinymce/dev/classes/EditorCommands.js
changeset 69 a361355b55c7
equal deleted inserted replaced
68:fd8fb93e1b6a 69:a361355b55c7
       
     1 /**
       
     2  * EditorCommands.js
       
     3  *
       
     4  * Copyright, Moxiecode Systems AB
       
     5  * Released under LGPL License.
       
     6  *
       
     7  * License: http://www.tinymce.com/license
       
     8  * Contributing: http://www.tinymce.com/contributing
       
     9  */
       
    10 
       
    11 /**
       
    12  * This class enables you to add custom editor commands and it contains
       
    13  * overrides for native browser commands to address various bugs and issues.
       
    14  *
       
    15  * @class tinymce.EditorCommands
       
    16  */
       
    17 define("tinymce/EditorCommands", [
       
    18 	"tinymce/html/Serializer",
       
    19 	"tinymce/Env",
       
    20 	"tinymce/util/Tools",
       
    21 	"tinymce/dom/ElementUtils",
       
    22 	"tinymce/dom/RangeUtils",
       
    23 	"tinymce/dom/TreeWalker"
       
    24 ], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker) {
       
    25 	// Added for compression purposes
       
    26 	var each = Tools.each, extend = Tools.extend;
       
    27 	var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
       
    28 	var isGecko = Env.gecko, isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11;
       
    29 	var TRUE = true, FALSE = false;
       
    30 
       
    31 	return function(editor) {
       
    32 		var dom, selection, formatter,
       
    33 			commands = {state: {}, exec: {}, value: {}},
       
    34 			settings = editor.settings,
       
    35 			bookmark;
       
    36 
       
    37 		editor.on('PreInit', function() {
       
    38 			dom = editor.dom;
       
    39 			selection = editor.selection;
       
    40 			settings = editor.settings;
       
    41 			formatter = editor.formatter;
       
    42 		});
       
    43 
       
    44 		/**
       
    45 		 * Executes the specified command.
       
    46 		 *
       
    47 		 * @method execCommand
       
    48 		 * @param {String} command Command to execute.
       
    49 		 * @param {Boolean} ui Optional user interface state.
       
    50 		 * @param {Object} value Optional value for command.
       
    51 		 * @return {Boolean} true/false if the command was found or not.
       
    52 		 */
       
    53 		function execCommand(command, ui, value, args) {
       
    54 			var func, customCommand, state = 0;
       
    55 
       
    56 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) {
       
    57 				editor.focus();
       
    58 			}
       
    59 
       
    60 			args = extend({}, args);
       
    61 			args = editor.fire('BeforeExecCommand', {command: command, ui: ui, value: value});
       
    62 			if (args.isDefaultPrevented()) {
       
    63 				return false;
       
    64 			}
       
    65 
       
    66 			customCommand = command.toLowerCase();
       
    67 			if ((func = commands.exec[customCommand])) {
       
    68 				func(customCommand, ui, value);
       
    69 				editor.fire('ExecCommand', {command: command, ui: ui, value: value});
       
    70 				return true;
       
    71 			}
       
    72 
       
    73 			// Plugin commands
       
    74 			each(editor.plugins, function(p) {
       
    75 				if (p.execCommand && p.execCommand(command, ui, value)) {
       
    76 					editor.fire('ExecCommand', {command: command, ui: ui, value: value});
       
    77 					state = true;
       
    78 					return false;
       
    79 				}
       
    80 			});
       
    81 
       
    82 			if (state) {
       
    83 				return state;
       
    84 			}
       
    85 
       
    86 			// Theme commands
       
    87 			if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) {
       
    88 				editor.fire('ExecCommand', {command: command, ui: ui, value: value});
       
    89 				return true;
       
    90 			}
       
    91 
       
    92 			// Browser commands
       
    93 			try {
       
    94 				state = editor.getDoc().execCommand(command, ui, value);
       
    95 			} catch (ex) {
       
    96 				// Ignore old IE errors
       
    97 			}
       
    98 
       
    99 			if (state) {
       
   100 				editor.fire('ExecCommand', {command: command, ui: ui, value: value});
       
   101 				return true;
       
   102 			}
       
   103 
       
   104 			return false;
       
   105 		}
       
   106 
       
   107 		/**
       
   108 		 * Queries the current state for a command for example if the current selection is "bold".
       
   109 		 *
       
   110 		 * @method queryCommandState
       
   111 		 * @param {String} command Command to check the state of.
       
   112 		 * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
       
   113 		 */
       
   114 		function queryCommandState(command) {
       
   115 			var func;
       
   116 
       
   117 			// Is hidden then return undefined
       
   118 			if (editor._isHidden()) {
       
   119 				return;
       
   120 			}
       
   121 
       
   122 			command = command.toLowerCase();
       
   123 			if ((func = commands.state[command])) {
       
   124 				return func(command);
       
   125 			}
       
   126 
       
   127 			// Browser commands
       
   128 			try {
       
   129 				return editor.getDoc().queryCommandState(command);
       
   130 			} catch (ex) {
       
   131 				// Fails sometimes see bug: 1896577
       
   132 			}
       
   133 
       
   134 			return false;
       
   135 		}
       
   136 
       
   137 		/**
       
   138 		 * Queries the command value for example the current fontsize.
       
   139 		 *
       
   140 		 * @method queryCommandValue
       
   141 		 * @param {String} command Command to check the value of.
       
   142 		 * @return {Object} Command value of false if it's not found.
       
   143 		 */
       
   144 		function queryCommandValue(command) {
       
   145 			var func;
       
   146 
       
   147 			// Is hidden then return undefined
       
   148 			if (editor._isHidden()) {
       
   149 				return;
       
   150 			}
       
   151 
       
   152 			command = command.toLowerCase();
       
   153 			if ((func = commands.value[command])) {
       
   154 				return func(command);
       
   155 			}
       
   156 
       
   157 			// Browser commands
       
   158 			try {
       
   159 				return editor.getDoc().queryCommandValue(command);
       
   160 			} catch (ex) {
       
   161 				// Fails sometimes see bug: 1896577
       
   162 			}
       
   163 		}
       
   164 
       
   165 		/**
       
   166 		 * Adds commands to the command collection.
       
   167 		 *
       
   168 		 * @method addCommands
       
   169 		 * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
       
   170 		 * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
       
   171 		 */
       
   172 		function addCommands(command_list, type) {
       
   173 			type = type || 'exec';
       
   174 
       
   175 			each(command_list, function(callback, command) {
       
   176 				each(command.toLowerCase().split(','), function(command) {
       
   177 					commands[type][command] = callback;
       
   178 				});
       
   179 			});
       
   180 		}
       
   181 
       
   182 		function addCommand(command, callback, scope) {
       
   183 			command = command.toLowerCase();
       
   184 			commands.exec[command] = function(command, ui, value, args) {
       
   185 				return callback.call(scope || editor, ui, value, args);
       
   186 			};
       
   187 		}
       
   188 
       
   189 		/**
       
   190 		 * Returns true/false if the command is supported or not.
       
   191 		 *
       
   192 		 * @method queryCommandSupported
       
   193 		 * @param {String} cmd Command that we check support for.
       
   194 		 * @return {Boolean} true/false if the command is supported or not.
       
   195 		 */
       
   196 		function queryCommandSupported(command) {
       
   197 			command = command.toLowerCase();
       
   198 
       
   199 			if (commands.exec[command]) {
       
   200 				return true;
       
   201 			}
       
   202 
       
   203 			// Browser commands
       
   204 			try {
       
   205 				return editor.getDoc().queryCommandSupported(command);
       
   206 			} catch (ex) {
       
   207 				// Fails sometimes see bug: 1896577
       
   208 			}
       
   209 
       
   210 			return false;
       
   211 		}
       
   212 
       
   213 		function addQueryStateHandler(command, callback, scope) {
       
   214 			command = command.toLowerCase();
       
   215 			commands.state[command] = function() {
       
   216 				return callback.call(scope || editor);
       
   217 			};
       
   218 		}
       
   219 
       
   220 		function addQueryValueHandler(command, callback, scope) {
       
   221 			command = command.toLowerCase();
       
   222 			commands.value[command] = function() {
       
   223 				return callback.call(scope || editor);
       
   224 			};
       
   225 		}
       
   226 
       
   227 		function hasCustomCommand(command) {
       
   228 			command = command.toLowerCase();
       
   229 			return !!commands.exec[command];
       
   230 		}
       
   231 
       
   232 		// Expose public methods
       
   233 		extend(this, {
       
   234 			execCommand: execCommand,
       
   235 			queryCommandState: queryCommandState,
       
   236 			queryCommandValue: queryCommandValue,
       
   237 			queryCommandSupported: queryCommandSupported,
       
   238 			addCommands: addCommands,
       
   239 			addCommand: addCommand,
       
   240 			addQueryStateHandler: addQueryStateHandler,
       
   241 			addQueryValueHandler: addQueryValueHandler,
       
   242 			hasCustomCommand: hasCustomCommand
       
   243 		});
       
   244 
       
   245 		// Private methods
       
   246 
       
   247 		function execNativeCommand(command, ui, value) {
       
   248 			if (ui === undefined) {
       
   249 				ui = FALSE;
       
   250 			}
       
   251 
       
   252 			if (value === undefined) {
       
   253 				value = null;
       
   254 			}
       
   255 
       
   256 			return editor.getDoc().execCommand(command, ui, value);
       
   257 		}
       
   258 
       
   259 		function isFormatMatch(name) {
       
   260 			return formatter.match(name);
       
   261 		}
       
   262 
       
   263 		function toggleFormat(name, value) {
       
   264 			formatter.toggle(name, value ? {value: value} : undefined);
       
   265 			editor.nodeChanged();
       
   266 		}
       
   267 
       
   268 		function storeSelection(type) {
       
   269 			bookmark = selection.getBookmark(type);
       
   270 		}
       
   271 
       
   272 		function restoreSelection() {
       
   273 			selection.moveToBookmark(bookmark);
       
   274 		}
       
   275 
       
   276 		// Add execCommand overrides
       
   277 		addCommands({
       
   278 			// Ignore these, added for compatibility
       
   279 			'mceResetDesignMode,mceBeginUndoLevel': function() {},
       
   280 
       
   281 			// Add undo manager logic
       
   282 			'mceEndUndoLevel,mceAddUndoLevel': function() {
       
   283 				editor.undoManager.add();
       
   284 			},
       
   285 
       
   286 			'Cut,Copy,Paste': function(command) {
       
   287 				var doc = editor.getDoc(), failed;
       
   288 
       
   289 				// Try executing the native command
       
   290 				try {
       
   291 					execNativeCommand(command);
       
   292 				} catch (ex) {
       
   293 					// Command failed
       
   294 					failed = TRUE;
       
   295 				}
       
   296 
       
   297 				// Present alert message about clipboard access not being available
       
   298 				if (failed || !doc.queryCommandSupported(command)) {
       
   299 					var msg = editor.translate(
       
   300 						"Your browser doesn't support direct access to the clipboard. " +
       
   301 						"Please use the Ctrl+X/C/V keyboard shortcuts instead."
       
   302 					);
       
   303 
       
   304 					if (Env.mac) {
       
   305 						msg = msg.replace(/Ctrl\+/g, '\u2318+');
       
   306 					}
       
   307 
       
   308 					editor.windowManager.alert(msg);
       
   309 				}
       
   310 			},
       
   311 
       
   312 			// Override unlink command
       
   313 			unlink: function() {
       
   314 				if (selection.isCollapsed()) {
       
   315 					var elm = selection.getNode();
       
   316 					if (elm.tagName == 'A') {
       
   317 						editor.dom.remove(elm, true);
       
   318 					}
       
   319 
       
   320 					return;
       
   321 				}
       
   322 
       
   323 				formatter.remove("link");
       
   324 			},
       
   325 
       
   326 			// Override justify commands to use the text formatter engine
       
   327 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
       
   328 				var align = command.substring(7);
       
   329 
       
   330 				if (align == 'full') {
       
   331 					align = 'justify';
       
   332 				}
       
   333 
       
   334 				// Remove all other alignments first
       
   335 				each('left,center,right,justify'.split(','), function(name) {
       
   336 					if (align != name) {
       
   337 						formatter.remove('align' + name);
       
   338 					}
       
   339 				});
       
   340 
       
   341 				toggleFormat('align' + align);
       
   342 				execCommand('mceRepaint');
       
   343 			},
       
   344 
       
   345 			// Override list commands to fix WebKit bug
       
   346 			'InsertUnorderedList,InsertOrderedList': function(command) {
       
   347 				var listElm, listParent;
       
   348 
       
   349 				execNativeCommand(command);
       
   350 
       
   351 				// WebKit produces lists within block elements so we need to split them
       
   352 				// we will replace the native list creation logic to custom logic later on
       
   353 				// TODO: Remove this when the list creation logic is removed
       
   354 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
       
   355 				if (listElm) {
       
   356 					listParent = listElm.parentNode;
       
   357 
       
   358 					// If list is within a text block then split that block
       
   359 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
       
   360 						storeSelection();
       
   361 						dom.split(listParent, listElm);
       
   362 						restoreSelection();
       
   363 					}
       
   364 				}
       
   365 			},
       
   366 
       
   367 			// Override commands to use the text formatter engine
       
   368 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
       
   369 				toggleFormat(command);
       
   370 			},
       
   371 
       
   372 			// Override commands to use the text formatter engine
       
   373 			'ForeColor,HiliteColor,FontName': function(command, ui, value) {
       
   374 				toggleFormat(command, value);
       
   375 			},
       
   376 
       
   377 			FontSize: function(command, ui, value) {
       
   378 				var fontClasses, fontSizes;
       
   379 
       
   380 				// Convert font size 1-7 to styles
       
   381 				if (value >= 1 && value <= 7) {
       
   382 					fontSizes = explode(settings.font_size_style_values);
       
   383 					fontClasses = explode(settings.font_size_classes);
       
   384 
       
   385 					if (fontClasses) {
       
   386 						value = fontClasses[value - 1] || value;
       
   387 					} else {
       
   388 						value = fontSizes[value - 1] || value;
       
   389 					}
       
   390 				}
       
   391 
       
   392 				toggleFormat(command, value);
       
   393 			},
       
   394 
       
   395 			RemoveFormat: function(command) {
       
   396 				formatter.remove(command);
       
   397 			},
       
   398 
       
   399 			mceBlockQuote: function() {
       
   400 				toggleFormat('blockquote');
       
   401 			},
       
   402 
       
   403 			FormatBlock: function(command, ui, value) {
       
   404 				return toggleFormat(value || 'p');
       
   405 			},
       
   406 
       
   407 			mceCleanup: function() {
       
   408 				var bookmark = selection.getBookmark();
       
   409 
       
   410 				editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
       
   411 
       
   412 				selection.moveToBookmark(bookmark);
       
   413 			},
       
   414 
       
   415 			mceRemoveNode: function(command, ui, value) {
       
   416 				var node = value || selection.getNode();
       
   417 
       
   418 				// Make sure that the body node isn't removed
       
   419 				if (node != editor.getBody()) {
       
   420 					storeSelection();
       
   421 					editor.dom.remove(node, TRUE);
       
   422 					restoreSelection();
       
   423 				}
       
   424 			},
       
   425 
       
   426 			mceSelectNodeDepth: function(command, ui, value) {
       
   427 				var counter = 0;
       
   428 
       
   429 				dom.getParent(selection.getNode(), function(node) {
       
   430 					if (node.nodeType == 1 && counter++ == value) {
       
   431 						selection.select(node);
       
   432 						return FALSE;
       
   433 					}
       
   434 				}, editor.getBody());
       
   435 			},
       
   436 
       
   437 			mceSelectNode: function(command, ui, value) {
       
   438 				selection.select(value);
       
   439 			},
       
   440 
       
   441 			mceInsertContent: function(command, ui, value) {
       
   442 				var parser, serializer, parentNode, rootNode, fragment, args;
       
   443 				var marker, rng, node, node2, bookmarkHtml, merge;
       
   444 				var textInlineElements = editor.schema.getTextInlineElements();
       
   445 
       
   446 				function trimOrPaddLeftRight(html) {
       
   447 					var rng, container, offset;
       
   448 
       
   449 					rng = selection.getRng(true);
       
   450 					container = rng.startContainer;
       
   451 					offset = rng.startOffset;
       
   452 
       
   453 					function hasSiblingText(siblingName) {
       
   454 						return container[siblingName] && container[siblingName].nodeType == 3;
       
   455 					}
       
   456 
       
   457 					if (container.nodeType == 3) {
       
   458 						if (offset > 0) {
       
   459 							html = html.replace(/^&nbsp;/, ' ');
       
   460 						} else if (!hasSiblingText('previousSibling')) {
       
   461 							html = html.replace(/^ /, '&nbsp;');
       
   462 						}
       
   463 
       
   464 						if (offset < container.length) {
       
   465 							html = html.replace(/&nbsp;(<br>|)$/, ' ');
       
   466 						} else if (!hasSiblingText('nextSibling')) {
       
   467 							html = html.replace(/(&nbsp;| )(<br>|)$/, '&nbsp;');
       
   468 						}
       
   469 					}
       
   470 
       
   471 					return html;
       
   472 				}
       
   473 
       
   474 				// Removes &nbsp; from a [b] c -> a &nbsp;c -> a c
       
   475 				function trimNbspAfterDeleteAndPaddValue() {
       
   476 					var rng, container, offset;
       
   477 
       
   478 					rng = selection.getRng(true);
       
   479 					container = rng.startContainer;
       
   480 					offset = rng.startOffset;
       
   481 
       
   482 					if (container.nodeType == 3 && rng.collapsed) {
       
   483 						if (container.data[offset] === '\u00a0') {
       
   484 							container.deleteData(offset, 1);
       
   485 
       
   486 							if (!/[\u00a0| ]$/.test(value)) {
       
   487 								value += ' ';
       
   488 							}
       
   489 						} else if (container.data[offset - 1] === '\u00a0') {
       
   490 							container.deleteData(offset - 1, 1);
       
   491 
       
   492 							if (!/[\u00a0| ]$/.test(value)) {
       
   493 								value = ' ' + value;
       
   494 							}
       
   495 						}
       
   496 					}
       
   497 				}
       
   498 
       
   499 				function markInlineFormatElements(fragment) {
       
   500 					if (merge) {
       
   501 						for (node = fragment.firstChild; node; node = node.walk(true)) {
       
   502 							if (textInlineElements[node.name]) {
       
   503 								node.attr('data-mce-new', "true");
       
   504 							}
       
   505 						}
       
   506 					}
       
   507 				}
       
   508 
       
   509 				function reduceInlineTextElements() {
       
   510 					if (merge) {
       
   511 						var root = editor.getBody(), elementUtils = new ElementUtils(dom);
       
   512 
       
   513 						each(dom.select('*[data-mce-new]'), function(node) {
       
   514 							node.removeAttribute('data-mce-new');
       
   515 
       
   516 							for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
       
   517 								if (elementUtils.compare(testNode, node)) {
       
   518 									dom.remove(node, true);
       
   519 								}
       
   520 							}
       
   521 						});
       
   522 					}
       
   523 				}
       
   524 
       
   525 				if (typeof value != 'string') {
       
   526 					merge = value.merge;
       
   527 					value = value.content;
       
   528 				}
       
   529 
       
   530 				// Check for whitespace before/after value
       
   531 				if (/^ | $/.test(value)) {
       
   532 					value = trimOrPaddLeftRight(value);
       
   533 				}
       
   534 
       
   535 				// Setup parser and serializer
       
   536 				parser = editor.parser;
       
   537 				serializer = new Serializer({}, editor.schema);
       
   538 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#x200B;</span>';
       
   539 
       
   540 				// Run beforeSetContent handlers on the HTML to be inserted
       
   541 				args = {content: value, format: 'html', selection: true};
       
   542 				editor.fire('BeforeSetContent', args);
       
   543 				value = args.content;
       
   544 
       
   545 				// Add caret at end of contents if it's missing
       
   546 				if (value.indexOf('{$caret}') == -1) {
       
   547 					value += '{$caret}';
       
   548 				}
       
   549 
       
   550 				// Replace the caret marker with a span bookmark element
       
   551 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
       
   552 
       
   553 				// If selection is at <body>|<p></p> then move it into <body><p>|</p>
       
   554 				rng = selection.getRng();
       
   555 				var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
       
   556 				var body = editor.getBody();
       
   557 				if (caretElement === body && selection.isCollapsed()) {
       
   558 					if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) {
       
   559 						rng = dom.createRng();
       
   560 						rng.setStart(body.firstChild, 0);
       
   561 						rng.setEnd(body.firstChild, 0);
       
   562 						selection.setRng(rng);
       
   563 					}
       
   564 				}
       
   565 
       
   566 				// Insert node maker where we will insert the new HTML and get it's parent
       
   567 				if (!selection.isCollapsed()) {
       
   568 					editor.getDoc().execCommand('Delete', false, null);
       
   569 					trimNbspAfterDeleteAndPaddValue();
       
   570 				}
       
   571 
       
   572 				parentNode = selection.getNode();
       
   573 
       
   574 				// Parse the fragment within the context of the parent node
       
   575 				var parserArgs = {context: parentNode.nodeName.toLowerCase()};
       
   576 				fragment = parser.parse(value, parserArgs);
       
   577 
       
   578 				markInlineFormatElements(fragment);
       
   579 
       
   580 				// Move the caret to a more suitable location
       
   581 				node = fragment.lastChild;
       
   582 				if (node.attr('id') == 'mce_marker') {
       
   583 					marker = node;
       
   584 
       
   585 					for (node = node.prev; node; node = node.walk(true)) {
       
   586 						if (node.type == 3 || !dom.isBlock(node.name)) {
       
   587 							if (editor.schema.isValidChild(node.parent.name, 'span')) {
       
   588 								node.parent.insert(marker, node, node.name === 'br');
       
   589 							}
       
   590 							break;
       
   591 						}
       
   592 					}
       
   593 				}
       
   594 
       
   595 				// If parser says valid we can insert the contents into that parent
       
   596 				if (!parserArgs.invalid) {
       
   597 					value = serializer.serialize(fragment);
       
   598 
       
   599 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
       
   600 					node = parentNode.firstChild;
       
   601 					node2 = parentNode.lastChild;
       
   602 					if (!node || (node === node2 && node.nodeName === 'BR')) {
       
   603 						dom.setHTML(parentNode, value);
       
   604 					} else {
       
   605 						selection.setContent(value);
       
   606 					}
       
   607 				} else {
       
   608 					// If the fragment was invalid within that context then we need
       
   609 					// to parse and process the parent it's inserted into
       
   610 
       
   611 					// Insert bookmark node and get the parent
       
   612 					selection.setContent(bookmarkHtml);
       
   613 					parentNode = selection.getNode();
       
   614 					rootNode = editor.getBody();
       
   615 
       
   616 					// Opera will return the document node when selection is in root
       
   617 					if (parentNode.nodeType == 9) {
       
   618 						parentNode = node = rootNode;
       
   619 					} else {
       
   620 						node = parentNode;
       
   621 					}
       
   622 
       
   623 					// Find the ancestor just before the root element
       
   624 					while (node !== rootNode) {
       
   625 						parentNode = node;
       
   626 						node = node.parentNode;
       
   627 					}
       
   628 
       
   629 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
       
   630 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
       
   631 					value = serializer.serialize(
       
   632 						parser.parse(
       
   633 							// Need to replace by using a function since $ in the contents would otherwise be a problem
       
   634 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
       
   635 								return serializer.serialize(fragment);
       
   636 							})
       
   637 						)
       
   638 					);
       
   639 
       
   640 					// Set the inner/outer HTML depending on if we are in the root or not
       
   641 					if (parentNode == rootNode) {
       
   642 						dom.setHTML(rootNode, value);
       
   643 					} else {
       
   644 						dom.setOuterHTML(parentNode, value);
       
   645 					}
       
   646 				}
       
   647 
       
   648 				reduceInlineTextElements();
       
   649 
       
   650 				marker = dom.get('mce_marker');
       
   651 				selection.scrollIntoView(marker);
       
   652 
       
   653 				// Move selection before marker and remove it
       
   654 				rng = dom.createRng();
       
   655 
       
   656 				// If previous sibling is a text node set the selection to the end of that node
       
   657 				node = marker.previousSibling;
       
   658 				if (node && node.nodeType == 3) {
       
   659 					rng.setStart(node, node.nodeValue.length);
       
   660 
       
   661 					// TODO: Why can't we normalize on IE
       
   662 					if (!isIE) {
       
   663 						node2 = marker.nextSibling;
       
   664 						if (node2 && node2.nodeType == 3) {
       
   665 							node.appendData(node2.data);
       
   666 							node2.parentNode.removeChild(node2);
       
   667 						}
       
   668 					}
       
   669 				} else {
       
   670 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
       
   671 					rng.setStartBefore(marker);
       
   672 					rng.setEndBefore(marker);
       
   673 				}
       
   674 
       
   675 				// Remove the marker node and set the new range
       
   676 				dom.remove(marker);
       
   677 				selection.setRng(rng);
       
   678 
       
   679 				// Dispatch after event and add any visual elements needed
       
   680 				editor.fire('SetContent', args);
       
   681 				editor.addVisual();
       
   682 			},
       
   683 
       
   684 			mceInsertRawHTML: function(command, ui, value) {
       
   685 				selection.setContent('tiny_mce_marker');
       
   686 				editor.setContent(
       
   687 					editor.getContent().replace(/tiny_mce_marker/g, function() {
       
   688 						return value;
       
   689 					})
       
   690 				);
       
   691 			},
       
   692 
       
   693 			mceToggleFormat: function(command, ui, value) {
       
   694 				toggleFormat(value);
       
   695 			},
       
   696 
       
   697 			mceSetContent: function(command, ui, value) {
       
   698 				editor.setContent(value);
       
   699 			},
       
   700 
       
   701 			'Indent,Outdent': function(command) {
       
   702 				var intentValue, indentUnit, value;
       
   703 
       
   704 				// Setup indent level
       
   705 				intentValue = settings.indentation;
       
   706 				indentUnit = /[a-z%]+$/i.exec(intentValue);
       
   707 				intentValue = parseInt(intentValue, 10);
       
   708 
       
   709 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
       
   710 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
       
   711 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
       
   712 						formatter.apply('div');
       
   713 					}
       
   714 
       
   715 					each(selection.getSelectedBlocks(), function(element) {
       
   716 						if (element.nodeName != "LI") {
       
   717 							var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';
       
   718 
       
   719 							indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';
       
   720 
       
   721 							if (command == 'outdent') {
       
   722 								value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
       
   723 								dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
       
   724 							} else {
       
   725 								value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
       
   726 								dom.setStyle(element, indentStyleName, value);
       
   727 							}
       
   728 						}
       
   729 					});
       
   730 				} else {
       
   731 					execNativeCommand(command);
       
   732 				}
       
   733 			},
       
   734 
       
   735 			mceRepaint: function() {
       
   736 				if (isGecko) {
       
   737 					try {
       
   738 						storeSelection(TRUE);
       
   739 
       
   740 						if (selection.getSel()) {
       
   741 							selection.getSel().selectAllChildren(editor.getBody());
       
   742 						}
       
   743 
       
   744 						selection.collapse(TRUE);
       
   745 						restoreSelection();
       
   746 					} catch (ex) {
       
   747 						// Ignore
       
   748 					}
       
   749 				}
       
   750 			},
       
   751 
       
   752 			InsertHorizontalRule: function() {
       
   753 				editor.execCommand('mceInsertContent', false, '<hr />');
       
   754 			},
       
   755 
       
   756 			mceToggleVisualAid: function() {
       
   757 				editor.hasVisual = !editor.hasVisual;
       
   758 				editor.addVisual();
       
   759 			},
       
   760 
       
   761 			mceReplaceContent: function(command, ui, value) {
       
   762 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
       
   763 			},
       
   764 
       
   765 			mceInsertLink: function(command, ui, value) {
       
   766 				var anchor;
       
   767 
       
   768 				if (typeof value == 'string') {
       
   769 					value = {href: value};
       
   770 				}
       
   771 
       
   772 				anchor = dom.getParent(selection.getNode(), 'a');
       
   773 
       
   774 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
       
   775 				value.href = value.href.replace(' ', '%20');
       
   776 
       
   777 				// Remove existing links if there could be child links or that the href isn't specified
       
   778 				if (!anchor || !value.href) {
       
   779 					formatter.remove('link');
       
   780 				}
       
   781 
       
   782 				// Apply new link to selection
       
   783 				if (value.href) {
       
   784 					formatter.apply('link', value, anchor);
       
   785 				}
       
   786 			},
       
   787 
       
   788 			selectAll: function() {
       
   789 				var root = dom.getRoot(), rng;
       
   790 
       
   791 				if (selection.getRng().setStart) {
       
   792 					rng = dom.createRng();
       
   793 					rng.setStart(root, 0);
       
   794 					rng.setEnd(root, root.childNodes.length);
       
   795 					selection.setRng(rng);
       
   796 				} else {
       
   797 					// IE will render it's own root level block elements and sometimes
       
   798 					// even put font elements in them when the user starts typing. So we need to
       
   799 					// move the selection to a more suitable element from this:
       
   800 					// <body>|<p></p></body> to this: <body><p>|</p></body>
       
   801 					rng = selection.getRng();
       
   802 					if (!rng.item) {
       
   803 						rng.moveToElementText(root);
       
   804 						rng.select();
       
   805 					}
       
   806 				}
       
   807 			},
       
   808 
       
   809 			"delete": function() {
       
   810 				execNativeCommand("Delete");
       
   811 
       
   812 				// Check if body is empty after the delete call if so then set the contents
       
   813 				// to an empty string and move the caret to any block produced by that operation
       
   814 				// this fixes the issue with root blocks not being properly produced after a delete call on IE
       
   815 				var body = editor.getBody();
       
   816 
       
   817 				if (dom.isEmpty(body)) {
       
   818 					editor.setContent('');
       
   819 
       
   820 					if (body.firstChild && dom.isBlock(body.firstChild)) {
       
   821 						editor.selection.setCursorLocation(body.firstChild, 0);
       
   822 					} else {
       
   823 						editor.selection.setCursorLocation(body, 0);
       
   824 					}
       
   825 				}
       
   826 			},
       
   827 
       
   828 			mceNewDocument: function() {
       
   829 				editor.setContent('');
       
   830 			},
       
   831 
       
   832 			InsertLineBreak: function(command, ui, value) {
       
   833 				// We load the current event in from EnterKey.js when appropriate to heed
       
   834 				// certain event-specific variations such as ctrl-enter in a list
       
   835 				var evt = value;
       
   836 				var brElm, extraBr, marker;
       
   837 				var rng = selection.getRng(true);
       
   838 				new RangeUtils(dom).normalize(rng);
       
   839 
       
   840 				var offset = rng.startOffset;
       
   841 				var container = rng.startContainer;
       
   842 
       
   843 				// Resolve node index
       
   844 				if (container.nodeType == 1 && container.hasChildNodes()) {
       
   845 					var isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
       
   846 
       
   847 					container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
       
   848 					if (isAfterLastNodeInContainer && container.nodeType == 3) {
       
   849 						offset = container.nodeValue.length;
       
   850 					} else {
       
   851 						offset = 0;
       
   852 					}
       
   853 				}
       
   854 
       
   855 				var parentBlock = dom.getParent(container, dom.isBlock);
       
   856 				var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
       
   857 				var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
       
   858 				var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
       
   859 
       
   860 				// Enter inside block contained within a LI then split or insert before/after LI
       
   861 				var isControlKey = evt && evt.ctrlKey;
       
   862 				if (containerBlockName == 'LI' && !isControlKey) {
       
   863 					parentBlock = containerBlock;
       
   864 					parentBlockName = containerBlockName;
       
   865 				}
       
   866 
       
   867 				// Walks the parent block to the right and look for BR elements
       
   868 				function hasRightSideContent() {
       
   869 					var walker = new TreeWalker(container, parentBlock), node;
       
   870 					var nonEmptyElementsMap = editor.schema.getNonEmptyElements();
       
   871 
       
   872 					while ((node = walker.next())) {
       
   873 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
       
   874 							return true;
       
   875 						}
       
   876 					}
       
   877 				}
       
   878 
       
   879 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
       
   880 					// Insert extra BR element at the end block elements
       
   881 					if (!isOldIE && !hasRightSideContent()) {
       
   882 						brElm = dom.create('br');
       
   883 						rng.insertNode(brElm);
       
   884 						rng.setStartAfter(brElm);
       
   885 						rng.setEndAfter(brElm);
       
   886 						extraBr = true;
       
   887 					}
       
   888 				}
       
   889 
       
   890 				brElm = dom.create('br');
       
   891 				rng.insertNode(brElm);
       
   892 
       
   893 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
       
   894 				var documentMode = dom.doc.documentMode;
       
   895 				if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
       
   896 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
       
   897 				}
       
   898 
       
   899 				// Insert temp marker and scroll to that
       
   900 				marker = dom.create('span', {}, '&nbsp;');
       
   901 				brElm.parentNode.insertBefore(marker, brElm);
       
   902 				selection.scrollIntoView(marker);
       
   903 				dom.remove(marker);
       
   904 
       
   905 				if (!extraBr) {
       
   906 					rng.setStartAfter(brElm);
       
   907 					rng.setEndAfter(brElm);
       
   908 				} else {
       
   909 					rng.setStartBefore(brElm);
       
   910 					rng.setEndBefore(brElm);
       
   911 				}
       
   912 
       
   913 				selection.setRng(rng);
       
   914 				editor.undoManager.add();
       
   915 
       
   916 				return TRUE;
       
   917 			}
       
   918 		});
       
   919 
       
   920 		// Add queryCommandState overrides
       
   921 		addCommands({
       
   922 			// Override justify commands
       
   923 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
       
   924 				var name = 'align' + command.substring(7);
       
   925 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
       
   926 				var matches = map(nodes, function(node) {
       
   927 					return !!formatter.matchNode(node, name);
       
   928 				});
       
   929 				return inArray(matches, TRUE) !== -1;
       
   930 			},
       
   931 
       
   932 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
       
   933 				return isFormatMatch(command);
       
   934 			},
       
   935 
       
   936 			mceBlockQuote: function() {
       
   937 				return isFormatMatch('blockquote');
       
   938 			},
       
   939 
       
   940 			Outdent: function() {
       
   941 				var node;
       
   942 
       
   943 				if (settings.inline_styles) {
       
   944 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
       
   945 						return TRUE;
       
   946 					}
       
   947 
       
   948 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
       
   949 						return TRUE;
       
   950 					}
       
   951 				}
       
   952 
       
   953 				return (
       
   954 					queryCommandState('InsertUnorderedList') ||
       
   955 					queryCommandState('InsertOrderedList') ||
       
   956 					(!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
       
   957 				);
       
   958 			},
       
   959 
       
   960 			'InsertUnorderedList,InsertOrderedList': function(command) {
       
   961 				var list = dom.getParent(selection.getNode(), 'ul,ol');
       
   962 
       
   963 				return list &&
       
   964 					(
       
   965 						command === 'insertunorderedlist' && list.tagName === 'UL' ||
       
   966 						command === 'insertorderedlist' && list.tagName === 'OL'
       
   967 					);
       
   968 			}
       
   969 		}, 'state');
       
   970 
       
   971 		// Add queryCommandValue overrides
       
   972 		addCommands({
       
   973 			'FontSize,FontName': function(command) {
       
   974 				var value = 0, parent;
       
   975 
       
   976 				if ((parent = dom.getParent(selection.getNode(), 'span'))) {
       
   977 					if (command == 'fontsize') {
       
   978 						value = parent.style.fontSize;
       
   979 					} else {
       
   980 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
       
   981 					}
       
   982 				}
       
   983 
       
   984 				return value;
       
   985 			}
       
   986 		}, 'value');
       
   987 
       
   988 		// Add undo manager logic
       
   989 		addCommands({
       
   990 			Undo: function() {
       
   991 				editor.undoManager.undo();
       
   992 			},
       
   993 
       
   994 			Redo: function() {
       
   995 				editor.undoManager.redo();
       
   996 			}
       
   997 		});
       
   998 	};
       
   999 });