123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- /**
- * EditorCommands.js
- *
- * Copyright 2009, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://tinymce.moxiecode.com/license
- * Contributing: http://tinymce.moxiecode.com/contributing
- */
- (function(tinymce) {
- // Added for compression purposes
- var each = tinymce.each, undefined, TRUE = true, FALSE = false;
- /**
- * 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
- */
- tinymce.EditorCommands = function(editor) {
- var dom = editor.dom,
- selection = editor.selection,
- commands = {state: {}, exec : {}, value : {}},
- settings = editor.settings,
- formatter = editor.formatter,
- bookmark;
- /**
- * 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) {
- var func;
- command = command.toLowerCase();
- if (func = commands.exec[command]) {
- func(command, ui, 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;
- command = command.toLowerCase();
- if (func = commands.state[command])
- return func(command);
- return -1;
- };
- /**
- * 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;
- command = command.toLowerCase();
- if (func = commands.value[command])
- return func(command);
- return FALSE;
- };
- /**
- * 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;
- });
- });
- };
- // Expose public methods
- tinymce.extend(this, {
- execCommand : execCommand,
- queryCommandState : queryCommandState,
- queryCommandValue : queryCommandValue,
- addCommands : addCommands
- });
- // 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);
- };
- 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)) {
- if (tinymce.isGecko) {
- editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
- if (state)
- open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
- });
- } else
- editor.windowManager.alert(editor.getLang('clipboard_no_support'));
- }
- },
- // Override unlink command
- unlink : function(command) {
- if (selection.isCollapsed())
- selection.select(selection.getNode());
- execNativeCommand(command);
- selection.collapse(FALSE);
- },
- // Override justify commands to use the text formatter engine
- 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
- var align = command.substring(7);
- // Remove all other alignments first
- each('left,center,right,full'.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 = tinymce.explode(settings.font_size_style_values);
- fontClasses = tinymce.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(command) {
- 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,
- marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
- // Setup parser and serializer
- parser = editor.parser;
- serializer = new tinymce.html.Serializer({}, editor.schema);
- bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
- // Run beforeSetContent handlers on the HTML to be inserted
- args = {content: value, format: 'html'};
- selection.onBeforeSetContent.dispatch(selection, 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);
- // Insert node maker where we will insert the new HTML and get it's parent
- if (!selection.isCollapsed())
- editor.getDoc().execCommand('Delete', false, null);
- parentNode = selection.getNode();
- // Parse the fragment within the context of the parent node
- args = {context : parentNode.nodeName.toLowerCase()};
- fragment = parser.parse(value, args);
- // 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)) {
- node.parent.insert(marker, node, node.name === 'br');
- break;
- }
- }
- }
- // If parser says valid we can insert the contents into that parent
- if (!args.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 = editor.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);
- }
- marker = dom.get('mce_marker');
- // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
- nodeRect = dom.getRect(marker);
- viewPortRect = dom.getViewPort(editor.getWin());
- // Check if node is out side the viewport if it is then scroll to it
- if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
- (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
- viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
- viewportBodyElement.scrollLeft = nodeRect.x;
- viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
- }
- // 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);
- } 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
- selection.onSetContent.dispatch(selection, 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 }));
- },
- 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);
- if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
- each(selection.getSelectedBlocks(), function(element) {
- if (command == 'outdent') {
- value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
- dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
- } else
- dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
- });
- } else
- execNativeCommand(command);
- },
- mceRepaint : function() {
- var bookmark;
- if (tinymce.isGecko) {
- try {
- storeSelection(TRUE);
- if (selection.getSel())
- selection.getSel().selectAllChildren(editor.getBody());
- selection.collapse(TRUE);
- restoreSelection();
- } catch (ex) {
- // Ignore
- }
- }
- },
- mceToggleFormat : function(command, ui, value) {
- formatter.toggle(value);
- },
- 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 = dom.createRng();
- rng.setStart(root, 0);
- rng.setEnd(root, root.childNodes.length);
- editor.selection.setRng(rng);
- }
- });
- // Add queryCommandState overrides
- addCommands({
- // Override justify commands
- 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
- return isFormatMatch('align' + command.substring(7));
- },
- '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) > 0)
- return TRUE;
- if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
- return TRUE;
- }
- return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
- },
- 'InsertUnorderedList,InsertOrderedList' : function(command) {
- return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : '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
- if (settings.custom_undo_redo) {
- addCommands({
- Undo : function() {
- editor.undoManager.undo();
- },
- Redo : function() {
- editor.undoManager.redo();
- }
- });
- }
- };
- })(tinymce);
|