diff -r fd8fb93e1b6a -r a361355b55c7 src/pyams_skin/resources/js/ext/tinymce/dev/classes/EditorCommands.js
--- /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(/^ /, ' ');
+ } else if (!hasSiblingText('previousSibling')) {
+ html = html.replace(/^ /, ' ');
+ }
+
+ if (offset < container.length) {
+ html = html.replace(/ (
|)$/, ' ');
+ } else if (!hasSiblingText('nextSibling')) {
+ html = html.replace(/( | )(
|)$/, ' ');
+ }
+ }
+
+ return html;
+ }
+
+ // Removes from a [b] c -> a 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 = '';
+
+ // 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
|
+ 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(//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, '|
+ 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', {}, ' '); + 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(); + } + }); + }; +});