--- /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(/ (<br>|)$/, ' ');
+ } else if (!hasSiblingText('nextSibling')) {
+ html = html.replace(/( | )(<br>|)$/, ' ');
+ }
+ }
+
+ 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 = '<span id="mce_marker" data-mce-type="bookmark">​</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', {}, ' ');
+ 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();
+ }
+ });
+ };
+});