Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / exec-command / exec-command.js
blobcc74d1f5be2def150a697f6ba28f14941e0f9180
1 /*
2 YUI 3.5.0 (build 5089)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('exec-command', function(Y) {
10     /**
11      * Plugin for the frame module to handle execCommands for Editor
12      * @class Plugin.ExecCommand
13      * @extends Base
14      * @constructor
15      * @module editor
16      * @submodule exec-command
17      */
18         var ExecCommand = function() {
19             ExecCommand.superclass.constructor.apply(this, arguments);
20         };
22         Y.extend(ExecCommand, Y.Base, {
23             /**
24             * An internal reference to the keyCode of the last key that was pressed.
25             * @private
26             * @property _lastKey
27             */
28             _lastKey: null,
29             /**
30             * An internal reference to the instance of the frame plugged into.
31             * @private
32             * @property _inst
33             */
34             _inst: null,
35             /**
36             * Execute a command on the frame's document.
37             * @method command
38             * @param {String} action The action to perform (bold, italic, fontname)
39             * @param {String} value The optional value (helvetica)
40             * @return {Node/NodeList} Should return the Node/Nodelist affected
41             */
42             command: function(action, value) {
43                 var fn = ExecCommand.COMMANDS[action];
44                 
45                 if (fn) {
46                     return fn.call(this, action, value);
47                 } else {
48                     return this._command(action, value);
49                 }
50             },
51             /**
52             * The private version of execCommand that doesn't filter for overrides.
53             * @private
54             * @method _command
55             * @param {String} action The action to perform (bold, italic, fontname)
56             * @param {String} value The optional value (helvetica)
57             */
58             _command: function(action, value) {
59                 var inst = this.getInstance();
60                 try {
61                     try {
62                         inst.config.doc.execCommand('styleWithCSS', null, 1);
63                     } catch (e1) {
64                         try {
65                             inst.config.doc.execCommand('useCSS', null, 0);
66                         } catch (e2) {
67                         }
68                     }
69                     inst.config.doc.execCommand(action, null, value);
70                 } catch (e) {
71                 }
72             },
73             /**
74             * Get's the instance of YUI bound to the parent frame
75             * @method getInstance
76             * @return {YUI} The YUI instance bound to the parent frame
77             */
78             getInstance: function() {
79                 if (!this._inst) {
80                     this._inst = this.get('host').getInstance();
81                 }
82                 return this._inst;
83             },
84             initializer: function() {
85                 Y.mix(this.get('host'), {
86                     execCommand: function(action, value) {
87                         return this.exec.command(action, value);
88                     },
89                     _execCommand: function(action, value) {
90                         return this.exec._command(action, value);
91                     }
92                 });
94                 this.get('host').on('dom:keypress', Y.bind(function(e) {
95                     this._lastKey = e.keyCode;
96                 }, this));
97             },
98             _wrapContent: function(str, override) {
99                 var useP = (this.getInstance().host.editorPara && !override ? true : false);
100                 
101                 if (useP) {
102                     str = '<p>' + str + '</p>';
103                 } else {
104                     str = str + '<br>';
105                 }
106                 return str;
107             }
108         }, {
109             /**
110             * execCommand
111             * @property NAME
112             * @static
113             */
114             NAME: 'execCommand',
115             /**
116             * exec
117             * @property NS
118             * @static
119             */
120             NS: 'exec',
121             ATTRS: {
122                 host: {
123                     value: false
124                 }
125             },
126             /**
127             * Static object literal of execCommand overrides
128             * @property COMMANDS
129             * @static
130             */
131             COMMANDS: {
132                 /**
133                 * Wraps the content with a new element of type (tag)
134                 * @method COMMANDS.wrap
135                 * @static
136                 * @param {String} cmd The command executed: wrap
137                 * @param {String} tag The tag to wrap the selection with
138                 * @return {NodeList} NodeList of the items touched by this command.
139                 */
140                 wrap: function(cmd, tag) {
141                     var inst = this.getInstance();
142                     return (new inst.EditorSelection()).wrapContent(tag);
143                 },
144                 /**
145                 * Inserts the provided HTML at the cursor, should be a single element.
146                 * @method COMMANDS.inserthtml
147                 * @static
148                 * @param {String} cmd The command executed: inserthtml
149                 * @param {String} html The html to insert
150                 * @return {Node} Node instance of the item touched by this command.
151                 */
152                 inserthtml: function(cmd, html) {
153                     var inst = this.getInstance();
154                     if (inst.EditorSelection.hasCursor() || Y.UA.ie) {
155                         return (new inst.EditorSelection()).insertContent(html);
156                     } else {
157                         this._command('inserthtml', html);
158                     }
159                 },
160                 /**
161                 * Inserts the provided HTML at the cursor, and focuses the cursor afterwards.
162                 * @method COMMANDS.insertandfocus
163                 * @static
164                 * @param {String} cmd The command executed: insertandfocus
165                 * @param {String} html The html to insert
166                 * @return {Node} Node instance of the item touched by this command.
167                 */
168                 insertandfocus: function(cmd, html) {
169                     var inst = this.getInstance(), out, sel;
170                     if (inst.EditorSelection.hasCursor()) {
171                         html += inst.EditorSelection.CURSOR;
172                         out = this.command('inserthtml', html);
173                         sel = new inst.EditorSelection();
174                         sel.focusCursor(true, true);
175                     } else {
176                         this.command('inserthtml', html);
177                     }
178                     return out;
179                 },
180                 /**
181                 * Inserts a BR at the current cursor position
182                 * @method COMMANDS.insertbr
183                 * @static
184                 * @param {String} cmd The command executed: insertbr
185                 */
186                 insertbr: function(cmd) {
187                     var inst = this.getInstance(),
188                         sel = new inst.EditorSelection(),
189                         html = '<var>|</var>', last = null,
190                         q = (Y.UA.webkit) ? 'span.Apple-style-span,var' : 'var';
192                     if (sel._selection.pasteHTML) {
193                         sel._selection.pasteHTML(html);
194                     } else {
195                         this._command('inserthtml', html);
196                     }
198                     var insert = function(n) {
199                         var c = inst.Node.create('<br>');
200                         n.insert(c, 'before');
201                         return c;
202                     };
204                     inst.all(q).each(function(n) {
205                         var g = true;   
206                         if (Y.UA.webkit) {
207                             g = false;
208                             if (n.get('innerHTML') === '|') {
209                                 g = true;
210                             }
211                         }
212                         if (g) {
213                             last = insert(n);
214                             if ((!last.previous() || !last.previous().test('br')) && Y.UA.gecko) {
215                                 var s = last.cloneNode();
216                                 last.insert(s, 'after');
217                                 last = s;
218                             }
219                             n.remove();
220                         }
221                     });
222                     if (Y.UA.webkit && last) {
223                         insert(last);
224                         sel.selectNode(last);
225                     }
226                 },
227                 /**
228                 * Inserts an image at the cursor position
229                 * @method COMMANDS.insertimage
230                 * @static
231                 * @param {String} cmd The command executed: insertimage
232                 * @param {String} img The url of the image to be inserted
233                 * @return {Node} Node instance of the item touched by this command.
234                 */
235                 insertimage: function(cmd, img) {
236                     return this.command('inserthtml', '<img src="' + img + '">');
237                 },
238                 /**
239                 * Add a class to all of the elements in the selection
240                 * @method COMMANDS.addclass
241                 * @static
242                 * @param {String} cmd The command executed: addclass
243                 * @param {String} cls The className to add
244                 * @return {NodeList} NodeList of the items touched by this command.
245                 */
246                 addclass: function(cmd, cls) {
247                     var inst = this.getInstance();
248                     return (new inst.EditorSelection()).getSelected().addClass(cls);
249                 },
250                 /**
251                 * Remove a class from all of the elements in the selection
252                 * @method COMMANDS.removeclass
253                 * @static
254                 * @param {String} cmd The command executed: removeclass
255                 * @param {String} cls The className to remove
256                 * @return {NodeList} NodeList of the items touched by this command.
257                 */
258                 removeclass: function(cmd, cls) {
259                     var inst = this.getInstance();
260                     return (new inst.EditorSelection()).getSelected().removeClass(cls);
261                 },
262                 /**
263                 * Adds a forecolor to the current selection, or creates a new element and applies it
264                 * @method COMMANDS.forecolor
265                 * @static
266                 * @param {String} cmd The command executed: forecolor
267                 * @param {String} val The color value to apply
268                 * @return {NodeList} NodeList of the items touched by this command.
269                 */
270                 forecolor: function(cmd, val) {
271                     var inst = this.getInstance(),
272                         sel = new inst.EditorSelection(), n;
274                     if (!Y.UA.ie) {
275                         this._command('useCSS', false);
276                     }
277                     if (inst.EditorSelection.hasCursor()) {
278                         if (sel.isCollapsed) {
279                             if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === '&nbsp;')) {
280                                 sel.anchorNode.setStyle('color', val);
281                                 n = sel.anchorNode;
282                             } else {
283                                 n = this.command('inserthtml', '<span style="color: ' + val + '">' + inst.EditorSelection.CURSOR + '</span>');
284                                 sel.focusCursor(true, true);
285                             }
286                             return n;
287                         } else {
288                             return this._command(cmd, val);
289                         }
290                     } else {
291                         this._command(cmd, val);
292                     }
293                 },
294                 /**
295                 * Adds a background color to the current selection, or creates a new element and applies it
296                 * @method COMMANDS.backcolor
297                 * @static
298                 * @param {String} cmd The command executed: backcolor
299                 * @param {String} val The color value to apply
300                 * @return {NodeList} NodeList of the items touched by this command.
301                 */
302                 backcolor: function(cmd, val) {
303                     var inst = this.getInstance(),
304                         sel = new inst.EditorSelection(), n;
305                     
306                     if (Y.UA.gecko || Y.UA.opera) {
307                         cmd = 'hilitecolor';
308                     }
309                     if (!Y.UA.ie) {
310                         this._command('useCSS', false);
311                     }
312                     if (inst.EditorSelection.hasCursor()) {
313                         if (sel.isCollapsed) {
314                             if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === '&nbsp;')) {
315                                 sel.anchorNode.setStyle('backgroundColor', val);
316                                 n = sel.anchorNode;
317                             } else {
318                                 n = this.command('inserthtml', '<span style="background-color: ' + val + '">' + inst.EditorSelection.CURSOR + '</span>');
319                                 sel.focusCursor(true, true);
320                             }
321                             return n;
322                         } else {
323                             return this._command(cmd, val);
324                         }
325                     } else {
326                         this._command(cmd, val);
327                     }
328                 },
329                 /**
330                 * Sugar method, calles backcolor
331                 * @method COMMANDS.hilitecolor
332                 * @static
333                 * @param {String} cmd The command executed: backcolor
334                 * @param {String} val The color value to apply
335                 * @return {NodeList} NodeList of the items touched by this command.
336                 */
337                 hilitecolor: function() {
338                     return ExecCommand.COMMANDS.backcolor.apply(this, arguments);
339                 },
340                 /**
341                 * Adds a font name to the current selection, or creates a new element and applies it
342                 * @method COMMANDS.fontname2
343                 * @deprecated
344                 * @static
345                 * @param {String} cmd The command executed: fontname
346                 * @param {String} val The font name to apply
347                 * @return {NodeList} NodeList of the items touched by this command.
348                 */
349                 fontname2: function(cmd, val) {
350                     this._command('fontname', val);
351                     var inst = this.getInstance(),
352                         sel = new inst.EditorSelection();
353                     
354                     if (sel.isCollapsed && (this._lastKey != 32)) {
355                         if (sel.anchorNode.test('font')) {
356                             sel.anchorNode.set('face', val);
357                         }
358                     }
359                 },
360                 /**
361                 * Adds a fontsize to the current selection, or creates a new element and applies it
362                 * @method COMMANDS.fontsize2
363                 * @deprecated
364                 * @static
365                 * @param {String} cmd The command executed: fontsize
366                 * @param {String} val The font size to apply
367                 * @return {NodeList} NodeList of the items touched by this command.
368                 */
369                 fontsize2: function(cmd, val) {
370                     this._command('fontsize', val);
372                     var inst = this.getInstance(),
373                         sel = new inst.EditorSelection();
374                     
375                     if (sel.isCollapsed && sel.anchorNode && (this._lastKey != 32)) {
376                         if (Y.UA.webkit) {
377                             if (sel.anchorNode.getStyle('lineHeight')) {
378                                 sel.anchorNode.setStyle('lineHeight', '');
379                             }
380                         }
381                         if (sel.anchorNode.test('font')) {
382                             sel.anchorNode.set('size', val);
383                         } else if (Y.UA.gecko) {
384                             var p = sel.anchorNode.ancestor(inst.EditorSelection.DEFAULT_BLOCK_TAG);
385                             if (p) {
386                                 p.setStyle('fontSize', '');
387                             }
388                         }
389                     }
390                 },
391                 /**
392                 * Overload for COMMANDS.list
393                 * @method COMMANDS.insertorderedlist
394                 * @static
395                 * @param {String} cmd The command executed: list, ul
396                 */
397                 insertunorderedlist: function(cmd) {
398                     this.command('list', 'ul');
399                 },
400                 /**
401                 * Overload for COMMANDS.list
402                 * @method COMMANDS.insertunorderedlist
403                 * @static
404                 * @param {String} cmd The command executed: list, ol
405                 */
406                 insertorderedlist: function(cmd) {
407                     this.command('list', 'ol');
408                 },
409                 /**
410                 * Noramlizes lists creation/destruction for IE. All others pass through to native calls
411                 * @method COMMANDS.list
412                 * @static
413                 * @param {String} cmd The command executed: list (not used)
414                 * @param {String} tag The tag to deal with
415                 */
416                 list: function(cmd, tag) {
417                     var inst = this.getInstance(), html, self = this,
418                         DIR = 'dir', cls = 'yui3-touched',
419                         dir, range, div, elm, n, str, s, par, list, lis,
420                         useP = (inst.host.editorPara ? true : false),
421                         sel = new inst.EditorSelection();
423                     cmd = 'insert' + ((tag === 'ul') ? 'un' : '') + 'orderedlist';
424                     
425                     if (Y.UA.ie && !sel.isCollapsed) {
426                         range = sel._selection;
427                         html = range.htmlText;
428                         div = inst.Node.create(html) || inst.one('body');
430                         if (div.test('li') || div.one('li')) {
431                             this._command(cmd, null);
432                             return;
433                         }
434                         if (div.test(tag)) {
435                             elm = range.item ? range.item(0) : range.parentElement();
436                             n = inst.one(elm);
437                             lis = n.all('li');
439                             str = '<div>';
440                             lis.each(function(l) {
441                                 str = self._wrapContent(l.get('innerHTML'));
442                             });
443                             str += '</div>';
444                             s = inst.Node.create(str);
445                             if (n.get('parentNode').test('div')) {
446                                 n = n.get('parentNode');
447                             }
448                             if (n && n.hasAttribute(DIR)) {
449                                 if (useP) {
450                                     s.all('p').setAttribute(DIR, n.getAttribute(DIR));
451                                 } else {
452                                     s.setAttribute(DIR, n.getAttribute(DIR));
453                                 }
454                             }
455                             if (useP) {
456                                 n.replace(s.get('innerHTML'));
457                             } else {
458                                 n.replace(s);
459                             }
460                             if (range.moveToElementText) {
461                                 range.moveToElementText(s._node);
462                             }
463                             range.select();
464                         } else {
465                             par = Y.one(range.parentElement());
466                             if (!par.test(inst.EditorSelection.BLOCKS)) {
467                                 par = par.ancestor(inst.EditorSelection.BLOCKS);
468                             }
469                             if (par) {
470                                 if (par.hasAttribute(DIR)) {
471                                     dir = par.getAttribute(DIR);
472                                 }
473                             }
474                             if (html.indexOf('<br>') > -1) {
475                                 html = html.split(/<br>/i);
476                             } else {
477                                 var tmp = inst.Node.create(html),
478                                 ps = tmp ? tmp.all('p') : null;
480                                 if (ps && ps.size()) {
481                                     html = [];
482                                     ps.each(function(n) {
483                                         html.push(n.get('innerHTML'));
484                                     });
485                                 } else {
486                                     html = [html];
487                                 }
488                             }
489                             list = '<' + tag + ' id="ie-list">';
490                             Y.each(html, function(v) {
491                                 var a = inst.Node.create(v);
492                                 if (a && a.test('p')) {
493                                     if (a.hasAttribute(DIR)) {
494                                         dir = a.getAttribute(DIR);
495                                     }
496                                     v = a.get('innerHTML');
497                                 }
498                                 list += '<li>' + v + '</li>';
499                             });
500                             list += '</' + tag + '>';
501                             range.pasteHTML(list);
502                             elm = inst.config.doc.getElementById('ie-list');
503                             elm.id = '';
504                             if (dir) {
505                                 elm.setAttribute(DIR, dir);
506                             }
507                             if (range.moveToElementText) {
508                                 range.moveToElementText(elm);
509                             }
510                             range.select();
511                         }
512                     } else if (Y.UA.ie) {
513                         par = inst.one(sel._selection.parentElement());
514                         if (par.test('p')) {
515                             if (par && par.hasAttribute(DIR)) {
516                                 dir = par.getAttribute(DIR);
517                             }
518                             html = Y.EditorSelection.getText(par);
519                             if (html === '') {
520                                 var sdir = '';
521                                 if (dir) {
522                                     sdir = ' dir="' + dir + '"';
523                                 }
524                                 list = inst.Node.create(Y.Lang.sub('<{tag}{dir}><li></li></{tag}>', { tag: tag, dir: sdir }));
525                                 par.replace(list);
526                                 sel.selectNode(list.one('li'));
527                             } else {
528                                 this._command(cmd, null);
529                             }
530                         } else {
531                             this._command(cmd, null);
532                         }
533                     } else {
534                         inst.all(tag).addClass(cls);
535                         if (sel.anchorNode.test(inst.EditorSelection.BLOCKS)) {
536                             par = sel.anchorNode;
537                         } else {
538                             par = sel.anchorNode.ancestor(inst.EditorSelection.BLOCKS);
539                         }
540                         if (!par) { //No parent, find the first block under the anchorNode
541                             par = sel.anchorNode.one(inst.EditorSelection.BLOCKS);
542                         }
544                         if (par && par.hasAttribute(DIR)) {
545                             dir = par.getAttribute(DIR);
546                         }
547                         if (par && par.test(tag)) {
548                             var hasPParent = par.ancestor('p');
549                             html = inst.Node.create('<div/>');
550                             elm = par.all('li');
551                             elm.each(function(h) {
552                                 html.append(self._wrapContent(h.get('innerHTML'), hasPParent));
553                             });
554                             if (dir) {
555                                 if (useP) {
556                                     html.all('p').setAttribute(DIR, dir);
557                                 } else {
558                                     html.setAttribute(DIR, dir);
559                                 }
560                             }
561                             if (useP) {
562                                 html = inst.Node.create(html.get('innerHTML'));
563                             }
564                             var fc = html.get('firstChild');
565                             par.replace(html);
566                             sel.selectNode(fc);
567                         } else {
568                             this._command(cmd, null);
569                         }
570                         list = inst.all(tag);
571                         if (dir) {
572                             if (list.size()) {
573                                 //Changed to a List
574                                 list.each(function(n) {
575                                     if (!n.hasClass(cls)) {
576                                         n.setAttribute(DIR, dir);
577                                     }
578                                 });
579                             }
580                         }
582                         list.removeClass(cls);
583                     }
584                 },
585                 /**
586                 * Noramlizes alignment for Webkit Browsers
587                 * @method COMMANDS.justify
588                 * @static
589                 * @param {String} cmd The command executed: justify (not used)
590                 * @param {String} val The actual command from the justify{center,all,left,right} stubs
591                 */
592                 justify: function(cmd, val) {
593                     if (Y.UA.webkit) {
594                         var inst = this.getInstance(),
595                             sel = new inst.EditorSelection(),
596                             aNode = sel.anchorNode;
598                             var bgColor = aNode.getStyle('backgroundColor');
599                             this._command(val);
600                             sel = new inst.EditorSelection();
601                             if (sel.anchorNode.test('div')) {
602                                 var html = '<span>' + sel.anchorNode.get('innerHTML') + '</span>';
603                                 sel.anchorNode.set('innerHTML', html);
604                                 sel.anchorNode.one('span').setStyle('backgroundColor', bgColor);
605                                 sel.selectNode(sel.anchorNode.one('span'));
606                             }
607                     } else {
608                         this._command(val);
609                     }
610                 },
611                 /**
612                 * Override method for COMMANDS.justify
613                 * @method COMMANDS.justifycenter
614                 * @static
615                 */
616                 justifycenter: function(cmd) {
617                     this.command('justify', 'justifycenter');
618                 },
619                 /**
620                 * Override method for COMMANDS.justify
621                 * @method COMMANDS.justifyleft
622                 * @static
623                 */
624                 justifyleft: function(cmd) {
625                     this.command('justify', 'justifyleft');
626                 },
627                 /**
628                 * Override method for COMMANDS.justify
629                 * @method COMMANDS.justifyright
630                 * @static
631                 */
632                 justifyright: function(cmd) {
633                     this.command('justify', 'justifyright');
634                 },
635                 /**
636                 * Override method for COMMANDS.justify
637                 * @method COMMANDS.justifyfull
638                 * @static
639                 */
640                 justifyfull: function(cmd) {
641                     this.command('justify', 'justifyfull');
642                 }
643             }
644         });
645         
646         /**
647         * This method is meant to normalize IE's in ability to exec the proper command on elements with CSS styling.
648         * @method fixIETags
649         * @protected
650         * @param {String} cmd The command to execute
651         * @param {String} tag The tag to create
652         * @param {String} rule The rule that we are looking for.
653         */
654         var fixIETags = function(cmd, tag, rule) {
655             var inst = this.getInstance(),
656                 doc = inst.config.doc,
657                 sel = doc.selection.createRange(),
658                 o = doc.queryCommandValue(cmd),
659                 html, reg, m, p, d, s, c;
661             if (o) {
662                 html = sel.htmlText;
663                 reg = new RegExp(rule, 'g');
664                 m = html.match(reg);
666                 if (m) {
667                     html = html.replace(rule + ';', '').replace(rule, '');
669                     sel.pasteHTML('<var id="yui-ie-bs">');
671                     p = doc.getElementById('yui-ie-bs');
672                     d = doc.createElement('div');
673                     s = doc.createElement(tag);
674                     
675                     d.innerHTML = html;
676                     if (p.parentNode !== inst.config.doc.body) {
677                         p = p.parentNode;
678                     }
680                     c = d.childNodes;
682                     p.parentNode.replaceChild(s, p);
684                     Y.each(c, function(f) {
685                         s.appendChild(f);
686                     });
687                     sel.collapse();
688                     if (sel.moveToElementText) {
689                         sel.moveToElementText(s);
690                     }
691                     sel.select();
692                 }
693             }
694             this._command(cmd);
695         };
697         if (Y.UA.ie) {
698             ExecCommand.COMMANDS.bold = function() {
699                 fixIETags.call(this, 'bold', 'b', 'FONT-WEIGHT: bold');
700             };
701             ExecCommand.COMMANDS.italic = function() {
702                 fixIETags.call(this, 'italic', 'i', 'FONT-STYLE: italic');
703             };
704             ExecCommand.COMMANDS.underline = function() {
705                 fixIETags.call(this, 'underline', 'u', 'TEXT-DECORATION: underline');
706             };
707         }
709         Y.namespace('Plugin');
710         Y.Plugin.ExecCommand = ExecCommand;
714 }, '3.5.0' ,{skinnable:false, requires:['frame']});