]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/classes/EditorCommands.js
Release 6.2.2
[Github/sugarcrm.git] / include / javascript / tiny_mce / classes / EditorCommands.js
1 /**
2  * EditorCommands.js
3  *
4  * Copyright 2009, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  */
10
11 (function(tinymce) {
12         // Added for compression purposes
13         var each = tinymce.each, undefined, TRUE = true, FALSE = false;
14
15         /**
16          * This class enables you to add custom editor commands and it contains
17          * overrides for native browser commands to address various bugs and issues.
18          *
19          * @class tinymce.EditorCommands
20          */
21         tinymce.EditorCommands = function(editor) {
22                 var dom = editor.dom,
23                         selection = editor.selection,
24                         commands = {state: {}, exec : {}, value : {}},
25                         settings = editor.settings,
26                         bookmark;
27
28                 /**
29                  * Executes the specified command.
30                  *
31                  * @method execCommand
32                  * @param {String} command Command to execute.
33                  * @param {Boolean} ui Optional user interface state.
34                  * @param {Object} value Optional value for command.
35                  * @return {Boolean} true/false if the command was found or not.
36                  */
37                 function execCommand(command, ui, value) {
38                         var func;
39
40                         command = command.toLowerCase();
41                         if (func = commands.exec[command]) {
42                                 func(command, ui, value);
43                                 return TRUE;
44                         }
45
46                         return FALSE;
47                 };
48
49                 /**
50                  * Queries the current state for a command for example if the current selection is "bold".
51                  *
52                  * @method queryCommandState
53                  * @param {String} command Command to check the state of.
54                  * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
55                  */
56                 function queryCommandState(command) {
57                         var func;
58
59                         command = command.toLowerCase();
60                         if (func = commands.state[command])
61                                 return func(command);
62
63                         return -1;
64                 };
65
66                 /**
67                  * Queries the command value for example the current fontsize.
68                  *
69                  * @method queryCommandValue
70                  * @param {String} command Command to check the value of.
71                  * @return {Object} Command value of false if it's not found.
72                  */
73                 function queryCommandValue(command) {
74                         var func;
75
76                         command = command.toLowerCase();
77                         if (func = commands.value[command])
78                                 return func(command);
79
80                         return FALSE;
81                 };
82
83                 /**
84                  * Adds commands to the command collection.
85                  *
86                  * @method addCommands
87                  * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
88                  * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
89                  */
90                 function addCommands(command_list, type) {
91                         type = type || 'exec';
92
93                         each(command_list, function(callback, command) {
94                                 each(command.toLowerCase().split(','), function(command) {
95                                         commands[type][command] = callback;
96                                 });
97                         });
98                 };
99
100                 // Expose public methods
101                 tinymce.extend(this, {
102                         execCommand : execCommand,
103                         queryCommandState : queryCommandState,
104                         queryCommandValue : queryCommandValue,
105                         addCommands : addCommands
106                 });
107
108                 // Private methods
109
110                 function execNativeCommand(command, ui, value) {
111                         if (ui === undefined)
112                                 ui = FALSE;
113
114                         if (value === undefined)
115                                 value = null;
116
117                         return editor.getDoc().execCommand(command, ui, value);
118                 };
119
120                 function isFormatMatch(name) {
121                         return editor.formatter.match(name);
122                 };
123
124                 function toggleFormat(name, value) {
125                         editor.formatter.toggle(name, value ? {value : value} : undefined);
126                 };
127
128                 function storeSelection(type) {
129                         bookmark = selection.getBookmark(type);
130                 };
131
132                 function restoreSelection() {
133                         selection.moveToBookmark(bookmark);
134                 };
135
136                 // Add execCommand overrides
137                 addCommands({
138                         // Ignore these, added for compatibility
139                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},
140
141                         // Add undo manager logic
142                         'mceEndUndoLevel,mceAddUndoLevel' : function() {
143                                 editor.undoManager.add();
144                         },
145
146                         'Cut,Copy,Paste' : function(command) {
147                                 var doc = editor.getDoc(), failed;
148
149                                 // Try executing the native command
150                                 try {
151                                         execNativeCommand(command);
152                                 } catch (ex) {
153                                         // Command failed
154                                         failed = TRUE;
155                                 }
156
157                                 // Present alert message about clipboard access not being available
158                                 if (failed || !doc.queryCommandSupported(command)) {
159                                         if (tinymce.isGecko) {
160                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
161                                                         if (state)
162                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
163                                                 });
164                                         } else
165                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
166                                 }
167                         },
168
169                         // Override unlink command
170                         unlink : function(command) {
171                                 if (selection.isCollapsed())
172                                         selection.select(selection.getNode());
173
174                                 execNativeCommand(command);
175                                 selection.collapse(FALSE);
176                         },
177
178                         // Override justify commands to use the text formatter engine
179                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
180                                 var align = command.substring(7);
181
182                                 // Remove all other alignments first
183                                 each('left,center,right,full'.split(','), function(name) {
184                                         if (align != name)
185                                                 editor.formatter.remove('align' + name);
186                                 });
187
188                                 toggleFormat('align' + align);
189                                 execCommand('mceRepaint');
190                         },
191
192                         // Override list commands to fix WebKit bug
193                         'InsertUnorderedList,InsertOrderedList' : function(command) {
194                                 var listElm, listParent;
195
196                                 execNativeCommand(command);
197
198                                 // WebKit produces lists within block elements so we need to split them
199                                 // we will replace the native list creation logic to custom logic later on
200                                 // TODO: Remove this when the list creation logic is removed
201                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');
202                                 if (listElm) {
203                                         listParent = listElm.parentNode;
204
205                                         // If list is within a text block then split that block
206                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
207                                                 storeSelection();
208                                                 dom.split(listParent, listElm);
209                                                 restoreSelection();
210                                         }
211                                 }
212                         },
213
214                         // Override commands to use the text formatter engine
215                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
216                                 toggleFormat(command);
217                         },
218
219                         // Override commands to use the text formatter engine
220                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
221                                 toggleFormat(command, value);
222                         },
223
224                         FontSize : function(command, ui, value) {
225                                 var fontClasses, fontSizes;
226
227                                 // Convert font size 1-7 to styles
228                                 if (value >= 1 && value <= 7) {
229                                         fontSizes = tinymce.explode(settings.font_size_style_values);
230                                         fontClasses = tinymce.explode(settings.font_size_classes);
231
232                                         if (fontClasses)
233                                                 value = fontClasses[value - 1] || value;
234                                         else
235                                                 value = fontSizes[value - 1] || value;
236                                 }
237
238                                 toggleFormat(command, value);
239                         },
240
241                         RemoveFormat : function(command) {
242                                 editor.formatter.remove(command);
243                         },
244
245                         mceBlockQuote : function(command) {
246                                 toggleFormat('blockquote');
247                         },
248
249                         FormatBlock : function(command, ui, value) {
250                                 return toggleFormat(value || 'p');
251                         },
252
253                         mceCleanup : function() {
254                                 var bookmark = selection.getBookmark();
255
256                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
257
258                                 selection.moveToBookmark(bookmark);
259                         },
260
261                         mceRemoveNode : function(command, ui, value) {
262                                 var node = value || selection.getNode();
263
264                                 // Make sure that the body node isn't removed
265                                 if (node != editor.getBody()) {
266                                         storeSelection();
267                                         editor.dom.remove(node, TRUE);
268                                         restoreSelection();
269                                 }
270                         },
271
272                         mceSelectNodeDepth : function(command, ui, value) {
273                                 var counter = 0;
274
275                                 dom.getParent(selection.getNode(), function(node) {
276                                         if (node.nodeType == 1 && counter++ == value) {
277                                                 selection.select(node);
278                                                 return FALSE;
279                                         }
280                                 }, editor.getBody());
281                         },
282
283                         mceSelectNode : function(command, ui, value) {
284                                 selection.select(value);
285                         },
286
287                         mceInsertContent : function(command, ui, value) {
288                                 var caretNode, rng, rootNode, parent, node, rng, nodeRect, viewPortRect, args;
289
290                                 function findSuitableCaretNode(node, root_node, next) {
291                                         var walker = new tinymce.dom.TreeWalker(next ? node.nextSibling : node.previousSibling, root_node);
292
293                                         while ((node = walker.current())) {
294                                                 if ((node.nodeType == 3 && tinymce.trim(node.nodeValue).length) || node.nodeName == 'BR' || node.nodeName == 'IMG')
295                                                         return node;
296
297                                                 if (next)
298                                                         walker.next();
299                                                 else
300                                                         walker.prev();
301                                         }
302                                 };
303
304                                 args = {content: value, format: 'html'};
305                                 selection.onBeforeSetContent.dispatch(selection, args);
306                                 value = args.content;
307
308                                 // Add caret at end of contents if it's missing
309                                 if (value.indexOf('{$caret}') == -1)
310                                         value += '{$caret}';
311
312                                 // Set the content at selection to a span and replace it's contents with the value
313                                 selection.setContent('<span id="__mce">\uFEFF</span>', {no_events : false});
314                                 dom.setOuterHTML('__mce', value.replace(/\{\$caret\}/, '<span data-mce-type="bookmark" id="__mce">\uFEFF</span>'));
315
316                                 caretNode = dom.select('#__mce')[0];
317                                 rootNode = dom.getRoot();
318
319                                 // Move the caret into the last suitable location within the previous sibling if it's a block since the block might be split
320                                 if (caretNode.previousSibling && dom.isBlock(caretNode.previousSibling) || caretNode.parentNode == rootNode) {
321                                         node = findSuitableCaretNode(caretNode, rootNode);
322                                         if (node) {
323                                                 if (node.nodeName == 'BR')
324                                                         node.parentNode.insertBefore(caretNode, node);
325                                                 else
326                                                         dom.insertAfter(caretNode, node);
327                                         }
328                                 }
329
330                                 // Find caret root parent and clean it up using the serializer to avoid nesting
331                                 while (caretNode) {
332                                         if (caretNode === rootNode) {
333                                                 // Clean up the parent element by parsing and serializing it
334                                                 // This will remove invalid elements/attributes and fix nesting issues
335                                                 dom.setOuterHTML(parent, 
336                                                         new tinymce.html.Serializer({}, editor.schema).serialize(
337                                                                 editor.parser.parse(dom.getOuterHTML(parent))
338                                                         )
339                                                 );
340
341                                                 break;
342                                         }
343
344                                         parent = caretNode;
345                                         caretNode = caretNode.parentNode;
346                                 }
347
348                                 // Find caret after cleanup and move selection to that location
349                                 caretNode = dom.select('#__mce')[0];
350                                 if (caretNode) {
351                                         node = findSuitableCaretNode(caretNode, rootNode) || findSuitableCaretNode(caretNode, rootNode, true);
352                                         dom.remove(caretNode);
353
354                                         if (node) {
355                                                 rng = dom.createRng();
356
357                                                 if (node.nodeType == 3) {
358                                                         rng.setStart(node, node.length);
359                                                         rng.setEnd(node, node.length);
360                                                 } else {
361                                                         if (node.nodeName == 'BR') {
362                                                                 rng.setStartBefore(node);
363                                                                 rng.setEndBefore(node);
364                                                         } else {
365                                                                 rng.setStartAfter(node);
366                                                                 rng.setEndAfter(node);
367                                                         }
368                                                 }
369
370                                                 selection.setRng(rng);
371
372                                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
373                                                 if (!tinymce.isIE) {
374                                                         node = dom.create('span', null, '\u00a0');
375                                                         rng.insertNode(node);
376                                                         nodeRect = dom.getRect(node);
377                                                         viewPortRect = dom.getViewPort(editor.getWin());
378
379                                                         // Check if node is out side the viewport if it is then scroll to it
380                                                         if ((nodeRect.y > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
381                                                                 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
382                                                                 editor.getBody().scrollLeft = nodeRect.x;
383                                                                 editor.getBody().scrollTop = nodeRect.y;
384                                                         }
385
386                                                         dom.remove(node);
387                                                 }
388
389                                                 // Make sure that the selection is collapsed after we removed the node fixes a WebKit bug
390                                                 // where WebKit would place the endContainer/endOffset at a different location than the startContainer/startOffset
391                                                 selection.collapse(true);
392                                         }
393                                 }
394
395                                 selection.onSetContent.dispatch(selection, args);
396                                 editor.addVisual();
397                         },
398
399                         mceInsertRawHTML : function(command, ui, value) {
400                                 selection.setContent('tiny_mce_marker');
401                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
402                         },
403
404                         mceSetContent : function(command, ui, value) {
405                                 editor.setContent(value);
406                         },
407
408                         'Indent,Outdent' : function(command) {
409                                 var intentValue, indentUnit, value;
410
411                                 // Setup indent level
412                                 intentValue = settings.indentation;
413                                 indentUnit = /[a-z%]+$/i.exec(intentValue);
414                                 intentValue = parseInt(intentValue);
415
416                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
417                                         each(selection.getSelectedBlocks(), function(element) {
418                                                 if (command == 'outdent') {
419                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
420                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
421                                                 } else
422                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
423                                         });
424                                 } else
425                                         execNativeCommand(command);
426                         },
427
428                         mceRepaint : function() {
429                                 var bookmark;
430
431                                 if (tinymce.isGecko) {
432                                         try {
433                                                 storeSelection(TRUE);
434
435                                                 if (selection.getSel())
436                                                         selection.getSel().selectAllChildren(editor.getBody());
437
438                                                 selection.collapse(TRUE);
439                                                 restoreSelection();
440                                         } catch (ex) {
441                                                 // Ignore
442                                         }
443                                 }
444                         },
445
446                         mceToggleFormat : function(command, ui, value) {
447                                 editor.formatter.toggle(value);
448                         },
449
450                         InsertHorizontalRule : function() {
451                                 editor.execCommand('mceInsertContent', false, '<hr />');
452                         },
453
454                         mceToggleVisualAid : function() {
455                                 editor.hasVisual = !editor.hasVisual;
456                                 editor.addVisual();
457                         },
458
459                         mceReplaceContent : function(command, ui, value) {
460                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
461                         },
462
463                         mceInsertLink : function(command, ui, value) {
464                                 var link = dom.getParent(selection.getNode(), 'a'), img, floatVal;
465
466                                 if (tinymce.is(value, 'string'))
467                                         value = {href : value};
468
469                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
470                                 value.href = value.href.replace(' ', '%20');
471
472                                 if (!link) {
473                                         // WebKit can't create links on float images for some odd reason so just remove it and restore it later
474                                         if (tinymce.isWebKit) {
475                                                 img = dom.getParent(selection.getNode(), 'img');
476
477                                                 if (img) {
478                                                         floatVal = img.style.cssFloat;
479                                                         img.style.cssFloat = null;
480                                                 }
481                                         }
482
483                                         execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
484
485                                         // Restore float value
486                                         if (floatVal)
487                                                 img.style.cssFloat = floatVal;
488
489                                         each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
490                                                 dom.setAttribs(link, value);
491                                         });
492                                 } else {
493                                         if (value.href)
494                                                 dom.setAttribs(link, value);
495                                         else
496                                                 editor.dom.remove(link, TRUE);
497                                 }
498                         },
499                         
500                         selectAll : function() {
501                                 var root = dom.getRoot(), rng = dom.createRng();
502
503                                 rng.setStart(root, 0);
504                                 rng.setEnd(root, root.childNodes.length);
505
506                                 editor.selection.setRng(rng);
507                         }
508                 });
509
510                 // Add queryCommandState overrides
511                 addCommands({
512                         // Override justify commands
513                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
514                                 return isFormatMatch('align' + command.substring(7));
515                         },
516
517                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
518                                 return isFormatMatch(command);
519                         },
520
521                         mceBlockQuote : function() {
522                                 return isFormatMatch('blockquote');
523                         },
524
525                         Outdent : function() {
526                                 var node;
527
528                                 if (settings.inline_styles) {
529                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
530                                                 return TRUE;
531
532                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
533                                                 return TRUE;
534                                 }
535
536                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
537                         },
538
539                         'InsertUnorderedList,InsertOrderedList' : function(command) {
540                                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
541                         }
542                 }, 'state');
543
544                 // Add queryCommandValue overrides
545                 addCommands({
546                         'FontSize,FontName' : function(command) {
547                                 var value = 0, parent;
548
549                                 if (parent = dom.getParent(selection.getNode(), 'span')) {
550                                         if (command == 'fontsize')
551                                                 value = parent.style.fontSize;
552                                         else
553                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
554                                 }
555
556                                 return value;
557                         }
558                 }, 'value');
559
560                 // Add undo manager logic
561                 if (settings.custom_undo_redo) {
562                         addCommands({
563                                 Undo : function() {
564                                         editor.undoManager.undo();
565                                 },
566
567                                 Redo : function() {
568                                         editor.undoManager.redo();
569                                 }
570                         });
571                 }
572         };
573 })(tinymce);