]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/editor/editor.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / editor / editor.js
1 /*
2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 3.3.0
6 build: 3167
7 */
8 YUI.add('frame', function(Y) {
9
10     /**
11      * Creates a wrapper around an iframe. It loads the content either from a local
12      * file or from script and creates a local YUI instance bound to that new window and document.
13      * @module editor
14      * @submodule frame
15      */     
16     /**
17      * Creates a wrapper around an iframe. It loads the content either from a local
18      * file or from script and creates a local YUI instance bound to that new window and document.
19      * @class Frame
20      * @for Frame
21      * @extends Base
22      * @constructor
23      */
24
25     var Frame = function() {
26         Frame.superclass.constructor.apply(this, arguments);
27     };
28
29     Y.extend(Frame, Y.Base, {
30         /**
31         * @private
32         * @property _ready
33         * @description Internal reference set when the content is ready.
34         * @type Boolean
35         */
36         _ready: null,
37         /**
38         * @private
39         * @property _rendered
40         * @description Internal reference set when render is called.
41         * @type Boolean
42         */
43         _rendered: null,
44         /**
45         * @private
46         * @property _iframe
47         * @description Internal Node reference to the iFrame or the window
48         * @type Node
49         */
50         _iframe: null,
51         /**
52         * @private
53         * @property _instance
54         * @description Internal reference to the YUI instance bound to the iFrame or window
55         * @type YUI
56         */
57         _instance: null,
58         /**
59         * @private
60         * @method _create
61         * @description Create the iframe or Window and get references to the Document & Window
62         * @return {Object} Hash table containing references to the new Document & Window
63         */
64         _create: function(cb) {
65             var win, doc, res, node;
66             
67             this._iframe = Y.Node.create(Frame.HTML);
68             this._iframe.setStyle('visibility', 'hidden');
69             this._iframe.set('src', this.get('src'));
70             this.get('container').append(this._iframe);
71
72             this._iframe.set('height', '99%');
73
74             
75             var html = '',
76                 extra_css = ((this.get('extracss')) ? '<style id="extra_css">' + this.get('extracss') + '</style>' : '');
77
78             html = Y.substitute(Frame.PAGE_HTML, {
79                 DIR: this.get('dir'),
80                 LANG: this.get('lang'),
81                 TITLE: this.get('title'),
82                 META: Frame.META,
83                 LINKED_CSS: this.get('linkedcss'),
84                 CONTENT: this.get('content'),
85                 BASE_HREF: this.get('basehref'),
86                 DEFAULT_CSS: Frame.DEFAULT_CSS,
87                 EXTRA_CSS: extra_css
88             });
89             if (Y.config.doc.compatMode != 'BackCompat') {
90                 
91                 //html = Frame.DOC_TYPE + "\n" + html;
92                 html = Frame.getDocType() + "\n" + html;
93             } else {
94             }
95
96
97
98             res = this._resolveWinDoc();
99             res.doc.open();
100             res.doc.write(html);
101             res.doc.close();
102
103             if (this.get('designMode')) {
104                 res.doc.designMode = 'on';
105             }
106             
107             if (!res.doc.documentElement) {
108                 var timer = Y.later(1, this, function() {
109                     if (res.doc && res.doc.documentElement) {
110                         cb(res);
111                         timer.cancel();
112                     }
113                 }, null, true);
114             } else {
115                 cb(res);
116             }
117
118         },
119         /**
120         * @private
121         * @method _resolveWinDoc
122         * @description Resolves the document and window from an iframe or window instance
123         * @param {Object} c The YUI Config to add the window and document to
124         * @return {Object} Object hash of window and document references, if a YUI config was passed, it is returned.
125         */
126         _resolveWinDoc: function(c) {
127             var config = (c) ? c : {};
128             config.win = Y.Node.getDOMNode(this._iframe.get('contentWindow'));
129             config.doc = Y.Node.getDOMNode(this._iframe.get('contentWindow.document'));
130             if (!config.doc) {
131                 config.doc = Y.config.doc;
132             }
133             if (!config.win) {
134                 config.win = Y.config.win;
135             }
136             return config;
137         },
138         /**
139         * @private
140         * @method _onDomEvent
141         * @description Generic handler for all DOM events fired by the iframe or window. This handler
142         * takes the current EventFacade and augments it to fire on the Frame host. It adds two new properties
143         * to the EventFacade called frameX and frameY which adds the scroll and xy position of the iframe
144         * to the original pageX and pageY of the event so external nodes can be positioned over the frame.
145         * @param {Event.Facade} e
146         */
147         _onDomEvent: function(e) {
148             var xy, node;
149
150             e.frameX = e.frameY = 0;
151
152             if (e.pageX > 0 || e.pageY > 0) {
153                 if (e.type.substring(0, 3) !== 'key') {
154                     node = this._instance.one('win');
155                     xy = this._iframe.getXY();
156                     e.frameX = xy[0] + e.pageX - node.get('scrollLeft');
157                     e.frameY = xy[1] + e.pageY - node.get('scrollTop');
158                 }
159             }
160
161             e.frameTarget = e.target;
162             e.frameCurrentTarget = e.currentTarget;
163             e.frameEvent = e;
164
165             this.fire('dom:' + e.type, e);
166         },
167         initializer: function() {
168             this.publish('ready', {
169                 emitFacade: true,
170                 defaultFn: this._defReadyFn
171             });
172         },
173         destructor: function() {
174             var inst = this.getInstance();
175
176             inst.one('doc').detachAll();
177             inst = null;
178             this._iframe.remove();
179         },
180         /**
181         * @private
182         * @method _DOMPaste
183         * @description Simple pass thru handler for the paste event so we can do content cleanup
184         * @param {Event.Facade} e
185         */
186         _DOMPaste: function(e) {
187             var inst = this.getInstance(),
188                 data = '', win = inst.config.win;
189
190             if (e._event.originalTarget) {
191                 data = e._event.originalTarget;
192             }
193             if (e._event.clipboardData) {
194                 data = e._event.clipboardData.getData('Text');
195             }
196             
197             if (win.clipboardData) {
198                 data = win.clipboardData.getData('Text');
199                 if (data === '') { // Could be empty, or failed
200                     // Verify failure
201                     if (!win.clipboardData.setData('Text', data)) {
202                         data = null;
203                     }
204                 }
205             }
206             
207
208             e.frameTarget = e.target;
209             e.frameCurrentTarget = e.currentTarget;
210             e.frameEvent = e;
211             
212             if (data) {
213                 e.clipboardData = {
214                     data: data,
215                     getData: function() {
216                         return data;
217                     }
218                 };
219             } else {
220                 e.clipboardData = null;
221             }
222
223             this.fire('dom:paste', e);
224         },
225         /**
226         * @private
227         * @method _defReadyFn
228         * @description Binds DOM events, sets the iframe to visible and fires the ready event
229         */
230         _defReadyFn: function() {
231             var inst = this.getInstance(),
232                 fn = Y.bind(this._onDomEvent, this),
233                 kfn = ((Y.UA.ie) ? Y.throttle(fn, 200) : fn);
234
235             inst.Node.DOM_EVENTS.activate = 1;
236             inst.Node.DOM_EVENTS.beforedeactivate = 1;
237             inst.Node.DOM_EVENTS.focusin = 1;
238             inst.Node.DOM_EVENTS.deactivate = 1;
239             inst.Node.DOM_EVENTS.focusout = 1;
240
241             //Y.each(inst.Node.DOM_EVENTS, function(v, k) {
242             Y.each(Frame.DOM_EVENTS, function(v, k) {
243                 if (v === 1) {
244                     if (k !== 'focus' && k !== 'blur' && k !== 'paste') {
245                         if (k.substring(0, 3) === 'key') {
246                             if (k === 'keydown') {
247                                 inst.on(k, fn, inst.config.doc);
248                             } else {
249                                 inst.on(k, kfn, inst.config.doc);
250                             }
251                         } else {
252                             inst.on(k, fn, inst.config.doc);
253                         }
254                     }
255                 }
256             }, this);
257
258             inst.Node.DOM_EVENTS.paste = 1;
259             
260             inst.on('paste', Y.bind(this._DOMPaste, this), inst.one('body'));
261
262             //Adding focus/blur to the window object
263             inst.on('focus', fn, inst.config.win);
264             inst.on('blur', fn, inst.config.win);
265
266             inst._use = inst.use;
267             inst.use = Y.bind(this.use, this);
268             this._iframe.setStyles({
269                 visibility: 'inherit'
270             });
271             inst.one('body').setStyle('display', 'block');
272             if (Y.UA.ie) {
273                 this._fixIECursors();
274             }
275         },
276         /**
277         * It appears that having a BR tag anywhere in the source "below" a table with a percentage width (in IE 7 & 8)
278         * if there is any TEXTINPUT's outside the iframe, the cursor will rapidly flickr and the CPU would occasionally 
279         * spike. This method finds all <BR>'s below the sourceIndex of the first table. Does some checks to see if they
280         * can be modified and replaces then with a <WBR> so the layout will remain in tact, but the flickering will
281         * no longer happen.
282         * @method _fixIECursors
283         * @private
284         */
285         _fixIECursors: function() {
286             var inst = this.getInstance(),
287                 tables = inst.all('table'),
288                 brs = inst.all('br'), si;
289
290             if (tables.size() && brs.size()) {
291                 //First Table
292                 si = tables.item(0).get('sourceIndex');
293                 brs.each(function(n) {
294                     var p = n.get('parentNode'),
295                         c = p.get('children'), b = p.all('>br');
296                     
297                     if (p.test('div')) {
298                         if (c.size() > 2) {
299                             n.replace(inst.Node.create('<wbr>'));
300                         } else {
301                             if (n.get('sourceIndex') > si) {
302                                 if (b.size()) {
303                                     n.replace(inst.Node.create('<wbr>'));
304                                 }
305                             } else {
306                                 if (b.size() > 1) {
307                                     n.replace(inst.Node.create('<wbr>'));
308                                 }
309                             }
310                         }
311                     }
312                     
313                 });
314             }
315         },
316         /**
317         * @private
318         * @method _onContentReady
319         * @description Called once the content is available in the frame/window and calls the final use call
320         * on the internal instance so that the modules are loaded properly.
321         */
322         _onContentReady: function(e) {
323             if (!this._ready) {
324                 this._ready = true;
325                 var inst = this.getInstance(),
326                     args = Y.clone(this.get('use'));
327                 
328                 this.fire('contentready');
329
330                 if (e) {
331                     inst.config.doc = Y.Node.getDOMNode(e.target);
332                 }
333                 //TODO Circle around and deal with CSS loading...
334                 args.push(Y.bind(function() {
335                     if (inst.Selection) {
336                         inst.Selection.DEFAULT_BLOCK_TAG = this.get('defaultblock');
337                     }
338
339                     this.fire('ready');
340                 }, this));
341                 inst.use.apply(inst, args);
342
343                 inst.one('doc').get('documentElement').addClass('yui-js-enabled');
344             }
345         },
346         /**
347         * @private
348         * @method _resolveBaseHref
349         * @description Resolves the basehref of the page the frame is created on. Only applies to dynamic content.
350         * @param {String} href The new value to use, if empty it will be resolved from the current url.
351         * @return {String}
352         */
353         _resolveBaseHref: function(href) {
354             if (!href || href === '') {
355                 href = Y.config.doc.location.href;
356                 if (href.indexOf('?') !== -1) { //Remove the query string
357                     href = href.substring(0, href.indexOf('?'));
358                 }
359                 href = href.substring(0, href.lastIndexOf('/')) + '/';
360             }
361             return href;
362         },
363         /**
364         * @private
365         * @method _getHTML
366         * @description Get the content from the iframe
367         * @param {String} html The raw HTML from the body of the iframe.
368         * @return {String}
369         */
370         _getHTML: function(html) {
371             if (this._ready) {
372                 var inst = this.getInstance();
373                 html = inst.one('body').get('innerHTML');
374             }
375             return html;
376         },
377         /**
378         * @private
379         * @method _setHTML
380         * @description Set the content of the iframe
381         * @param {String} html The raw HTML to set the body of the iframe to.
382         * @return {String}
383         */
384         _setHTML: function(html) {
385             if (this._ready) {
386                 var inst = this.getInstance();
387                 inst.one('body').set('innerHTML', html);
388             } else {
389                 //This needs to be wrapped in a contentready callback for the !_ready state
390                 this.on('contentready', Y.bind(function(html, e) {
391                     var inst = this.getInstance();
392                     inst.one('body').set('innerHTML', html);
393                 }, this, html));
394             }
395             return html;
396         },
397         /**
398         * @private
399         * @method _setLinkedCSS
400         * @description Set's the linked CSS on the instance..
401         */
402         _getLinkedCSS: function(urls) {
403             if (!Y.Lang.isArray(urls)) {
404                 urls = [urls];
405             }
406             var str = '';
407             if (!this._ready) {
408                 Y.each(urls, function(v) {
409                     if (v !== '') {
410                         str += '<link rel="stylesheet" href="' + v + '" type="text/css">';
411                     }
412                 });
413             } else {
414                 str = urls;
415             }
416             return str;
417         },
418         /**
419         * @private
420         * @method _setLinkedCSS
421         * @description Set's the linked CSS on the instance..
422         */
423         _setLinkedCSS: function(css) {
424             if (this._ready) {
425                 var inst = this.getInstance();
426                 inst.Get.css(css);
427             }
428             return css;
429         },
430         /**
431         * @private
432         * @method _setExtraCSS
433         * @description Set's the extra CSS on the instance..
434         */
435         _setExtraCSS: function(css) {
436             if (this._ready) {
437                 var inst = this.getInstance(),
438                     node = inst.get('#extra_css');
439                 
440                 node.remove();
441                 inst.one('head').append('<style id="extra_css">' + css + '</style>');
442             }
443             return css;
444         },
445         /**
446         * @private
447         * @method _instanceLoaded
448         * @description Called from the first YUI instance that sets up the internal instance.
449         * This loads the content into the window/frame and attaches the contentready event.
450         * @param {YUI} inst The internal YUI instance bound to the frame/window
451         */
452         _instanceLoaded: function(inst) {
453             this._instance = inst;
454             this._onContentReady();
455             
456             var doc = this._instance.config.doc;
457
458             if (this.get('designMode')) {
459                 if (!Y.UA.ie) {
460                     try {
461                         //Force other browsers into non CSS styling
462                         doc.execCommand('styleWithCSS', false, false);
463                         doc.execCommand('insertbronreturn', false, false);
464                     } catch (err) {}
465                 }
466             }
467         },
468         //BEGIN PUBLIC METHODS
469         /**
470         * @method use
471         * @description This is a scoped version of the normal YUI.use method & is bound to this frame/window.
472         * At setup, the inst.use method is mapped to this method.
473         */
474         use: function() {
475             var inst = this.getInstance(),
476                 args = Y.Array(arguments),
477                 cb = false;
478
479             if (Y.Lang.isFunction(args[args.length - 1])) {
480                 cb = args.pop();
481             }
482             if (cb) {
483                 args.push(function() {
484                     cb.apply(inst, arguments);
485
486                 });
487             }
488             inst._use.apply(inst, args);
489         },
490         /**
491         * @method delegate
492         * @description A delegate method passed to the instance's delegate method
493         * @param {String} type The type of event to listen for
494         * @param {Function} fn The method to attach
495         * @param {String} cont The container to act as a delegate, if no "sel" passed, the body is assumed as the container.
496         * @param {String} sel The selector to match in the event (optional)
497         * @return {EventHandle} The Event handle returned from Y.delegate
498         */
499         delegate: function(type, fn, cont, sel) {
500             var inst = this.getInstance();
501             if (!inst) {
502                 return false;
503             }
504             if (!sel) {
505                 sel = cont;
506                 cont = 'body';
507             }
508             return inst.delegate(type, fn, cont, sel);
509         },
510         /**
511         * @method getInstance
512         * @description Get a reference to the internal YUI instance.
513         * @return {YUI} The internal YUI instance
514         */
515         getInstance: function() {
516             return this._instance;
517         },
518         /**
519         * @method render
520         * @description Render the iframe into the container config option or open the window.
521         * @param {String/HTMLElement/Node} node The node to render to
522         * @return {Y.Frame}
523         * @chainable
524         */
525         render: function(node) {
526             if (this._rendered) {
527                 return this;
528             }
529             this._rendered = true;
530             if (node) {
531                 this.set('container', node);
532             }
533
534             this._create(Y.bind(function(res) {
535
536                 var inst, timer,
537                     cb = Y.bind(function(i) {
538                         this._instanceLoaded(i);
539                     }, this),
540                     args = Y.clone(this.get('use')),
541                     config = {
542                         debug: false,
543                         win: res.win,
544                         doc: res.doc
545                     },
546                     fn = Y.bind(function() {
547                         config = this._resolveWinDoc(config);
548                         inst = YUI(config);
549
550                         try {
551                             inst.use('node-base', cb);
552                             if (timer) {
553                                 clearInterval(timer);
554                             }
555                         } catch (e) {
556                             timer = setInterval(function() {
557                                 fn();
558                             }, 350);
559                         }
560                     }, this);
561
562                 args.push(fn);
563
564                 Y.use.apply(Y, args);
565
566             }, this));
567
568             return this;
569         },
570         /**
571         * @private
572         * @method _handleFocus
573         * @description Does some tricks on focus to set the proper cursor position.
574         */
575         _handleFocus: function() {
576             var inst = this.getInstance(),
577                 sel = new inst.Selection();
578
579             if (sel.anchorNode) {
580                 var n = sel.anchorNode,
581                     c = n.get('childNodes');
582
583                 if (c.size() == 1) {
584                     if (c.item(0).test('br')) {
585                         sel.selectNode(n, true, false);
586                     }
587                     if (c.item(0).test('p')) {
588                         n = c.item(0).one('br.yui-cursor').get('parentNode');
589                         sel.selectNode(n, true, false);
590                     }
591                 }
592             }
593         },
594         /**
595         * @method focus
596         * @description Set the focus to the iframe
597         * @param {Function} fn Callback function to execute after focus happens        
598         * @return {Frame}
599         * @chainable        
600         */
601         focus: function(fn) {
602             if (Y.UA.ie) {
603                 try {
604                     Y.one('win').focus();
605                     this.getInstance().one('win').focus();
606                 } catch (ierr) {
607                 }
608                 if (fn === true) {
609                     this._handleFocus();
610                 }
611                 if (Y.Lang.isFunction(fn)) {
612                     fn();
613                 }
614             } else {
615                 try {
616                     Y.one('win').focus();
617                     Y.later(100, this, function() {
618                         this.getInstance().one('win').focus();
619                         if (fn === true) {
620                             this._handleFocus();
621                         }
622                         if (Y.Lang.isFunction(fn)) {
623                             fn();
624                         }
625                     });
626                 } catch (ferr) {
627                 }
628             }
629             return this;
630         },
631         /**
632         * @method show
633         * @description Show the iframe instance
634         * @return {Frame}
635         * @chainable        
636         */
637         show: function() {
638             this._iframe.setStyles({
639                 position: 'static',
640                 left: ''
641             });
642             if (Y.UA.gecko) {
643                 try {
644                     this._instance.config.doc.designMode = 'on';
645                 } catch (e) { }
646                 this.focus();
647             }           
648             return this;
649         },
650         /**
651         * @method hide
652         * @description Hide the iframe instance
653         * @return {Frame}
654         * @chainable        
655         */
656         hide: function() {
657             this._iframe.setStyles({
658                 position: 'absolute',
659                 left: '-999999px'
660             });
661             return this;
662         }
663     }, {
664         
665         /**
666         * @static
667         * @property DOM_EVENTS
668         * @description The DomEvents that the frame automatically attaches and bubbles
669         * @type Object
670         */
671         DOM_EVENTS: {
672             dblclick: 1,
673             click: 1,
674             paste: 1,
675             mouseup: 1,
676             mousedown: 1,
677             keyup: 1,
678             keydown: 1,
679             keypress: 1,
680             activate: 1,
681             deactivate: 1,
682             beforedeactivate: 1,
683             focusin: 1,
684             focusout: 1
685         },
686
687         /**
688         * @static
689         * @property DEFAULT_CSS
690         * @description The default css used when creating the document.
691         * @type String
692         */
693         //DEFAULT_CSS: 'html { height: 95%; } body { padding: 7px; background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } img { cursor: pointer !important; border: none; }',
694         DEFAULT_CSS: 'body { background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } img { cursor: pointer !important; border: none; }',
695         /**
696         * @static
697         * @property HTML
698         * @description The template string used to create the iframe
699         * @type String
700         */
701         //HTML: '<iframe border="0" frameBorder="0" marginWidth="0" marginHeight="0" leftMargin="0" topMargin="0" allowTransparency="true" width="100%" height="99%"></iframe>',
702         HTML: '<iframe border="0" frameBorder="0" marginWidth="0" marginHeight="0" leftMargin="0" topMargin="0" allowTransparency="true" width="100%" height="99%"></iframe>',
703         /**
704         * @static
705         * @property PAGE_HTML
706         * @description The template used to create the page when created dynamically.
707         * @type String
708         */
709         PAGE_HTML: '<html dir="{DIR}" lang="{LANG}"><head><title>{TITLE}</title>{META}<base href="{BASE_HREF}"/>{LINKED_CSS}<style id="editor_css">{DEFAULT_CSS}</style>{EXTRA_CSS}</head><body>{CONTENT}</body></html>',
710
711         /**
712         * @static
713         * @method getDocType
714         * @description Parses document.doctype and generates a DocType to match the parent page, if supported.
715         * For IE8, it grabs document.all[0].nodeValue and uses that. For IE < 8, it falls back to Frame.DOC_TYPE.
716         * @returns {String} The normalized DocType to apply to the iframe
717         */
718         getDocType: function() {
719             var dt = Y.config.doc.doctype,
720                 str = Frame.DOC_TYPE;
721
722             if (dt) {
723                 str = '<!DOCTYPE ' + dt.name + ((dt.publicId) ? ' ' + dt.publicId : '') + ((dt.systemId) ? ' ' + dt.systemId : '') + '>';
724             } else {
725                 if (Y.config.doc.all) {
726                     dt = Y.config.doc.all[0];
727                     if (dt.nodeType) {
728                         if (dt.nodeType === 8) {
729                             if (dt.nodeValue) {
730                                 if (dt.nodeValue.toLowerCase().indexOf('doctype') !== -1) {
731                                     str = '<!' + dt.nodeValue + '>';
732                                 }
733                             }
734                         }
735                     }
736                 }
737             }
738             return str;
739         },
740         /**
741         * @static
742         * @property DOC_TYPE
743         * @description The DOCTYPE to prepend to the new document when created. Should match the one on the page being served.
744         * @type String
745         */
746         DOC_TYPE: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
747         /**
748         * @static
749         * @property META
750         * @description The meta-tag for Content-Type to add to the dynamic document
751         * @type String
752         */
753         //META: '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7">',
754         META: '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>',
755         /**
756         * @static
757         * @property NAME
758         * @description The name of the class (frame)
759         * @type String
760         */
761         NAME: 'frame',
762         ATTRS: {
763             /**
764             * @attribute title
765             * @description The title to give the blank page.
766             * @type String
767             */
768             title: {
769                 value: 'Blank Page'
770             },
771             /**
772             * @attribute dir
773             * @description The default text direction for this new frame. Default: ltr
774             * @type String
775             */
776             dir: {
777                 value: 'ltr'
778             },
779             /**
780             * @attribute lang
781             * @description The default language. Default: en-US
782             * @type String
783             */
784             lang: {
785                 value: 'en-US'
786             },
787             /**
788             * @attribute src
789             * @description The src of the iframe/window. Defaults to javascript:;
790             * @type String
791             */
792             src: {
793                 //Hackish, IE needs the false in the Javascript URL
794                 value: 'javascript' + ((Y.UA.ie) ? ':false' : ':') + ';'
795             },
796             /**
797             * @attribute designMode
798             * @description Should designMode be turned on after creation.
799             * @writeonce
800             * @type Boolean
801             */
802             designMode: {
803                 writeOnce: true,
804                 value: false
805             },
806             /**
807             * @attribute content
808             * @description The string to inject into the body of the new frame/window.
809             * @type String
810             */
811             content: {
812                 value: '<br>',
813                 setter: '_setHTML',
814                 getter: '_getHTML'
815             },
816             /**
817             * @attribute basehref
818             * @description The base href to use in the iframe.
819             * @type String
820             */
821             basehref: {
822                 value: false,
823                 getter: '_resolveBaseHref'
824             },
825             /**
826             * @attribute use
827             * @description Array of modules to include in the scoped YUI instance at render time. Default: ['none', 'selector-css2']
828             * @writeonce
829             * @type Array
830             */
831             use: {
832                 writeOnce: true,
833                 value: ['substitute', 'node', 'node-style', 'selector-css3']
834             },
835             /**
836             * @attribute container
837             * @description The container to append the iFrame to on render.
838             * @type String/HTMLElement/Node
839             */
840             container: {
841                 value: 'body',
842                 setter: function(n) {
843                     return Y.one(n);
844                 }
845             },
846             /**
847             * @attribute node
848             * @description The Node instance of the iframe.
849             * @type Node
850             */
851             node: {
852                 readOnly: true,
853                 value: null,
854                 getter: function() {
855                     return this._iframe;
856                 }
857             },
858             /**
859             * @attribute id
860             * @description Set the id of the new Node. (optional)
861             * @type String
862             * @writeonce
863             */
864             id: {
865                 writeOnce: true,
866                 getter: function(id) {
867                     if (!id) {
868                         id = 'iframe-' + Y.guid();
869                     }
870                     return id;
871                 }
872             },
873             /**
874             * @attribute linkedcss
875             * @description An array of url's to external linked style sheets
876             * @type String
877             */
878             linkedcss: {
879                 value: '',
880                 getter: '_getLinkedCSS',
881                 setter: '_setLinkedCSS'
882             },
883             /**
884             * @attribute extracss
885             * @description A string of CSS to add to the Head of the Editor
886             * @type String
887             */
888             extracss: {
889                 value: '',
890                 setter: '_setExtraCSS'
891             },
892             /**
893             * @attribute host
894             * @description A reference to the Editor instance 
895             * @type Object
896             */
897             host: {
898                 value: false
899             },
900             /**
901             * @attribute defaultblock
902             * @description The default tag to use for block level items, defaults to: p
903             * @type String
904             */            
905             defaultblock: {
906                 value: 'p'
907             }
908         }
909     });
910
911
912     Y.Frame = Frame;
913
914
915
916 }, '3.3.0' ,{requires:['base', 'node', 'selector-css3', 'substitute'], skinnable:false});
917 YUI.add('selection', function(Y) {
918
919     /**
920      * Wraps some common Selection/Range functionality into a simple object
921      * @module editor
922      * @submodule selection
923      */     
924     /**
925      * Wraps some common Selection/Range functionality into a simple object
926      * @class Selection
927      * @for Selection
928      * @constructor
929      */
930     
931     //TODO This shouldn't be there, Y.Node doesn't normalize getting textnode content.
932     var textContent = 'textContent',
933     INNER_HTML = 'innerHTML',
934     FONT_FAMILY = 'fontFamily';
935
936     if (Y.UA.ie) {
937         textContent = 'nodeValue';
938     }
939
940     Y.Selection = function(domEvent) {
941         var sel, par, ieNode, nodes, rng, i;
942
943         if (Y.config.win.getSelection) {
944                 sel = Y.config.win.getSelection();
945         } else if (Y.config.doc.selection) {
946             sel = Y.config.doc.selection.createRange();
947         }
948         this._selection = sel;
949
950         if (sel.pasteHTML) {
951             this.isCollapsed = (sel.compareEndPoints('StartToEnd', sel)) ? false : true;
952             if (this.isCollapsed) {
953                 this.anchorNode = this.focusNode = Y.one(sel.parentElement());
954
955                 if (domEvent) {
956                     ieNode = Y.config.doc.elementFromPoint(domEvent.clientX, domEvent.clientY);
957                 }
958                 if (!ieNode) {
959                     par = sel.parentElement();
960                     nodes = par.childNodes;
961                     rng = sel.duplicate();
962
963                     for (i = 0; i < nodes.length; i++) {
964                         //This causes IE to not allow a selection on a doubleclick
965                         //rng.select(nodes[i]);
966                         if (rng.inRange(sel)) {
967                             if (!ieNode) {
968                                 ieNode = nodes[i];
969                             }
970                         }
971                     }
972                 }
973
974                 this.ieNode = ieNode;
975                 
976                 if (ieNode) {
977                     if (ieNode.nodeType !== 3) {
978                         if (ieNode.firstChild) {
979                             ieNode = ieNode.firstChild;
980                         }
981                         if (ieNode && ieNode.tagName && ieNode.tagName.toLowerCase() === 'body') {
982                             if (ieNode.firstChild) {
983                                 ieNode = ieNode.firstChild;
984                             }
985                         }
986                     }
987                     this.anchorNode = this.focusNode = Y.Selection.resolve(ieNode);
988                     
989                     this.anchorOffset = this.focusOffset = (this.anchorNode.nodeValue) ? this.anchorNode.nodeValue.length : 0 ;
990                     
991                     this.anchorTextNode = this.focusTextNode = Y.one(ieNode);
992                 }
993                 
994                 
995             } else {
996                 //This helps IE deal with a selection and nodeChange events
997                 if (sel.htmlText) {
998                     var n = Y.Node.create(sel.htmlText);
999                     if (n.get('id')) {
1000                         var id = n.get('id');
1001                         this.anchorNode = this.focusNode = Y.one('#' + id);
1002                     } else {
1003                         n = n.get('childNodes');
1004                         this.anchorNode = this.focusNode = n.item(0);
1005                     }
1006                 }
1007             }
1008
1009             //var self = this;
1010             //debugger;
1011         } else {
1012             this.isCollapsed = sel.isCollapsed;
1013             this.anchorNode = Y.Selection.resolve(sel.anchorNode);
1014             this.focusNode = Y.Selection.resolve(sel.focusNode);
1015             this.anchorOffset = sel.anchorOffset;
1016             this.focusOffset = sel.focusOffset;
1017             
1018             this.anchorTextNode = Y.one(sel.anchorNode);
1019             this.focusTextNode = Y.one(sel.focusNode);
1020         }
1021         if (Y.Lang.isString(sel.text)) {
1022             this.text = sel.text;
1023         } else {
1024             if (sel.toString) {
1025                 this.text = sel.toString();
1026             } else {
1027                 this.text = '';
1028             }
1029         }
1030     };
1031     
1032     /**
1033     * Performs a prefilter on all nodes in the editor. Looks for nodes with a style: fontFamily or font face
1034     * It then creates a dynamic class assigns it and removed the property. This is so that we don't lose
1035     * the fontFamily when selecting nodes.
1036     * @static
1037     * @method filter
1038     */
1039     Y.Selection.filter = function(blocks) {
1040         var startTime = (new Date()).getTime();
1041
1042         var nodes = Y.all(Y.Selection.ALL),
1043             baseNodes = Y.all('strong,em'),
1044             doc = Y.config.doc,
1045             hrs = doc.getElementsByTagName('hr'),
1046             classNames = {}, cssString = '',
1047             ls;
1048
1049         var startTime1 = (new Date()).getTime();
1050         nodes.each(function(n) {
1051             var raw = Y.Node.getDOMNode(n);
1052             if (raw.style[FONT_FAMILY]) {
1053                 classNames['.' + n._yuid] = raw.style[FONT_FAMILY];
1054                 n.addClass(n._yuid);
1055                 raw.style[FONT_FAMILY] = 'inherit';
1056
1057                 raw.removeAttribute('face');
1058                 if (raw.getAttribute('style') === '') {
1059                     raw.removeAttribute('style');
1060                 }
1061                 //This is for IE
1062                 if (raw.getAttribute('style')) {
1063                     if (raw.getAttribute('style').toLowerCase() === 'font-family: ') {
1064                         raw.removeAttribute('style');
1065                     }
1066                 }
1067             }
1068             /*
1069             if (n.getStyle(FONT_FAMILY)) {
1070                 classNames['.' + n._yuid] = n.getStyle(FONT_FAMILY);
1071                 n.addClass(n._yuid);
1072                 n.removeAttribute('face');
1073                 n.setStyle(FONT_FAMILY, '');
1074                 if (n.getAttribute('style') === '') {
1075                     n.removeAttribute('style');
1076                 }
1077                 //This is for IE
1078                 if (n.getAttribute('style').toLowerCase() === 'font-family: ') {
1079                     n.removeAttribute('style');
1080                 }
1081             }
1082             */
1083         });
1084         var endTime1 = (new Date()).getTime();
1085
1086         Y.all('.hr').addClass('yui-skip').addClass('yui-non');
1087
1088         Y.each(hrs, function(hr) {
1089             var el = doc.createElement('div');
1090                 el.className = 'hr yui-non yui-skip';
1091                 
1092                 el.setAttribute('readonly', true);
1093                 el.setAttribute('contenteditable', false); //Keep it from being Edited
1094                 if (hr.parentNode) {
1095                     hr.parentNode.replaceChild(el, hr);
1096                 }
1097                 //Had to move to inline style. writes for ie's < 8. They don't render el.setAttribute('style');
1098                 var s = el.style;
1099                 s.border = '1px solid #ccc';
1100                 s.lineHeight = '0';
1101                 s.fontSize = '0';
1102                 s.marginTop = '5px';
1103                 s.marginBottom = '5px';
1104                 s.marginLeft = '0px';
1105                 s.marginRight = '0px';
1106                 s.padding = '0';
1107         });
1108         
1109
1110         Y.each(classNames, function(v, k) {
1111             cssString += k + ' { font-family: ' + v.replace(/"/gi, '') + '; }';
1112         });
1113         Y.StyleSheet(cssString, 'editor');
1114
1115         
1116         //Not sure about this one?
1117         baseNodes.each(function(n, k) {
1118             var t = n.get('tagName').toLowerCase(),
1119                 newTag = 'i';
1120             if (t === 'strong') {
1121                 newTag = 'b';
1122             }
1123             Y.Selection.prototype._swap(baseNodes.item(k), newTag);
1124         });
1125
1126         //Filter out all the empty UL/OL's
1127         ls = Y.all('ol,ul');
1128         ls.each(function(v, k) {
1129             var lis = v.all('li');
1130             if (!lis.size()) {
1131                 v.remove();
1132             }
1133         });
1134         
1135         if (blocks) {
1136             Y.Selection.filterBlocks();
1137         }
1138         var endTime = (new Date()).getTime();
1139     };
1140
1141     /**
1142     * Method attempts to replace all "orphined" text nodes in the main body by wrapping them with a <p>. Called from filter.
1143     * @static
1144     * @method filterBlocks
1145     */
1146     Y.Selection.filterBlocks = function() {
1147         var startTime = (new Date()).getTime();
1148         var childs = Y.config.doc.body.childNodes, i, node, wrapped = false, doit = true,
1149             sel, single, br, divs, spans, c, s;
1150
1151         if (childs) {
1152             for (i = 0; i < childs.length; i++) {
1153                 node = Y.one(childs[i]);
1154                 if (!node.test(Y.Selection.BLOCKS)) {
1155                     doit = true;
1156                     if (childs[i].nodeType == 3) {
1157                         c = childs[i][textContent].match(Y.Selection.REG_CHAR);
1158                         s = childs[i][textContent].match(Y.Selection.REG_NON);
1159                         if (c === null && s) {
1160                             doit = false;
1161                             
1162                         }
1163                     }
1164                     if (doit) {
1165                         if (!wrapped) {
1166                             wrapped = [];
1167                         }
1168                         wrapped.push(childs[i]);
1169                     }
1170                 } else {
1171                     wrapped = Y.Selection._wrapBlock(wrapped);
1172                 }
1173             }
1174             wrapped = Y.Selection._wrapBlock(wrapped);
1175         }
1176
1177         single = Y.all(Y.Selection.DEFAULT_BLOCK_TAG);
1178         if (single.size() === 1) {
1179             br = single.item(0).all('br');
1180             if (br.size() === 1) {
1181                 if (!br.item(0).test('.yui-cursor')) {
1182                     br.item(0).remove();
1183                 }
1184                 var html = single.item(0).get('innerHTML');
1185                 if (html === '' || html === ' ') {
1186                     single.set('innerHTML', Y.Selection.CURSOR);
1187                     sel = new Y.Selection();
1188                     sel.focusCursor(true, true);
1189                 }
1190             }
1191         } else {
1192             single.each(function(p) {
1193                 var html = p.get('innerHTML');
1194                 if (html === '') {
1195                     p.remove();
1196                 }
1197             });
1198         }
1199         
1200         if (!Y.UA.ie) {
1201             /*
1202             divs = Y.all('div, p');
1203             divs.each(function(d) {
1204                 if (d.hasClass('yui-non')) {
1205                     return;
1206                 }
1207                 var html = d.get('innerHTML');
1208                 if (html === '') {
1209                     d.remove();
1210                 } else {
1211                     if (d.get('childNodes').size() == 1) {
1212                         if (d.ancestor('p')) {
1213                             d.replace(d.get('firstChild'));
1214                         }
1215                     }
1216                 }
1217             });*/
1218
1219             /** Removed this, as it was causing Pasting to be funky in Safari
1220             spans = Y.all('.Apple-style-span, .apple-style-span');
1221             spans.each(function(s) {
1222                 s.setAttribute('style', '');
1223             });
1224             */
1225         }
1226
1227
1228         var endTime = (new Date()).getTime();
1229     };
1230
1231     /**
1232     * Regular Expression to determine if a string has a character in it
1233     * @static
1234     * @property REG_CHAR
1235     */   
1236     Y.Selection.REG_CHAR = /[a-zA-Z-0-9_]/gi;
1237
1238     /**
1239     * Regular Expression to determine if a string has a non-character in it
1240     * @static
1241     * @property REG_NON
1242     */
1243     Y.Selection.REG_NON = /[\s\S|\n|\t]/gi;
1244
1245     /**
1246     * Regular Expression to remove all HTML from a string
1247     * @static
1248     * @property REG_NOHTML
1249     */
1250     Y.Selection.REG_NOHTML = /<\S[^><]*>/g;
1251
1252
1253     /**
1254     * Wraps an array of elements in a Block level tag
1255     * @static
1256     * @private
1257     * @method _wrapBlock
1258     */
1259     Y.Selection._wrapBlock = function(wrapped) {
1260         if (wrapped) {
1261             var newChild = Y.Node.create('<' + Y.Selection.DEFAULT_BLOCK_TAG + '></' + Y.Selection.DEFAULT_BLOCK_TAG + '>'),
1262                 firstChild = Y.one(wrapped[0]), i;
1263
1264             for (i = 1; i < wrapped.length; i++) {
1265                 newChild.append(wrapped[i]);
1266             }
1267             firstChild.replace(newChild);
1268             newChild.prepend(firstChild);
1269         }
1270         return false;
1271     };
1272
1273     /**
1274     * Undoes what filter does enough to return the HTML from the Editor, then re-applies the filter.
1275     * @static
1276     * @method unfilter
1277     * @return {String} The filtered HTML
1278     */
1279     Y.Selection.unfilter = function() {
1280         var nodes = Y.all('body [class]'),
1281             html = '', nons, ids,
1282             body = Y.one('body');
1283         
1284         
1285         nodes.each(function(n) {
1286             if (n.hasClass(n._yuid)) {
1287                 //One of ours
1288                 n.setStyle(FONT_FAMILY, n.getStyle(FONT_FAMILY));
1289                 n.removeClass(n._yuid);
1290                 if (n.getAttribute('class') === '') {
1291                     n.removeAttribute('class');
1292                 }
1293             }
1294         });
1295
1296         nons = Y.all('.yui-non');
1297         nons.each(function(n) {
1298             if (!n.hasClass('yui-skip') && n.get('innerHTML') === '') {
1299                 n.remove();
1300             } else {
1301                 n.removeClass('yui-non').removeClass('yui-skip');
1302             }
1303         });
1304
1305         ids = Y.all('body [id]');
1306         ids.each(function(n) {
1307             if (n.get('id').indexOf('yui_3_') === 0) {
1308                 n.removeAttribute('id');
1309                 n.removeAttribute('_yuid');
1310             }
1311         });
1312         
1313         if (body) {
1314             html = body.get('innerHTML');
1315         }
1316         
1317         Y.all('.hr').addClass('yui-skip').addClass('yui-non');
1318         
1319         /*
1320         nodes.each(function(n) {
1321             n.addClass(n._yuid);
1322             n.setStyle(FONT_FAMILY, '');
1323             if (n.getAttribute('style') === '') {
1324                 n.removeAttribute('style');
1325             }
1326         });
1327         */
1328         
1329         return html;
1330     };
1331     /**
1332     * Resolve a node from the selection object and return a Node instance
1333     * @static
1334     * @method resolve
1335     * @param {HTMLElement} n The HTMLElement to resolve. Might be a TextNode, gives parentNode.
1336     * @return {Node} The Resolved node
1337     */
1338     Y.Selection.resolve = function(n) {
1339         if (n && n.nodeType === 3) {
1340             //Adding a try/catch here because in rare occasions IE will
1341             //Throw a error accessing the parentNode of a stranded text node.
1342             //In the case of Ctrl+Z (Undo)
1343             try {
1344                 n = n.parentNode;
1345             } catch (re) {
1346                 n = 'body';
1347             }
1348         }
1349         return Y.one(n);
1350     };
1351
1352     /**
1353     * Returns the innerHTML of a node with all HTML tags removed.
1354     * @static
1355     * @method getText
1356     * @param {Node} node The Node instance to remove the HTML from
1357     * @return {String} The string of text
1358     */
1359     Y.Selection.getText = function(node) {
1360         var txt = node.get('innerHTML').replace(Y.Selection.REG_NOHTML, '');
1361         //Clean out the cursor subs to see if the Node is empty
1362         txt = txt.replace('<span><br></span>', '').replace('<br>', '');
1363         return txt;
1364     };
1365
1366     //Y.Selection.DEFAULT_BLOCK_TAG = 'div';
1367     Y.Selection.DEFAULT_BLOCK_TAG = 'p';
1368
1369     /**
1370     * The selector to use when looking for Nodes to cache the value of: [style],font[face]
1371     * @static
1372     * @property ALL
1373     */
1374     Y.Selection.ALL = '[style],font[face]';
1375
1376     /**
1377     * The selector to use when looking for block level items.
1378     * @static
1379     * @property BLOCKS
1380     */
1381     Y.Selection.BLOCKS = 'p,div,ul,ol,table,style';
1382     /**
1383     * The temporary fontname applied to a selection to retrieve their values: yui-tmp
1384     * @static
1385     * @property TMP
1386     */
1387     Y.Selection.TMP = 'yui-tmp';
1388     /**
1389     * The default tag to use when creating elements: span
1390     * @static
1391     * @property DEFAULT_TAG
1392     */
1393     Y.Selection.DEFAULT_TAG = 'span';
1394
1395     /**
1396     * The id of the outer cursor wrapper
1397     * @static
1398     * @property DEFAULT_TAG
1399     */
1400     Y.Selection.CURID = 'yui-cursor';
1401
1402     /**
1403     * The id used to wrap the inner space of the cursor position
1404     * @static
1405     * @property CUR_WRAPID
1406     */
1407     Y.Selection.CUR_WRAPID = 'yui-cursor-wrapper';
1408
1409     /**
1410     * The default HTML used to focus the cursor..
1411     * @static
1412     * @property CURSOR
1413     */
1414     Y.Selection.CURSOR = '<span><br class="yui-cursor"></span>';
1415
1416     Y.Selection.hasCursor = function() {
1417         var cur = Y.all('#' + Y.Selection.CUR_WRAPID);
1418         return cur.size();
1419     };
1420
1421     /**
1422     * Called from Editor keydown to remove the "extra" space before the cursor.
1423     * @static
1424     * @method cleanCursor
1425     */
1426     Y.Selection.cleanCursor = function() {
1427         var cur, sel = 'br.yui-cursor';
1428         cur = Y.all(sel);
1429         if (cur.size()) {
1430             cur.each(function(b) {
1431                 var c = b.get('parentNode.parentNode.childNodes'), html;
1432                 if (c.size() > 1) {
1433                     b.remove();
1434                 } else {
1435                     html = Y.Selection.getText(c.item(0));
1436                     if (html !== '') {
1437                         b.remove();
1438                     }
1439                 }
1440             });
1441         }
1442         /*
1443         var cur = Y.all('#' + Y.Selection.CUR_WRAPID);
1444         if (cur.size()) {
1445             cur.each(function(c) {
1446                 var html = c.get('innerHTML');
1447                 if (html == '&nbsp;' || html == '<br>') {
1448                     if (c.previous() || c.next()) {
1449                         c.remove();
1450                     }
1451                 }
1452             });
1453         }
1454         */
1455     };
1456
1457     Y.Selection.prototype = {
1458         /**
1459         * Range text value
1460         * @property text
1461         * @type String
1462         */
1463         text: null,
1464         /**
1465         * Flag to show if the range is collapsed or not
1466         * @property isCollapsed
1467         * @type Boolean
1468         */
1469         isCollapsed: null,
1470         /**
1471         * A Node instance of the parentNode of the anchorNode of the range
1472         * @property anchorNode
1473         * @type Node
1474         */
1475         anchorNode: null,
1476         /**
1477         * The offset from the range object
1478         * @property anchorOffset
1479         * @type Number
1480         */
1481         anchorOffset: null,
1482         /**
1483         * A Node instance of the actual textNode of the range.
1484         * @property anchorTextNode
1485         * @type Node
1486         */
1487         anchorTextNode: null,
1488         /**
1489         * A Node instance of the parentNode of the focusNode of the range
1490         * @property focusNode
1491         * @type Node
1492         */
1493         focusNode: null,
1494         /**
1495         * The offset from the range object
1496         * @property focusOffset
1497         * @type Number
1498         */
1499         focusOffset: null,
1500         /**
1501         * A Node instance of the actual textNode of the range.
1502         * @property focusTextNode
1503         * @type Node
1504         */
1505         focusTextNode: null,
1506         /**
1507         * The actual Selection/Range object
1508         * @property _selection
1509         * @private
1510         */
1511         _selection: null,
1512         /**
1513         * Wrap an element, with another element 
1514         * @private
1515         * @method _wrap
1516         * @param {HTMLElement} n The node to wrap 
1517         * @param {String} tag The tag to use when creating the new element.
1518         * @return {HTMLElement} The wrapped node
1519         */
1520         _wrap: function(n, tag) {
1521             var tmp = Y.Node.create('<' + tag + '></' + tag + '>');
1522             tmp.set(INNER_HTML, n.get(INNER_HTML));
1523             n.set(INNER_HTML, '');
1524             n.append(tmp);
1525             return Y.Node.getDOMNode(tmp);
1526         },
1527         /**
1528         * Swap an element, with another element 
1529         * @private
1530         * @method _swap
1531         * @param {HTMLElement} n The node to swap 
1532         * @param {String} tag The tag to use when creating the new element.
1533         * @return {HTMLElement} The new node
1534         */
1535         _swap: function(n, tag) {
1536             var tmp = Y.Node.create('<' + tag + '></' + tag + '>');
1537             tmp.set(INNER_HTML, n.get(INNER_HTML));
1538             n.replace(tmp, n);
1539             return Y.Node.getDOMNode(tmp);
1540         },
1541         /**
1542         * Get all the nodes in the current selection. This method will actually perform a filter first.
1543         * Then it calls doc.execCommand('fontname', null, 'yui-tmp') to touch all nodes in the selection.
1544         * The it compiles a list of all nodes affected by the execCommand and builds a NodeList to return.
1545         * @method getSelected
1546         * @return {NodeList} A NodeList of all items in the selection.
1547         */
1548         getSelected: function() {
1549             Y.Selection.filter();
1550             Y.config.doc.execCommand('fontname', null, Y.Selection.TMP);
1551             var nodes = Y.all(Y.Selection.ALL),
1552                 items = [];
1553             
1554             nodes.each(function(n, k) {
1555                 if (n.getStyle(FONT_FAMILY) ==  Y.Selection.TMP) {
1556                     n.setStyle(FONT_FAMILY, '');
1557                     n.removeAttribute('face');
1558                     if (n.getAttribute('style') === '') {
1559                         n.removeAttribute('style');
1560                     }
1561                     if (!n.test('body')) {
1562                         items.push(Y.Node.getDOMNode(nodes.item(k)));
1563                     }
1564                 }
1565             });
1566             return Y.all(items);
1567         },
1568         /**
1569         * Insert HTML at the current cursor position and return a Node instance of the newly inserted element.
1570         * @method insertContent
1571         * @param {String} html The HTML to insert.
1572         * @return {Node} The inserted Node.
1573         */
1574         insertContent: function(html) {
1575             return this.insertAtCursor(html, this.anchorTextNode, this.anchorOffset, true);
1576         },
1577         /**
1578         * Insert HTML at the current cursor position, this method gives you control over the text node to insert into and the offset where to put it.
1579         * @method insertAtCursor
1580         * @param {String} html The HTML to insert.
1581         * @param {Node} node The text node to break when inserting.
1582         * @param {Number} offset The left offset of the text node to break and insert the new content.
1583         * @param {Boolean} collapse Should the range be collapsed after insertion. default: false
1584         * @return {Node} The inserted Node.
1585         */
1586         insertAtCursor: function(html, node, offset, collapse) {
1587             var cur = Y.Node.create('<' + Y.Selection.DEFAULT_TAG + ' class="yui-non"></' + Y.Selection.DEFAULT_TAG + '>'),
1588                 inHTML, txt, txt2, newNode, range = this.createRange(), b;
1589
1590             if (node && node.test('body')) {
1591                 b = Y.Node.create('<span></span>');
1592                 node.append(b);
1593                 node = b;
1594             }
1595
1596             
1597             if (range.pasteHTML) {
1598                 newNode = Y.Node.create(html);
1599                 try {
1600                     range.pasteHTML('<span id="rte-insert"></span>');
1601                 } catch (e) {}
1602                 inHTML = Y.one('#rte-insert');
1603                 if (inHTML) {
1604                     inHTML.set('id', '');
1605                     inHTML.replace(newNode);
1606                     return newNode;
1607                 } else {
1608                     Y.on('available', function() {
1609                         inHTML.set('id', '');
1610                         inHTML.replace(newNode);
1611                     }, '#rte-insert');
1612                 }
1613             } else {
1614                 //TODO using Y.Node.create here throws warnings & strips first white space character
1615                 //txt = Y.one(Y.Node.create(inHTML.substr(0, offset)));
1616                 //txt2 = Y.one(Y.Node.create(inHTML.substr(offset)));
1617                 if (offset > 0) {
1618                     inHTML = node.get(textContent);
1619
1620                     txt = Y.one(Y.config.doc.createTextNode(inHTML.substr(0, offset)));
1621                     txt2 = Y.one(Y.config.doc.createTextNode(inHTML.substr(offset)));
1622
1623                     node.replace(txt, node);
1624                     newNode = Y.Node.create(html);
1625                     if (newNode.get('nodeType') === 11) {
1626                         b = Y.Node.create('<span></span>');
1627                         b.append(newNode);
1628                         newNode = b;
1629                     }
1630                     txt.insert(newNode, 'after');
1631                     //if (txt2 && txt2.get('length')) {
1632                     if (txt2) {
1633                         newNode.insert(cur, 'after');
1634                         cur.insert(txt2, 'after');
1635                         this.selectNode(cur, collapse);
1636                     }
1637                 } else {
1638                     if (node.get('nodeType') === 3) {
1639                         node = node.get('parentNode');
1640                     }
1641                     newNode = Y.Node.create(html);
1642                     html = node.get('innerHTML').replace(/\n/gi, '');
1643                     if (html === '' || html === '<br>') {
1644                         node.append(newNode);
1645                     } else {
1646                         if (newNode.get('parentNode')) {
1647                             node.insert(newNode, 'before');
1648                         } else {
1649                             Y.one('body').prepend(newNode);
1650                         }
1651                     }
1652                     if (node.get('firstChild').test('br')) {
1653                         node.get('firstChild').remove();
1654                     }
1655                 }
1656             }
1657             return newNode;
1658         },
1659         /**
1660         * Get all elements inside a selection and wrap them with a new element and return a NodeList of all elements touched.
1661         * @method wrapContent
1662         * @param {String} tag The tag to wrap all selected items with.
1663         * @return {NodeList} A NodeList of all items in the selection.
1664         */
1665         wrapContent: function(tag) {
1666             tag = (tag) ? tag : Y.Selection.DEFAULT_TAG;
1667
1668             if (!this.isCollapsed) {
1669                 var items = this.getSelected(),
1670                     changed = [], range, last, first, range2;
1671
1672                 items.each(function(n, k) {
1673                     var t = n.get('tagName').toLowerCase();
1674                     if (t === 'font') {
1675                         changed.push(this._swap(items.item(k), tag));
1676                     } else {
1677                         changed.push(this._wrap(items.item(k), tag));
1678                     }
1679                 }, this);
1680                 
1681                         range = this.createRange();
1682                 first = changed[0];
1683                 last = changed[changed.length - 1];
1684                 if (this._selection.removeAllRanges) {
1685                     range.setStart(changed[0], 0);
1686                     range.setEnd(last, last.childNodes.length);
1687                     this._selection.removeAllRanges();
1688                     this._selection.addRange(range);
1689                 } else {
1690                     range.moveToElementText(Y.Node.getDOMNode(first));
1691                     range2 = this.createRange();
1692                     range2.moveToElementText(Y.Node.getDOMNode(last));
1693                     range.setEndPoint('EndToEnd', range2);
1694                     range.select();
1695                 }
1696
1697                 changed = Y.all(changed);
1698                 return changed;
1699
1700
1701             } else {
1702                 return Y.all([]);
1703             }
1704         },
1705         /**
1706         * Find and replace a string inside a text node and replace it with HTML focusing the node after 
1707         * to allow you to continue to type.
1708         * @method replace
1709         * @param {String} se The string to search for.
1710         * @param {String} re The string of HTML to replace it with.
1711         * @return {Node} The node inserted.
1712         */
1713         replace: function(se,re) {
1714             var range = this.createRange(), node, txt, index, newNode;
1715
1716             if (range.getBookmark) {
1717                 index = range.getBookmark();
1718                 txt = this.anchorNode.get('innerHTML').replace(se, re);
1719                 this.anchorNode.set('innerHTML', txt);
1720                 range.moveToBookmark(index);
1721                 newNode = Y.one(range.parentElement());
1722             } else {
1723                 node = this.anchorTextNode;
1724                 txt = node.get(textContent);
1725                 index = txt.indexOf(se);
1726
1727                 txt = txt.replace(se, '');
1728                 node.set(textContent, txt);
1729                 newNode = this.insertAtCursor(re, node, index, true);
1730             }
1731             return newNode;
1732         },
1733         /**
1734         * Destroy the range.
1735         * @method remove
1736         * @chainable
1737         * @return {Y.Selection}
1738         */
1739         remove: function() {
1740             this._selection.removeAllRanges();
1741             return this;
1742         },
1743         /**
1744         * Wrapper for the different range creation methods.
1745         * @method createRange
1746         * @return {RangeObject}
1747         */
1748         createRange: function() {
1749             if (Y.config.doc.selection) {
1750                 return Y.config.doc.selection.createRange();
1751             } else {
1752                         return Y.config.doc.createRange();
1753             }
1754         },
1755         /**
1756         * Select a Node (hilighting it).
1757         * @method selectNode
1758         * @param {Node} node The node to select
1759         * @param {Boolean} collapse Should the range be collapsed after insertion. default: false
1760         * @chainable
1761         * @return {Y.Selection}
1762         */
1763         selectNode: function(node, collapse, end) {
1764             if (!node) {
1765                 return;
1766             }
1767             end = end || 0;
1768             node = Y.Node.getDOMNode(node);
1769                     var range = this.createRange();
1770             if (range.selectNode) {
1771                 range.selectNode(node);
1772                 this._selection.removeAllRanges();
1773                 this._selection.addRange(range);
1774                 if (collapse) {
1775                     try {
1776                         this._selection.collapse(node, end);
1777                     } catch (err) {
1778                         this._selection.collapse(node, 0);
1779                     }
1780                 }
1781             } else {
1782                 if (node.nodeType === 3) {
1783                     node = node.parentNode;
1784                 }
1785                 try {
1786                     range.moveToElementText(node);
1787                 } catch(e) {}
1788                 if (collapse) {
1789                     range.collapse(((end) ? false : true));
1790                 }
1791                 range.select();
1792             }
1793             return this;
1794         },
1795         /**
1796         * Put a placeholder in the DOM at the current cursor position.
1797         * @method setCursor
1798         * @return {Node}
1799         */
1800         setCursor: function() {
1801             this.removeCursor(false);
1802             return this.insertContent(Y.Selection.CURSOR);
1803         },
1804         /**
1805         * Get the placeholder in the DOM at the current cursor position.
1806         * @method getCursor
1807         * @return {Node}
1808         */
1809         getCursor: function() {
1810             return Y.all('#' + Y.Selection.CURID);
1811         },
1812         /**
1813         * Remove the cursor placeholder from the DOM.
1814         * @method removeCursor
1815         * @param {Boolean} keep Setting this to true will keep the node, but remove the unique parts that make it the cursor.
1816         * @return {Node}
1817         */
1818         removeCursor: function(keep) {
1819             var cur = this.getCursor();
1820             if (cur) {
1821                 if (keep) {
1822                     cur.removeAttribute('id');
1823                     cur.set('innerHTML', '<br class="yui-cursor">');
1824                 } else {
1825                     cur.remove();
1826                 }
1827             }
1828             return cur;
1829         },
1830         /**
1831         * Gets a stored cursor and focuses it for editing, must be called sometime after setCursor
1832         * @method focusCursor
1833         * @return {Node}
1834         */
1835         focusCursor: function(collapse, end) {
1836             if (collapse !== false) {
1837                 collapse = true;
1838             }
1839             if (end !== false) {
1840                 end = true;
1841             }
1842             var cur = this.removeCursor(true);
1843             if (cur) {
1844                 cur.each(function(c) {
1845                     this.selectNode(c, collapse, end);
1846                 }, this);
1847             }
1848         },
1849         /**
1850         * Generic toString for logging.
1851         * @method toString
1852         * @return {String}
1853         */
1854         toString: function() {
1855             return 'Selection Object';
1856         }
1857     };
1858
1859
1860 }, '3.3.0' ,{requires:['node'], skinnable:false});
1861 YUI.add('exec-command', function(Y) {
1862
1863
1864     /**
1865      * Plugin for the frame module to handle execCommands for Editor
1866      * @module editor
1867      * @submodule exec-command
1868      */     
1869     /**
1870      * Plugin for the frame module to handle execCommands for Editor
1871      * @class Plugin.ExecCommand
1872      * @extends Base
1873      * @constructor
1874      */
1875         var ExecCommand = function() {
1876             ExecCommand.superclass.constructor.apply(this, arguments);
1877         };
1878
1879         Y.extend(ExecCommand, Y.Base, {
1880             /**
1881             * An internal reference to the keyCode of the last key that was pressed.
1882             * @private
1883             * @property _lastKey
1884             */
1885             _lastKey: null,
1886             /**
1887             * An internal reference to the instance of the frame plugged into.
1888             * @private
1889             * @property _inst
1890             */
1891             _inst: null,
1892             /**
1893             * Execute a command on the frame's document.
1894             * @method command
1895             * @param {String} action The action to perform (bold, italic, fontname)
1896             * @param {String} value The optional value (helvetica)
1897             * @return {Node/NodeList} Should return the Node/Nodelist affected
1898             */
1899             command: function(action, value) {
1900                 var fn = ExecCommand.COMMANDS[action];
1901                 
1902                 /*
1903                 if (action !== 'insertbr') {
1904                     Y.later(0, this, function() {
1905                         var inst = this.getInstance();
1906                         if (inst && inst.Selection) {
1907                             inst.Selection.cleanCursor();
1908                         }
1909                     });
1910                 }
1911                 */
1912
1913                 if (fn) {
1914                     return fn.call(this, action, value);
1915                 } else {
1916                     return this._command(action, value);
1917                 }
1918             },
1919             /**
1920             * The private version of execCommand that doesn't filter for overrides.
1921             * @private
1922             * @method _command
1923             * @param {String} action The action to perform (bold, italic, fontname)
1924             * @param {String} value The optional value (helvetica)
1925             */
1926             _command: function(action, value) {
1927                 var inst = this.getInstance();
1928                 try {
1929                     try {
1930                         inst.config.doc.execCommand('styleWithCSS', null, 1);
1931                     } catch (e1) {
1932                         try {
1933                             inst.config.doc.execCommand('useCSS', null, 0);
1934                         } catch (e2) {
1935                         }
1936                     }
1937                     inst.config.doc.execCommand(action, null, value);
1938                 } catch (e) {
1939                 }
1940             },
1941             /**
1942             * Get's the instance of YUI bound to the parent frame
1943             * @method getInstance
1944             * @return {YUI} The YUI instance bound to the parent frame
1945             */
1946             getInstance: function() {
1947                 if (!this._inst) {
1948                     this._inst = this.get('host').getInstance();
1949                 }
1950                 return this._inst;
1951             },
1952             initializer: function() {
1953                 Y.mix(this.get('host'), {
1954                     execCommand: function(action, value) {
1955                         return this.exec.command(action, value);
1956                     },
1957                     _execCommand: function(action, value) {
1958                         return this.exec._command(action, value);
1959                     }
1960                 });
1961
1962                 this.get('host').on('dom:keypress', Y.bind(function(e) {
1963                     this._lastKey = e.keyCode;
1964                 }, this));
1965             }
1966         }, {
1967             /**
1968             * execCommand
1969             * @property NAME
1970             * @static
1971             */
1972             NAME: 'execCommand',
1973             /**
1974             * exec
1975             * @property NS
1976             * @static
1977             */
1978             NS: 'exec',
1979             ATTRS: {
1980                 host: {
1981                     value: false
1982                 }
1983             },
1984             /**
1985             * Static object literal of execCommand overrides
1986             * @property COMMANDS
1987             * @static
1988             */
1989             COMMANDS: {
1990                 /**
1991                 * Wraps the content with a new element of type (tag)
1992                 * @method COMMANDS.wrap
1993                 * @static
1994                 * @param {String} cmd The command executed: wrap
1995                 * @param {String} tag The tag to wrap the selection with
1996                 * @return {NodeList} NodeList of the items touched by this command.
1997                 */
1998                 wrap: function(cmd, tag) {
1999                     var inst = this.getInstance();
2000                     return (new inst.Selection()).wrapContent(tag);
2001                 },
2002                 /**
2003                 * Inserts the provided HTML at the cursor, should be a single element.
2004                 * @method COMMANDS.inserthtml
2005                 * @static
2006                 * @param {String} cmd The command executed: inserthtml
2007                 * @param {String} html The html to insert
2008                 * @return {Node} Node instance of the item touched by this command.
2009                 */
2010                 inserthtml: function(cmd, html) {
2011                     var inst = this.getInstance();
2012                     if (inst.Selection.hasCursor() || Y.UA.ie) {
2013                         return (new inst.Selection()).insertContent(html);
2014                     } else {
2015                         this._command('inserthtml', html);
2016                     }
2017                 },
2018                 /**
2019                 * Inserts the provided HTML at the cursor, and focuses the cursor afterwards.
2020                 * @method COMMANDS.insertandfocus
2021                 * @static
2022                 * @param {String} cmd The command executed: insertandfocus
2023                 * @param {String} html The html to insert
2024                 * @return {Node} Node instance of the item touched by this command.
2025                 */
2026                 insertandfocus: function(cmd, html) {
2027                     var inst = this.getInstance(), out, sel;
2028                     if (inst.Selection.hasCursor()) {
2029                         html += inst.Selection.CURSOR;
2030                         out = this.command('inserthtml', html);
2031                         sel = new inst.Selection();
2032                         sel.focusCursor(true, true);
2033                     } else {
2034                         this.command('inserthtml', html);
2035                     }
2036                     return out;
2037                 },
2038                 /**
2039                 * Inserts a BR at the current cursor position
2040                 * @method COMMANDS.insertbr
2041                 * @static
2042                 * @param {String} cmd The command executed: insertbr
2043                 */
2044                 insertbr: function(cmd) {
2045                     var inst = this.getInstance(), cur,
2046                         sel = new inst.Selection();
2047
2048                     sel.setCursor();
2049                     cur = sel.getCursor();
2050                     cur.insert('<br>', 'before');
2051                     sel.focusCursor(true, false);
2052                     return ((cur && cur.previous) ? cur.previous() : null);
2053                 },
2054                 /**
2055                 * Inserts an image at the cursor position
2056                 * @method COMMANDS.insertimage
2057                 * @static
2058                 * @param {String} cmd The command executed: insertimage
2059                 * @param {String} img The url of the image to be inserted
2060                 * @return {Node} Node instance of the item touched by this command.
2061                 */
2062                 insertimage: function(cmd, img) {
2063                     return this.command('inserthtml', '<img src="' + img + '">');
2064                 },
2065                 /**
2066                 * Add a class to all of the elements in the selection
2067                 * @method COMMANDS.addclass
2068                 * @static
2069                 * @param {String} cmd The command executed: addclass
2070                 * @param {String} cls The className to add
2071                 * @return {NodeList} NodeList of the items touched by this command.
2072                 */
2073                 addclass: function(cmd, cls) {
2074                     var inst = this.getInstance();
2075                     return (new inst.Selection()).getSelected().addClass(cls);
2076                 },
2077                 /**
2078                 * Remove a class from all of the elements in the selection
2079                 * @method COMMANDS.removeclass
2080                 * @static
2081                 * @param {String} cmd The command executed: removeclass
2082                 * @param {String} cls The className to remove
2083                 * @return {NodeList} NodeList of the items touched by this command.
2084                 */
2085                 removeclass: function(cmd, cls) {
2086                     var inst = this.getInstance();
2087                     return (new inst.Selection()).getSelected().removeClass(cls);
2088                 },
2089                 /**
2090                 * Adds a forecolor to the current selection, or creates a new element and applies it
2091                 * @method COMMANDS.forecolor
2092                 * @static
2093                 * @param {String} cmd The command executed: forecolor
2094                 * @param {String} val The color value to apply
2095                 * @return {NodeList} NodeList of the items touched by this command.
2096                 */
2097                 forecolor: function(cmd, val) {
2098                     var inst = this.getInstance(),
2099                         sel = new inst.Selection(), n;
2100
2101                     if (!Y.UA.ie) {
2102                         this._command('useCSS', false);
2103                     }
2104                     if (inst.Selection.hasCursor()) {
2105                         if (sel.isCollapsed) {
2106                             if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === '&nbsp;')) {
2107                                 sel.anchorNode.setStyle('color', val);
2108                                 n = sel.anchorNode;
2109                             } else {
2110                                 n = this.command('inserthtml', '<span style="color: ' + val + '">' + inst.Selection.CURSOR + '</span>');
2111                                 sel.focusCursor(true, true);
2112                             }
2113                             return n;
2114                         } else {
2115                             return this._command(cmd, val);
2116                         }
2117                     } else {
2118                         this._command(cmd, val);
2119                     }
2120                 },
2121                 /**
2122                 * Adds a background color to the current selection, or creates a new element and applies it
2123                 * @method COMMANDS.backcolor
2124                 * @static
2125                 * @param {String} cmd The command executed: backcolor
2126                 * @param {String} val The color value to apply
2127                 * @return {NodeList} NodeList of the items touched by this command.
2128                 */
2129                 backcolor: function(cmd, val) {
2130                     var inst = this.getInstance(),
2131                         sel = new inst.Selection(), n;
2132                     
2133                     if (Y.UA.gecko || Y.UA.opera) {
2134                         cmd = 'hilitecolor';
2135                     }
2136                     if (!Y.UA.ie) {
2137                         this._command('useCSS', false);
2138                     }
2139                     if (inst.Selection.hasCursor()) {
2140                         if (sel.isCollapsed) {
2141                             if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === '&nbsp;')) {
2142                                 sel.anchorNode.setStyle('backgroundColor', val);
2143                                 n = sel.anchorNode;
2144                             } else {
2145                                 n = this.command('inserthtml', '<span style="background-color: ' + val + '">' + inst.Selection.CURSOR + '</span>');
2146                                 sel.focusCursor(true, true);
2147                             }
2148                             return n;
2149                         } else {
2150                             return this._command(cmd, val);
2151                         }
2152                     } else {
2153                         this._command(cmd, val);
2154                     }
2155                 },
2156                 /**
2157                 * Sugar method, calles backcolor
2158                 * @method COMMANDS.hilitecolor
2159                 * @static
2160                 * @param {String} cmd The command executed: backcolor
2161                 * @param {String} val The color value to apply
2162                 * @return {NodeList} NodeList of the items touched by this command.
2163                 */
2164                 hilitecolor: function() {
2165                     return ExecCommand.COMMANDS.backcolor.apply(this, arguments);
2166                 },
2167                 /**
2168                 * Adds a font name to the current selection, or creates a new element and applies it
2169                 * @method COMMANDS.fontname
2170                 * @static
2171                 * @param {String} cmd The command executed: fontname
2172                 * @param {String} val The font name to apply
2173                 * @return {NodeList} NodeList of the items touched by this command.
2174                 */
2175                 fontname: function(cmd, val) {
2176                     this._command('fontname', val);
2177                     var inst = this.getInstance(),
2178                         sel = new inst.Selection();
2179                     
2180                     if (sel.isCollapsed && (this._lastKey != 32)) {
2181                         if (sel.anchorNode.test('font')) {
2182                             sel.anchorNode.set('face', val);
2183                         }
2184                     }
2185                 },
2186                 /**
2187                 * Adds a fontsize to the current selection, or creates a new element and applies it
2188                 * @method COMMANDS.fontsize
2189                 * @static
2190                 * @param {String} cmd The command executed: fontsize
2191                 * @param {String} val The font size to apply
2192                 * @return {NodeList} NodeList of the items touched by this command.
2193                 */
2194                 fontsize: function(cmd, val) {
2195                     this._command('fontsize', val);
2196
2197                     var inst = this.getInstance(),
2198                         sel = new inst.Selection();
2199                     
2200                     if (sel.isCollapsed && sel.anchorNode && (this._lastKey != 32)) {
2201                         if (Y.UA.webkit) {
2202                             if (sel.anchorNode.getStyle('lineHeight')) {
2203                                 sel.anchorNode.setStyle('lineHeight', '');
2204                             }
2205                         }
2206                         if (sel.anchorNode.test('font')) {
2207                             sel.anchorNode.set('size', val);
2208                         } else if (Y.UA.gecko) {
2209                             var p = sel.anchorNode.ancestor(inst.Selection.DEFAULT_BLOCK_TAG);
2210                             if (p) {
2211                                 p.setStyle('fontSize', '');
2212                             }
2213                         }
2214                     }
2215                 }
2216             }
2217         });
2218         
2219         /**
2220         * This method is meant to normalize IE's in ability to exec the proper command on elements with CSS styling.
2221         * @method fixIETags
2222         * @protected
2223         * @param {String} cmd The command to execute
2224         * @param {String} tag The tag to create
2225         * @param {String} rule The rule that we are looking for.
2226         */
2227         var fixIETags = function(cmd, tag, rule) {
2228             var inst = this.getInstance(),
2229                 doc = inst.config.doc,
2230                 sel = doc.selection.createRange(),
2231                 o = doc.queryCommandValue(cmd),
2232                 html, reg, m, p, d, s, c;
2233
2234             if (o) {
2235                 html = sel.htmlText;
2236                 reg = new RegExp(rule, 'g');
2237                 m = html.match(reg);
2238
2239                 if (m) {
2240                     html = html.replace(rule + ';', '').replace(rule, '');
2241
2242                     sel.pasteHTML('<var id="yui-ie-bs">');
2243
2244                     p = doc.getElementById('yui-ie-bs');
2245                     d = doc.createElement('div');
2246                     s = doc.createElement(tag);
2247                     
2248                     d.innerHTML = html;
2249                     if (p.parentNode !== inst.config.doc.body) {
2250                         p = p.parentNode;
2251                     }
2252
2253                     c = d.childNodes;
2254
2255                     p.parentNode.replaceChild(s, p);
2256
2257                     Y.each(c, function(f) {
2258                         s.appendChild(f);
2259                     });
2260                     sel.collapse();
2261                     sel.moveToElementText(s);
2262                     sel.select();
2263                 }
2264             }
2265             this._command(cmd);
2266         };
2267
2268         if (Y.UA.ie) {
2269             ExecCommand.COMMANDS.bold = function() {
2270                 fixIETags.call(this, 'bold', 'b', 'FONT-WEIGHT: bold');
2271             }
2272             ExecCommand.COMMANDS.italic = function() {
2273                 fixIETags.call(this, 'italic', 'i', 'FONT-STYLE: italic');
2274             }
2275             ExecCommand.COMMANDS.underline = function() {
2276                 fixIETags.call(this, 'underline', 'u', 'TEXT-DECORATION: underline');
2277             }
2278         }
2279
2280         Y.namespace('Plugin');
2281         Y.Plugin.ExecCommand = ExecCommand;
2282
2283
2284
2285 }, '3.3.0' ,{requires:['frame'], skinnable:false});
2286 YUI.add('editor-tab', function(Y) {
2287
2288     /**
2289      * Handles tab and shift-tab indent/outdent support.
2290      * @module editor
2291      * @submodule editor-tab
2292      */     
2293     /**
2294      * Handles tab and shift-tab indent/outdent support.
2295      * @class Plugin.EditorTab
2296      * @constructor
2297      * @extends Base
2298      */
2299     
2300     var EditorTab = function() {
2301         EditorTab.superclass.constructor.apply(this, arguments);
2302     }, HOST = 'host';
2303
2304     Y.extend(EditorTab, Y.Base, {
2305         /**
2306         * Listener for host's nodeChange event and captures the tabkey interaction.
2307         * @private
2308         * @method _onNodeChange
2309         * @param {Event} e The Event facade passed from the host.
2310         */
2311         _onNodeChange: function(e) {
2312             var action = 'indent';
2313
2314             if (e.changedType === 'tab') {
2315                 if (!e.changedNode.test('li, li *')) {
2316                     e.changedEvent.halt();
2317                     e.preventDefault();
2318                     if (e.changedEvent.shiftKey) {
2319                         action = 'outdent';
2320                     }
2321
2322                     this.get(HOST).execCommand(action, '');
2323                 }
2324             }
2325         },
2326         initializer: function() {
2327             this.get(HOST).on('nodeChange', Y.bind(this._onNodeChange, this));
2328         }
2329     }, {
2330         /**
2331         * editorTab
2332         * @property NAME
2333         * @static
2334         */
2335         NAME: 'editorTab',
2336         /**
2337         * tab
2338         * @property NS
2339         * @static
2340         */
2341         NS: 'tab',
2342         ATTRS: {
2343             host: {
2344                 value: false
2345             }
2346         }
2347     });
2348
2349
2350     Y.namespace('Plugin');
2351
2352     Y.Plugin.EditorTab = EditorTab;
2353
2354
2355 }, '3.3.0' ,{requires:['editor-base'], skinnable:false});
2356 YUI.add('createlink-base', function(Y) {
2357
2358     /**
2359      * Base class for Editor. Handles the business logic of Editor, no GUI involved only utility methods and events.
2360      * @module editor
2361      * @submodule createlink-base
2362      */     
2363     /**
2364      * Adds prompt style link creation. Adds an override for the <a href="Plugin.ExecCommand.html#method_COMMANDS.createlink">createlink execCommand</a>.
2365      * @class Plugin.CreateLinkBase
2366      * @static
2367      */
2368     
2369     var CreateLinkBase = {};
2370     /**
2371     * Strings used by the plugin
2372     * @property STRINGS
2373     * @static
2374     */
2375     CreateLinkBase.STRINGS = {
2376             /**
2377             * String used for the Prompt
2378             * @property PROMPT
2379             * @static
2380             */
2381             PROMPT: 'Please enter the URL for the link to point to:',
2382             /**
2383             * String used as the default value of the Prompt
2384             * @property DEFAULT
2385             * @static
2386             */
2387             DEFAULT: 'http://'
2388     };
2389
2390     Y.namespace('Plugin');
2391     Y.Plugin.CreateLinkBase = CreateLinkBase;
2392
2393     Y.mix(Y.Plugin.ExecCommand.COMMANDS, {
2394         /**
2395         * Override for the createlink method from the <a href="Plugin.CreateLinkBase.html">CreateLinkBase</a> plugin.
2396         * @for ExecCommand
2397         * @method COMMANDS.createlink
2398         * @static
2399         * @param {String} cmd The command executed: createlink
2400         * @return {Node} Node instance of the item touched by this command.
2401         */
2402         createlink: function(cmd) {
2403             var inst = this.get('host').getInstance(), out, a, sel, holder,
2404                 url = prompt(CreateLinkBase.STRINGS.PROMPT, CreateLinkBase.STRINGS.DEFAULT);
2405
2406             if (url) {
2407                 holder = inst.config.doc.createElement('div');
2408                 url = inst.config.doc.createTextNode(url);
2409                 holder.appendChild(url);
2410                 url = holder.innerHTML;
2411
2412
2413                 this.get('host')._execCommand(cmd, url);
2414                 sel = new inst.Selection();
2415                 out = sel.getSelected();
2416                 if (!sel.isCollapsed && out.size()) {
2417                     //We have a selection
2418                     a = out.item(0).one('a');
2419                     if (a) {
2420                         out.item(0).replace(a);
2421                     }
2422                     if (Y.UA.gecko) {
2423                         if (a.get('parentNode').test('span')) {
2424                             if (a.get('parentNode').one('br.yui-cursor')) {
2425                                 a.get('parentNode').insert(a, 'before');
2426                             }
2427                         }
2428                     }
2429                 } else {
2430                     //No selection, insert a new node..
2431                     this.get('host').execCommand('inserthtml', '<a href="' + url + '">' + url + '</a>');
2432                 }
2433             }
2434             return a;
2435         }
2436     });
2437
2438
2439
2440 }, '3.3.0' ,{requires:['editor-base'], skinnable:false});
2441 YUI.add('editor-base', function(Y) {
2442
2443
2444     /**
2445      * Base class for Editor. Handles the business logic of Editor, no GUI involved only utility methods and events.
2446      * @module editor
2447      * @submodule editor-base
2448      */     
2449     /**
2450      * Base class for Editor. Handles the business logic of Editor, no GUI involved only utility methods and events.
2451      * @class EditorBase
2452      * @for EditorBase
2453      * @extends Base
2454      * @constructor
2455      */
2456     
2457     var EditorBase = function() {
2458         EditorBase.superclass.constructor.apply(this, arguments);
2459     }, LAST_CHILD = ':last-child', BODY = 'body';
2460
2461     Y.extend(EditorBase, Y.Base, {
2462         /**
2463         * Internal reference to the Y.Frame instance
2464         * @property frame
2465         */
2466         frame: null,
2467         initializer: function() {
2468             var frame = new Y.Frame({
2469                 designMode: true,
2470                 title: EditorBase.STRINGS.title,
2471                 use: EditorBase.USE,
2472                 dir: this.get('dir'),
2473                 extracss: this.get('extracss'),
2474                 linkedcss: this.get('linkedcss'),
2475                 defaultblock: this.get('defaultblock'),
2476                 host: this
2477             }).plug(Y.Plugin.ExecCommand);
2478
2479
2480             frame.after('ready', Y.bind(this._afterFrameReady, this));
2481             frame.addTarget(this);
2482
2483             this.frame = frame;
2484
2485             this.publish('nodeChange', {
2486                 emitFacade: true,
2487                 bubbles: true,
2488                 defaultFn: this._defNodeChangeFn
2489             });
2490             
2491             //this.plug(Y.Plugin.EditorPara);
2492         },
2493         destructor: function() {
2494             this.frame.destroy();
2495
2496             this.detachAll();
2497         },
2498         /**
2499         * Copy certain styles from one node instance to another (used for new paragraph creation mainly)
2500         * @method copyStyles
2501         * @param {Node} from The Node instance to copy the styles from 
2502         * @param {Node} to The Node instance to copy the styles to
2503         */
2504         copyStyles: function(from, to) {
2505             if (from.test('a')) {
2506                 //Don't carry the A styles
2507                 return;
2508             }
2509             var styles = ['color', 'fontSize', 'fontFamily', 'backgroundColor', 'fontStyle' ],
2510                 newStyles = {};
2511
2512             Y.each(styles, function(v) {
2513                 newStyles[v] = from.getStyle(v);
2514             });
2515             if (from.ancestor('b,strong')) {
2516                 newStyles.fontWeight = 'bold';
2517             }
2518             if (from.ancestor('u')) {
2519                 if (!newStyles.textDecoration) {
2520                     newStyles.textDecoration = 'underline';
2521                 }
2522             }
2523             to.setStyles(newStyles);
2524         },
2525         /**
2526         * Holder for the selection bookmark in IE.
2527         * @property _lastBookmark
2528         * @private
2529         */
2530         _lastBookmark: null,
2531         /**
2532         * Resolves the e.changedNode in the nodeChange event if it comes from the document. If
2533         * the event came from the document, it will get the last child of the last child of the document
2534         * and return that instead.
2535         * @method _resolveChangedNode
2536         * @param {Node} n The node to resolve
2537         * @private
2538         */
2539         _resolveChangedNode: function(n) {
2540             var inst = this.getInstance(), lc, lc2, found;
2541             if (inst && n && n.test('html')) {
2542                 lc = inst.one(BODY).one(LAST_CHILD);
2543                 while (!found) {
2544                     if (lc) {
2545                         lc2 = lc.one(LAST_CHILD);
2546                         if (lc2) {
2547                             lc = lc2;
2548                         } else {
2549                             found = true;
2550                         }
2551                     } else {
2552                         found = true;
2553                     }
2554                 }
2555                 if (lc) {
2556                     if (lc.test('br')) {
2557                         if (lc.previous()) {
2558                             lc = lc.previous();
2559                         } else {
2560                             lc = lc.get('parentNode');
2561                         }
2562                     }
2563                     if (lc) {
2564                         n = lc;
2565                     }
2566                 }
2567                 
2568             }
2569             return n;
2570         },
2571         /**
2572         * The default handler for the nodeChange event.
2573         * @method _defNodeChangeFn
2574         * @param {Event} e The event
2575         * @private
2576         */
2577         _defNodeChangeFn: function(e) {
2578             var startTime = (new Date()).getTime();
2579             var inst = this.getInstance(), sel, cur,
2580                 btag = inst.Selection.DEFAULT_BLOCK_TAG;
2581
2582             if (Y.UA.ie) {
2583                 try {
2584                     sel = inst.config.doc.selection.createRange();
2585                     if (sel.getBookmark) {
2586                         this._lastBookmark = sel.getBookmark();
2587                     }
2588                 } catch (ie) {}
2589             }
2590
2591             e.changedNode = this._resolveChangedNode(e.changedNode);
2592
2593             /*
2594             * @TODO
2595             * This whole method needs to be fixed and made more dynamic.
2596             * Maybe static functions for the e.changeType and an object bag
2597             * to walk through and filter to pass off the event to before firing..
2598             */
2599             
2600             switch (e.changedType) {
2601                 case 'keydown':
2602                     if (!Y.UA.gecko) {
2603                         if (!EditorBase.NC_KEYS[e.changedEvent.keyCode] && !e.changedEvent.shiftKey && !e.changedEvent.ctrlKey && (e.changedEvent.keyCode !== 13)) {
2604                             //inst.later(100, inst, inst.Selection.cleanCursor);
2605                         }
2606                     }
2607                     break;
2608                 case 'tab':
2609                     if (!e.changedNode.test('li, li *') && !e.changedEvent.shiftKey) {
2610                         e.changedEvent.frameEvent.preventDefault();
2611                         if (Y.UA.webkit) {
2612                             this.execCommand('inserttext', '\t');
2613                         } else if (Y.UA.gecko) {
2614                             this.frame.exec._command('inserthtml', '<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>');
2615                         } else if (Y.UA.ie) {
2616                             sel = new inst.Selection();
2617                             sel._selection.pasteHTML(EditorBase.TABKEY);
2618                         }
2619                     }
2620                     break;
2621             }
2622             if (Y.UA.webkit && e.commands && (e.commands.indent || e.commands.outdent)) {
2623                 /**
2624                 * When executing execCommand 'indent or 'outdent' Webkit applies
2625                 * a class to the BLOCKQUOTE that adds left/right margin to it
2626                 * This strips that style so it is just a normal BLOCKQUOTE
2627                 */
2628                 var bq = inst.all('.webkit-indent-blockquote');
2629                 if (bq.size()) {
2630                     bq.setStyle('margin', '');
2631                 }
2632             }
2633
2634             var changed = this.getDomPath(e.changedNode, false),
2635                 cmds = {}, family, fsize, classes = [],
2636                 fColor = '', bColor = '';
2637
2638             if (e.commands) {
2639                 cmds = e.commands;
2640             }
2641             
2642             Y.each(changed, function(el) {
2643                 var tag = el.tagName.toLowerCase(),
2644                     cmd = EditorBase.TAG2CMD[tag];
2645
2646                 if (cmd) {
2647                     cmds[cmd] = 1;
2648                 }
2649
2650                 //Bold and Italic styles
2651                 var s = el.currentStyle || el.style;
2652                 if ((''+s.fontWeight) == 'bold') { //Cast this to a string
2653                     cmds.bold = 1;
2654                 }
2655                 if (Y.UA.ie) {
2656                     if (s.fontWeight > 400) {
2657                         cmds.bold = 1;
2658                     }
2659                 }
2660                 if (s.fontStyle == 'italic') {
2661                     cmds.italic = 1;
2662                 }
2663                 if (s.textDecoration == 'underline') {
2664                     cmds.underline = 1;
2665                 }
2666                 if (s.textDecoration == 'line-through') {
2667                     cmds.strikethrough = 1;
2668                 }
2669                 
2670                 var n = inst.one(el);
2671                 if (n.getStyle('fontFamily')) {
2672                     var family2 = n.getStyle('fontFamily').split(',')[0].toLowerCase();
2673                     if (family2) {
2674                         family = family2;
2675                     }
2676                     if (family) {
2677                         family = family.replace(/'/g, '').replace(/"/g, '');
2678                     }
2679                 }
2680
2681                 fsize = EditorBase.NORMALIZE_FONTSIZE(n);
2682
2683
2684                 var cls = el.className.split(' ');
2685
2686                 Y.each(cls, function(v) {
2687                     if (v !== '' && (v.substr(0, 4) !== 'yui_')) {
2688                         classes.push(v);
2689                     }
2690                 });
2691
2692                 fColor = EditorBase.FILTER_RGB(n.getStyle('color'));
2693                 var bColor2 = EditorBase.FILTER_RGB(s.backgroundColor);
2694                 if (bColor2 !== 'transparent') {
2695                     if (bColor2 !== '') {
2696                         bColor = bColor2;
2697                     }
2698                 }
2699                 
2700             });
2701             
2702             e.dompath = inst.all(changed);
2703             e.classNames = classes;
2704             e.commands = cmds;
2705
2706             //TODO Dont' like this, not dynamic enough..
2707             if (!e.fontFamily) {
2708                 e.fontFamily = family;
2709             }
2710             if (!e.fontSize) {
2711                 e.fontSize = fsize;
2712             }
2713             if (!e.fontColor) {
2714                 e.fontColor = fColor;
2715             }
2716             if (!e.backgroundColor) {
2717                 e.backgroundColor = bColor;
2718             }
2719
2720             var endTime = (new Date()).getTime();
2721         },
2722         /**
2723         * Walk the dom tree from this node up to body, returning a reversed array of parents.
2724         * @method getDomPath
2725         * @param {Node} node The Node to start from 
2726         */
2727         getDomPath: function(node, nodeList) {
2728                         var domPath = [], domNode,
2729                 inst = this.frame.getInstance();
2730
2731             domNode = inst.Node.getDOMNode(node);
2732             //return inst.all(domNode);
2733
2734             while (domNode !== null) {
2735                 
2736                 if ((domNode === inst.config.doc.documentElement) || (domNode === inst.config.doc) || !domNode.tagName) {
2737                     domNode = null;
2738                     break;
2739                 }
2740                 
2741                 if (!inst.DOM.inDoc(domNode)) {
2742                     domNode = null;
2743                     break;
2744                 }
2745                 
2746                 //Check to see if we get el.nodeName and nodeType
2747                 if (domNode.nodeName && domNode.nodeType && (domNode.nodeType == 1)) {
2748                     domPath.push(domNode);
2749                 }
2750
2751                 if (domNode == inst.config.doc.body) {
2752                     domNode = null;
2753                     break;
2754                 }
2755
2756                 domNode = domNode.parentNode;
2757             }
2758
2759             /*{{{ Using Node 
2760             while (node !== null) {
2761                 if (node.test('html') || node.test('doc') || !node.get('tagName')) {
2762                     node = null;
2763                     break;
2764                 }
2765                 if (!node.inDoc()) {
2766                     node = null;
2767                     break;
2768                 }
2769                 //Check to see if we get el.nodeName and nodeType
2770                 if (node.get('nodeName') && node.get('nodeType') && (node.get('nodeType') == 1)) {
2771                     domPath.push(inst.Node.getDOMNode(node));
2772                 }
2773
2774                 if (node.test('body')) {
2775                     node = null;
2776                     break;
2777                 }
2778
2779                 node = node.get('parentNode');
2780             }
2781             }}}*/
2782
2783             if (domPath.length === 0) {
2784                 domPath[0] = inst.config.doc.body;
2785             }
2786
2787             if (nodeList) {
2788                 return inst.all(domPath.reverse());
2789             } else {
2790                 return domPath.reverse();
2791             }
2792
2793         },
2794         /**
2795         * After frame ready, bind mousedown & keyup listeners
2796         * @method _afterFrameReady
2797         * @private
2798         */
2799         _afterFrameReady: function() {
2800             var inst = this.frame.getInstance();
2801             
2802             this.frame.on('dom:mouseup', Y.bind(this._onFrameMouseUp, this));
2803             this.frame.on('dom:mousedown', Y.bind(this._onFrameMouseDown, this));
2804             this.frame.on('dom:keydown', Y.bind(this._onFrameKeyDown, this));
2805
2806             if (Y.UA.ie) {
2807                 this.frame.on('dom:activate', Y.bind(this._onFrameActivate, this));
2808                 this.frame.on('dom:beforedeactivate', Y.bind(this._beforeFrameDeactivate, this));
2809             }
2810             this.frame.on('dom:keyup', Y.bind(this._onFrameKeyUp, this));
2811             this.frame.on('dom:keypress', Y.bind(this._onFrameKeyPress, this));
2812
2813             inst.Selection.filter();
2814             this.fire('ready');
2815         },
2816         /**
2817         * Caches the current cursor position in IE.
2818         * @method _beforeFrameDeactivate
2819         * @private
2820         */
2821         _beforeFrameDeactivate: function() {
2822             var inst = this.getInstance(),
2823                 sel = inst.config.doc.selection.createRange();
2824             
2825             if ((!sel.compareEndPoints('StartToEnd', sel))) {
2826                 sel.pasteHTML('<var id="yui-ie-cursor">');
2827             }
2828         },
2829         /**
2830         * Moves the cached selection bookmark back so IE can place the cursor in the right place.
2831         * @method _onFrameActivate
2832         * @private
2833         */
2834         _onFrameActivate: function() {
2835             var inst = this.getInstance(),
2836                 sel = new inst.Selection(),
2837                 range = sel.createRange(),
2838                 cur = inst.all('#yui-ie-cursor');
2839
2840             if (cur.size()) {
2841                 cur.each(function(n) {
2842                     n.set('id', '');
2843                     range.moveToElementText(n._node);
2844                     range.move('character', -1);
2845                     range.move('character', 1);
2846                     range.select();
2847                     range.text = '';
2848                     n.remove();
2849                 });
2850             }
2851         },
2852         /**
2853         * Fires nodeChange event
2854         * @method _onFrameMouseUp
2855         * @private
2856         */
2857         _onFrameMouseUp: function(e) {
2858             this.fire('nodeChange', { changedNode: e.frameTarget, changedType: 'mouseup', changedEvent: e.frameEvent  });
2859         },
2860         /**
2861         * Fires nodeChange event
2862         * @method _onFrameMouseDown
2863         * @private
2864         */
2865         _onFrameMouseDown: function(e) {
2866             this.fire('nodeChange', { changedNode: e.frameTarget, changedType: 'mousedown', changedEvent: e.frameEvent  });
2867         },
2868         /**
2869         * Caches a copy of the selection for key events. Only creating the selection on keydown
2870         * @property _currentSelection
2871         * @private
2872         */
2873         _currentSelection: null,
2874         /**
2875         * Holds the timer for selection clearing
2876         * @property _currentSelectionTimer
2877         * @private
2878         */
2879         _currentSelectionTimer: null,
2880         /**
2881         * Flag to determine if we can clear the selection or not.
2882         * @property _currentSelectionClear
2883         * @private
2884         */
2885         _currentSelectionClear: null,
2886         /**
2887         * Fires nodeChange event
2888         * @method _onFrameKeyDown
2889         * @private
2890         */
2891         _onFrameKeyDown: function(e) {
2892             var inst, sel;
2893             if (!this._currentSelection) {
2894                 if (this._currentSelectionTimer) {
2895                     this._currentSelectionTimer.cancel();
2896                 }
2897                 this._currentSelectionTimer = Y.later(850, this, function() {
2898                     this._currentSelectionClear = true;
2899                 });
2900                 
2901                 inst = this.frame.getInstance();
2902                 sel = new inst.Selection(e);
2903
2904                 this._currentSelection = sel;
2905             } else {
2906                 sel = this._currentSelection;
2907             }
2908
2909             inst = this.frame.getInstance();
2910             sel = new inst.Selection();
2911
2912             this._currentSelection = sel;
2913             
2914             if (sel && sel.anchorNode) {
2915                 this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: 'keydown', changedEvent: e.frameEvent });
2916                 if (EditorBase.NC_KEYS[e.keyCode]) {
2917                     this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: EditorBase.NC_KEYS[e.keyCode], changedEvent: e.frameEvent });
2918                     this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: EditorBase.NC_KEYS[e.keyCode] + '-down', changedEvent: e.frameEvent });
2919                 }
2920             }
2921         },
2922         /**
2923         * Fires nodeChange event
2924         * @method _onFrameKeyPress
2925         * @private
2926         */
2927         _onFrameKeyPress: function(e) {
2928             var sel = this._currentSelection;
2929
2930             if (sel && sel.anchorNode) {
2931                 this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: 'keypress', changedEvent: e.frameEvent });
2932                 if (EditorBase.NC_KEYS[e.keyCode]) {
2933                     this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: EditorBase.NC_KEYS[e.keyCode] + '-press', changedEvent: e.frameEvent });
2934                 }
2935             }
2936         },
2937         /**
2938         * Fires nodeChange event for keyup on specific keys
2939         * @method _onFrameKeyUp
2940         * @private
2941         */
2942         _onFrameKeyUp: function(e) {
2943             var sel = this._currentSelection;
2944
2945             if (sel && sel.anchorNode) {
2946                 this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: 'keyup', selection: sel, changedEvent: e.frameEvent  });
2947                 if (EditorBase.NC_KEYS[e.keyCode]) {
2948                     this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: EditorBase.NC_KEYS[e.keyCode] + '-up', selection: sel, changedEvent: e.frameEvent  });
2949                 }
2950             }
2951             if (this._currentSelectionClear) {
2952                 this._currentSelectionClear = this._currentSelection = null;
2953             }
2954         },
2955         /**
2956         * Pass through to the frame.execCommand method
2957         * @method execCommand
2958         * @param {String} cmd The command to pass: inserthtml, insertimage, bold
2959         * @param {String} val The optional value of the command: Helvetica
2960         * @return {Node/NodeList} The Node or Nodelist affected by the command. Only returns on override commands, not browser defined commands.
2961         */
2962         execCommand: function(cmd, val) {
2963             var ret = this.frame.execCommand(cmd, val),
2964                 inst = this.frame.getInstance(),
2965                 sel = new inst.Selection(), cmds = {},
2966                 e = { changedNode: sel.anchorNode, changedType: 'execcommand', nodes: ret };
2967
2968             switch (cmd) {
2969                 case 'forecolor':
2970                     e.fontColor = val;
2971                     break;
2972                 case 'backcolor':
2973                     e.backgroundColor = val;
2974                     break;
2975                 case 'fontsize':
2976                     e.fontSize = val;
2977                     break;
2978                 case 'fontname':
2979                     e.fontFamily = val;
2980                     break;
2981             }
2982
2983             cmds[cmd] = 1;
2984             e.commands = cmds;
2985
2986             this.fire('nodeChange', e);
2987
2988             return ret;
2989         },
2990         /**
2991         * Get the YUI instance of the frame
2992         * @method getInstance
2993         * @return {YUI} The YUI instance bound to the frame.
2994         */
2995         getInstance: function() {
2996             return this.frame.getInstance();
2997         },
2998         /**
2999         * Renders the Y.Frame to the passed node.
3000         * @method render
3001         * @param {Selector/HTMLElement/Node} node The node to append the Editor to
3002         * @return {EditorBase}
3003         * @chainable
3004         */
3005         render: function(node) {
3006             this.frame.set('content', this.get('content'));
3007             this.frame.render(node);
3008             return this;
3009         },
3010         /**
3011         * Focus the contentWindow of the iframe
3012         * @method focus
3013         * @param {Function} fn Callback function to execute after focus happens
3014         * @return {EditorBase}
3015         * @chainable
3016         */
3017         focus: function(fn) {
3018             this.frame.focus(fn);
3019             return this;
3020         },
3021         /**
3022         * Handles the showing of the Editor instance. Currently only handles the iframe
3023         * @method show
3024         * @return {EditorBase}
3025         * @chainable
3026         */
3027         show: function() {
3028             this.frame.show();
3029             return this;
3030         },
3031         /**
3032         * Handles the hiding of the Editor instance. Currently only handles the iframe
3033         * @method hide
3034         * @return {EditorBase}
3035         * @chainable
3036         */
3037         hide: function() {
3038             this.frame.hide();
3039             return this;
3040         },
3041         /**
3042         * (Un)Filters the content of the Editor, cleaning YUI related code. //TODO better filtering
3043         * @method getContent
3044         * @return {String} The filtered content of the Editor
3045         */
3046         getContent: function() {
3047             var html = '', inst = this.getInstance();
3048             if (inst && inst.Selection) {
3049                 html = inst.Selection.unfilter();
3050             }
3051             //Removing the _yuid from the objects in IE
3052             html = html.replace(/ _yuid="([^>]*)"/g, '');
3053             return html;
3054         }
3055     }, {
3056         /**
3057         * @static
3058         * @method NORMALIZE_FONTSIZE
3059         * @description Pulls the fontSize from a node, then checks for string values (x-large, x-small)
3060         * and converts them to pixel sizes. If the parsed size is different from the original, it calls
3061         * node.setStyle to update the node with a pixel size for normalization.
3062         */
3063         NORMALIZE_FONTSIZE: function(n) {
3064             var size = n.getStyle('fontSize'), oSize = size;
3065             
3066             switch (size) {
3067                 case '-webkit-xxx-large':
3068                     size = '48px';
3069                     break;
3070                 case 'xx-large':
3071                     size = '32px';
3072                     break;
3073                 case 'x-large':
3074                     size = '24px';
3075                     break;
3076                 case 'large':
3077                     size = '18px';
3078                     break;
3079                 case 'medium':
3080                     size = '16px';
3081                     break;
3082                 case 'small':
3083                     size = '13px';
3084                     break;
3085                 case 'x-small':
3086                     size = '10px';
3087                     break;
3088             }
3089             if (oSize !== size) {
3090                 n.setStyle('fontSize', size);
3091             }
3092             return size;
3093         },
3094         /**
3095         * @static
3096         * @property TABKEY
3097         * @description The HTML markup to use for the tabkey
3098         */
3099         TABKEY: '<span class="tab">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>',
3100         /**
3101         * @static
3102         * @method FILTER_RGB
3103         * @param String css The CSS string containing rgb(#,#,#);
3104         * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
3105         * @return String
3106         */
3107         FILTER_RGB: function(css) {
3108             if (css.toLowerCase().indexOf('rgb') != -1) {
3109                 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
3110                 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
3111             
3112                 if (rgb.length == 5) {
3113                     var r = parseInt(rgb[1], 10).toString(16);
3114                     var g = parseInt(rgb[2], 10).toString(16);
3115                     var b = parseInt(rgb[3], 10).toString(16);
3116
3117                     r = r.length == 1 ? '0' + r : r;
3118                     g = g.length == 1 ? '0' + g : g;
3119                     b = b.length == 1 ? '0' + b : b;
3120
3121                     css = "#" + r + g + b;
3122                 }
3123             }
3124             return css;
3125         },        
3126         /**
3127         * @static
3128         * @property TAG2CMD
3129         * @description A hash table of tags to their execcomand's
3130         */
3131         TAG2CMD: {
3132             'b': 'bold',
3133             'strong': 'bold',
3134             'i': 'italic',
3135             'em': 'italic',
3136             'u': 'underline',
3137             'sup': 'superscript',
3138             'sub': 'subscript',
3139             'img': 'insertimage',
3140             'a' : 'createlink',
3141             'ul' : 'insertunorderedlist',
3142             'ol' : 'insertorderedlist'
3143         },
3144         /**
3145         * Hash table of keys to fire a nodeChange event for.
3146         * @static
3147         * @property NC_KEYS
3148         * @type Object
3149         */
3150         NC_KEYS: {
3151             8: 'backspace',
3152             9: 'tab',
3153             13: 'enter',
3154             32: 'space',
3155             33: 'pageup',
3156             34: 'pagedown',
3157             35: 'end',
3158             36: 'home',
3159             37: 'left',
3160             38: 'up',
3161             39: 'right',
3162             40: 'down',
3163             46: 'delete'
3164         },
3165         /**
3166         * The default modules to use inside the Frame
3167         * @static
3168         * @property USE
3169         * @type Array
3170         */
3171         USE: ['substitute', 'node', 'selector-css3', 'selection', 'stylesheet'],
3172         /**
3173         * The Class Name: editorBase
3174         * @static
3175         * @property NAME
3176         */
3177         NAME: 'editorBase',
3178         /**
3179         * Editor Strings
3180         * @static
3181         * @property STRINGS
3182         */
3183         STRINGS: {
3184             /**
3185             * Title of frame document: Rich Text Editor
3186             * @static
3187             * @property STRINGS.title
3188             */
3189             title: 'Rich Text Editor'
3190         },
3191         ATTRS: {
3192             /**
3193             * The content to load into the Editor Frame
3194             * @attribute content
3195             */
3196             content: {
3197                 value: '<br class="yui-cursor">',
3198                 setter: function(str) {
3199                     if (str.substr(0, 1) === "\n") {
3200                         str = str.substr(1);
3201                     }
3202                     if (str === '') {
3203                         str = '<br class="yui-cursor">';
3204                     }
3205                     if (str === ' ') {
3206                         if (Y.UA.gecko) {
3207                             str = '<br class="yui-cursor">';
3208                         }
3209                     }
3210                     return this.frame.set('content', str);
3211                 },
3212                 getter: function() {
3213                     return this.frame.get('content');
3214                 }
3215             },
3216             /**
3217             * The value of the dir attribute on the HTML element of the frame. Default: ltr
3218             * @attribute dir
3219             */
3220             dir: {
3221                 writeOnce: true,
3222                 value: 'ltr'
3223             },
3224             /**
3225             * @attribute linkedcss
3226             * @description An array of url's to external linked style sheets
3227             * @type String
3228             */            
3229             linkedcss: {
3230                 value: '',
3231                 setter: function(css) {
3232                     if (this.frame) {
3233                         this.frame.set('linkedcss', css);
3234                     }
3235                     return css;
3236                 }
3237             },
3238             /**
3239             * @attribute extracss
3240             * @description A string of CSS to add to the Head of the Editor
3241             * @type String
3242             */            
3243             extracss: {
3244                 value: false,
3245                 setter: function(css) {
3246                     if (this.frame) {
3247                         this.frame.set('extracss', css);
3248                     }
3249                     return css;
3250                 }
3251             },
3252             /**
3253             * @attribute defaultblock
3254             * @description The default tag to use for block level items, defaults to: p
3255             * @type String
3256             */            
3257             defaultblock: {
3258                 value: 'p'
3259             }
3260         }
3261     });
3262
3263     Y.EditorBase = EditorBase;
3264
3265     /**
3266     * @event nodeChange
3267     * @description Fired from mouseup & keyup.
3268     * @param {Event.Facade} event An Event Facade object with the following specific properties added:
3269     * <dl>
3270     *   <dt>changedEvent</dt><dd>The event that caused the nodeChange</dd>
3271     *   <dt>changedNode</dt><dd>The node that was interacted with</dd>
3272     *   <dt>changedType</dt><dd>The type of change: mousedown, mouseup, right, left, backspace, tab, enter, etc..</dd>
3273     *   <dt>commands</dt><dd>The list of execCommands that belong to this change and the dompath that's associated with the changedNode</dd>
3274     *   <dt>classNames</dt><dd>An array of classNames that are applied to the changedNode and all of it's parents</dd>
3275     *   <dt>dompath</dt><dd>A sorted array of node instances that make up the DOM path from the changedNode to body.</dd>
3276     *   <dt>backgroundColor</dt><dd>The cascaded backgroundColor of the changedNode</dd>
3277     *   <dt>fontColor</dt><dd>The cascaded fontColor of the changedNode</dd>
3278     *   <dt>fontFamily</dt><dd>The cascaded fontFamily of the changedNode</dd>
3279     *   <dt>fontSize</dt><dd>The cascaded fontSize of the changedNode</dd>
3280     * </dl>
3281     * @type {Event.Custom}
3282     */
3283
3284     /**
3285     * @event ready
3286     * @description Fired after the frame is ready.
3287     * @param {Event.Facade} event An Event Facade object.
3288     * @type {Event.Custom}
3289     */
3290
3291
3292
3293
3294
3295 }, '3.3.0' ,{requires:['base', 'frame', 'node', 'exec-command', 'selection', 'editor-para'], skinnable:false});
3296 YUI.add('editor-lists', function(Y) {
3297
3298     /**
3299      * Handles list manipulation inside the Editor. Adds keyboard manipulation and execCommand support. Adds overrides for the <a href="Plugin.ExecCommand.html#method_COMMANDS.insertorderedlist">insertorderedlist</a> and <a href="Plugin.ExecCommand.html#method_COMMANDS.insertunorderedlist">insertunorderedlist</a> execCommands.
3300      * @module editor
3301      * @submodule editor-lists
3302      */     
3303     /**
3304      * Handles list manipulation inside the Editor. Adds keyboard manipulation and execCommand support. Adds overrides for the <a href="Plugin.ExecCommand.html#method_COMMANDS.insertorderedlist">insertorderedlist</a> and <a href="Plugin.ExecCommand.html#method_COMMANDS.insertunorderedlist">insertunorderedlist</a> execCommands.
3305      * @class Plugin.EditorLists
3306      * @constructor
3307      * @extends Base
3308      */
3309     
3310     var EditorLists = function() {
3311         EditorLists.superclass.constructor.apply(this, arguments);
3312     }, LI = 'li', OL = 'ol', UL = 'ul', HOST = 'host';
3313
3314     Y.extend(EditorLists, Y.Base, {
3315         /**
3316         * Listener for host's nodeChange event and captures the tabkey interaction only when inside a list node.
3317         * @private
3318         * @method _onNodeChange
3319         * @param {Event} e The Event facade passed from the host.
3320         */
3321         _onNodeChange: function(e) {
3322             var inst = this.get(HOST).getInstance(), sel, li, 
3323             newLi, newList, sTab, par, moved = false, tag, focusEnd = false;
3324
3325             if (Y.UA.ie && e.changedType === 'enter') {
3326                 if (e.changedNode.test(LI + ', ' + LI + ' *')) {
3327                     e.changedEvent.halt();
3328                     e.preventDefault();
3329                     li = e.changedNode;
3330                     newLi = inst.Node.create('<' + LI + '>' + EditorLists.NON + '</' + LI + '>');
3331                         
3332                     if (!li.test(LI)) {
3333                         li = li.ancestor(LI);
3334                     }
3335                     li.insert(newLi, 'after');
3336                     
3337                     sel = new inst.Selection();
3338                     sel.selectNode(newLi.get('firstChild'), true, false);
3339                 }
3340             }
3341             if (e.changedType === 'tab') {
3342                 if (e.changedNode.test(LI + ', ' + LI + ' *')) {
3343                     e.changedEvent.halt();
3344                     e.preventDefault();
3345                     li = e.changedNode;
3346                     sTab = e.changedEvent.shiftKey;
3347                     par = li.ancestor(OL + ',' + UL);
3348                     tag = UL;
3349
3350
3351                     if (par.get('tagName').toLowerCase() === OL) {
3352                         tag = OL;
3353                     }
3354                     
3355                     if (!li.test(LI)) {
3356                         li = li.ancestor(LI);
3357                     }
3358                     if (sTab) {
3359                         if (li.ancestor(LI)) {
3360                             li.ancestor(LI).insert(li, 'after');
3361                             moved = true;
3362                             focusEnd = true;
3363                         }
3364                     } else {
3365                         //li.setStyle('border', '1px solid red');
3366                         if (li.previous(LI)) {
3367                             newList = inst.Node.create('<' + tag + '></' + tag + '>');
3368                             li.previous(LI).append(newList);
3369                             newList.append(li);
3370                             moved = true;
3371                         }
3372                     }
3373                 }
3374                 if (moved) {
3375                     if (!li.test(LI)) {
3376                         li = li.ancestor(LI);
3377                     }
3378                     li.all(EditorLists.REMOVE).remove();
3379                     if (Y.UA.ie) {
3380                         li = li.append(EditorLists.NON).one(EditorLists.NON_SEL);
3381                     }
3382                     //Selection here..
3383                     (new inst.Selection()).selectNode(li, true, focusEnd);
3384                 }
3385             }
3386         },
3387         initializer: function() {
3388             this.get(HOST).on('nodeChange', Y.bind(this._onNodeChange, this));
3389         }
3390     }, {
3391         /**
3392         * The non element placeholder, used for positioning the cursor and filling empty items
3393         * @property REMOVE
3394         * @static
3395         */
3396         NON: '<span class="yui-non">&nbsp;</span>',
3397         /**
3398         * The selector query to get all non elements
3399         * @property NONSEL
3400         * @static
3401         */
3402         NON_SEL: 'span.yui-non',
3403         /**
3404         * The items to removed from a list when a list item is moved, currently removes BR nodes
3405         * @property REMOVE
3406         * @static
3407         */
3408         REMOVE: 'br',
3409         /**
3410         * editorLists
3411         * @property NAME
3412         * @static
3413         */
3414         NAME: 'editorLists',
3415         /**
3416         * lists
3417         * @property NS
3418         * @static
3419         */
3420         NS: 'lists',
3421         ATTRS: {
3422             host: {
3423                 value: false
3424             }
3425         }
3426     });
3427
3428
3429     Y.namespace('Plugin');
3430
3431     Y.Plugin.EditorLists = EditorLists;
3432
3433     Y.mix(Y.Plugin.ExecCommand.COMMANDS, {
3434         /**
3435         * Override for the insertunorderedlist method from the <a href="Plugin.EditorLists.html">EditorLists</a> plugin.
3436         * @for ExecCommand
3437         * @method COMMANDS.insertunorderedlist
3438         * @static
3439         * @param {String} cmd The command executed: insertunorderedlist
3440         * @return {Node} Node instance of the item touched by this command.
3441         */
3442         insertunorderedlist: function(cmd) {
3443             var inst = this.get('host').getInstance(), out;
3444             this.get('host')._execCommand(cmd, '');
3445         },
3446         /**
3447         * Override for the insertorderedlist method from the <a href="Plugin.EditorLists.html">EditorLists</a> plugin.
3448         * @for ExecCommand
3449         * @method COMMANDS.insertorderedlist
3450         * @static
3451         * @param {String} cmd The command executed: insertorderedlist
3452         * @return {Node} Node instance of the item touched by this command.
3453         */
3454         insertorderedlist: function(cmd) {
3455             var inst = this.get('host').getInstance(), out;
3456             this.get('host')._execCommand(cmd, '');
3457         }
3458     });
3459
3460
3461
3462
3463 }, '3.3.0' ,{requires:['editor-base'], skinnable:false});
3464 YUI.add('editor-bidi', function(Y) {
3465
3466
3467
3468     /**
3469      * Plugin for Editor to support BiDirectional (bidi) text operations.
3470      * @module editor
3471      * @submodule editor-bidi
3472      */     
3473     /**
3474      * Plugin for Editor to support BiDirectional (bidi) text operations.
3475      * @class Plugin.EditorBidi
3476      * @extends Base
3477      * @constructor
3478      */
3479
3480
3481     var EditorBidi = function() {
3482         EditorBidi.superclass.constructor.apply(this, arguments);
3483     }, HOST = 'host', DIR = 'dir', BODY = 'BODY', NODE_CHANGE = 'nodeChange',
3484     B_C_CHANGE = 'bidiContextChange', FIRST_P = BODY + ' > p';
3485
3486     Y.extend(EditorBidi, Y.Base, {
3487         /**
3488         * Place holder for the last direction when checking for a switch
3489         * @private
3490         * @property lastDirection
3491         */
3492         lastDirection: null,
3493         /**
3494         * Tells us that an initial bidi check has already been performed
3495         * @private
3496         * @property firstEvent
3497         */
3498         firstEvent: null,
3499
3500         /**
3501         * Method checks to see if the direction of the text has changed based on a nodeChange event.
3502         * @private
3503         * @method _checkForChange
3504         */
3505         _checkForChange: function() {
3506             var host = this.get(HOST),
3507                 inst = host.getInstance(),
3508                 sel = new inst.Selection(),
3509                 node, direction;
3510             
3511             if (sel.isCollapsed) {
3512                 node = EditorBidi.blockParent(sel.focusNode);
3513                 direction = node.getStyle('direction');
3514                 if (direction !== this.lastDirection) {
3515                     host.fire(B_C_CHANGE, { changedTo: direction });
3516                     this.lastDirection = direction;
3517                 }
3518             } else {
3519                 host.fire(B_C_CHANGE, { changedTo: 'select' });
3520                 this.lastDirection = null;
3521             }
3522         },
3523
3524         /**
3525         * Checked for a change after a specific nodeChange event has been fired.
3526         * @private
3527         * @method _afterNodeChange
3528         */
3529         _afterNodeChange: function(e) { 
3530             // If this is the first event ever, or an event that can result in a context change
3531             if (this.firstEvent || EditorBidi.EVENTS[e.changedType]) {
3532                 this._checkForChange();
3533                 this.firstEvent = false;
3534             }
3535         },
3536
3537         /**
3538         * Checks for a direction change after a mouseup occurs.
3539         * @private
3540         * @method _afterMouseUp
3541         */
3542         _afterMouseUp: function(e) {
3543             this._checkForChange();
3544             this.firstEvent = false;
3545         },
3546         initializer: function() {
3547             var host = this.get(HOST);
3548
3549             this.firstEvent = true;
3550             
3551             host.after(NODE_CHANGE, Y.bind(this._afterNodeChange, this));
3552             host.after('dom:mouseup', Y.bind(this._afterMouseUp, this));
3553         }
3554     }, {
3555         /**
3556         * The events to check for a direction change on
3557         * @property EVENTS
3558         * @static
3559         */
3560         EVENTS: {
3561             'backspace-up': true,
3562             'pageup-up': true,
3563             'pagedown-down': true,
3564             'end-up': true,
3565             'home-up': true,
3566             'left-up': true,
3567             'up-up': true,
3568             'right-up': true,
3569             'down-up': true,
3570             'delete-up': true
3571         },
3572
3573         /**
3574         * More elements may be needed. BODY *must* be in the list to take care of the special case.
3575         * 
3576         * blockParent could be changed to use inst.Selection.BLOCKS
3577         * instead, but that would make Y.Plugin.EditorBidi.blockParent
3578         * unusable in non-RTE contexts (it being usable is a nice
3579         * side-effect).
3580         * @property BLOCKS
3581         * @static
3582         */
3583         BLOCKS: Y.Selection.BLOCKS+',LI,HR,' + BODY,
3584         /**
3585         * Template for creating a block element
3586         * @static
3587         * @property DIV_WRAPPER
3588         */
3589         DIV_WRAPPER: '<DIV></DIV>',
3590         /**
3591         * Returns a block parent for a given element
3592         * @static
3593         * @method blockParent
3594         */
3595         blockParent: function(node, wrap) {
3596             var parent = node, divNode, firstChild;
3597             
3598             if (!parent) {
3599                 parent = Y.one(BODY);
3600             }
3601             
3602             if (!parent.test(EditorBidi.BLOCKS)) {
3603                 parent = parent.ancestor(EditorBidi.BLOCKS);
3604             }
3605             if (wrap && parent.test(BODY)) {
3606                 // This shouldn't happen if the RTE handles everything
3607                 // according to spec: we should get to a P before BODY. But
3608                 // we don't want to set the direction of BODY even if that
3609                 // happens, so we wrap everything in a DIV.
3610                 
3611                 // The code is based on YUI3's Y.Selection._wrapBlock function.
3612                 divNode = Y.Node.create(EditorBidi.DIV_WRAPPER);
3613                 parent.get('children').each(function(node, index) {
3614                     if (index === 0) {
3615                         firstChild = node;
3616                     } else {
3617                         divNode.append(node);
3618                     }
3619                 });
3620                 firstChild.replace(divNode);
3621                 divNode.prepend(firstChild);
3622                 parent = divNode;
3623             }
3624             return parent;
3625         },
3626         /**
3627         * The data key to store on the node.
3628         * @static
3629         * @property _NODE_SELECTED
3630         */
3631         _NODE_SELECTED: 'bidiSelected',
3632         /**
3633         * Generates a list of all the block parents of the current NodeList
3634         * @static
3635         * @method addParents
3636         */
3637         addParents: function(nodeArray) {
3638             var i, parent, addParent;
3639
3640             for (i = 0; i < nodeArray.length; i += 1) {
3641                 nodeArray[i].setData(EditorBidi._NODE_SELECTED, true);
3642             }
3643
3644             // This works automagically, since new parents added get processed
3645             // later themselves. So if there's a node early in the process that
3646             // we haven't discovered some of its siblings yet, thus resulting in
3647             // its parent not added, the parent will be added later, since those
3648             // siblings will be added to the array and then get processed.
3649             for (i = 0; i < nodeArray.length; i += 1) {
3650                 parent = nodeArray[i].get('parentNode');
3651
3652                 // Don't add the parent if the parent is the BODY element.
3653                 // We don't want to change the direction of BODY. Also don't
3654                 // do it if the parent is already in the list.
3655                 if (!parent.test(BODY) && !parent.getData(EditorBidi._NODE_SELECTED)) {
3656                     addParent = true;
3657                     parent.get('children').some(function(sibling) {
3658                         if (!sibling.getData(EditorBidi._NODE_SELECTED)) {
3659                             addParent = false;
3660                             return true; // stop more processing
3661                         }
3662                     });
3663                     if (addParent) {
3664                         nodeArray.push(parent);
3665                         parent.setData(EditorBidi._NODE_SELECTED, true);
3666                     }
3667                 }
3668             }   
3669
3670             for (i = 0; i < nodeArray.length; i += 1) {
3671                 nodeArray[i].clearData(EditorBidi._NODE_SELECTED);
3672             }
3673
3674             return nodeArray;
3675         },
3676
3677
3678         /**
3679         * editorBidi
3680         * @static
3681         * @property NAME
3682         */
3683         NAME: 'editorBidi',
3684         /**
3685         * editorBidi
3686         * @static
3687         * @property NS
3688         */
3689         NS: 'editorBidi',
3690         ATTRS: {
3691             host: {
3692                 value: false
3693             }
3694         }
3695     });
3696     
3697     Y.namespace('Plugin');
3698     
3699     Y.Plugin.EditorBidi = EditorBidi;
3700
3701     /**
3702      * bidi execCommand override for setting the text direction of a node.
3703      * @for Plugin.ExecCommand
3704      * @property COMMANDS.bidi
3705      */
3706
3707     Y.Plugin.ExecCommand.COMMANDS.bidi = function(cmd, direction) {
3708         var inst = this.getInstance(),
3709             sel = new inst.Selection(),
3710             returnValue, block,
3711             selected, selectedBlocks, dir;
3712
3713         inst.Selection.filterBlocks();
3714         if (sel.isCollapsed) { // No selection
3715             block = EditorBidi.blockParent(sel.anchorNode);
3716             if (!direction) {
3717                 //If no direction is set, auto-detect the proper setting to make it "toggle"
3718                 dir = block.getAttribute(DIR);
3719                 if (!dir || dir == 'ltr') {
3720                     direction = 'rtl';
3721                 } else {
3722                     direction = 'ltr';
3723                 }
3724             }
3725             block.setAttribute(DIR, direction);
3726             returnValue = block;
3727         } else { // some text is selected
3728             selected = sel.getSelected();
3729             selectedBlocks = [];
3730             selected.each(function(node) {
3731                 /*
3732                 * Temporarily removed this check, should already be fixed
3733                 * in Y.Selection.getSelected()
3734                 */
3735                 //if (!node.test(BODY)) { // workaround for a YUI bug
3736                    selectedBlocks.push(EditorBidi.blockParent(node));
3737                 //}
3738             });
3739             selectedBlocks = inst.all(EditorBidi.addParents(selectedBlocks));
3740             selectedBlocks.setAttribute(DIR, direction);
3741             returnValue = selectedBlocks;
3742         }
3743
3744         this.get(HOST).get(HOST).editorBidi._checkForChange();
3745         return returnValue;
3746     };
3747
3748
3749
3750
3751 }, '3.3.0' ,{requires:['editor-base'], skinnable:false});
3752 YUI.add('editor-para', function(Y) {
3753
3754
3755
3756     /**
3757      * Plugin for Editor to paragraph auto wrapping and correction.
3758      * @module editor
3759      * @submodule editor-para
3760      */     
3761     /**
3762      * Plugin for Editor to paragraph auto wrapping and correction.
3763      * @class Plugin.EditorPara
3764      * @extends Base
3765      * @constructor
3766      */
3767
3768
3769     var EditorPara = function() {
3770         EditorPara.superclass.constructor.apply(this, arguments);
3771     }, HOST = 'host', BODY = 'body', NODE_CHANGE = 'nodeChange', PARENT_NODE = 'parentNode',
3772     FIRST_P = BODY + ' > p', P = 'p', BR = '<br>', FC = 'firstChild', LI = 'li';
3773
3774
3775     Y.extend(EditorPara, Y.Base, {
3776         /**
3777         * Utility method to create an empty paragraph when the document is empty.
3778         * @private
3779         * @method _fixFirstPara
3780         */
3781         _fixFirstPara: function() {
3782             var host = this.get(HOST), inst = host.getInstance(), sel;
3783             inst.one('body').set('innerHTML', '<' + P + '>' + inst.Selection.CURSOR + '</' + P + '>');
3784             var n = inst.one(FIRST_P);
3785             sel = new inst.Selection();
3786             sel.selectNode(n, true, false);
3787             
3788         },
3789         /**
3790         * nodeChange handler to handle fixing an empty document.
3791         * @private
3792         * @method _onNodeChange
3793         */
3794         _onNodeChange: function(e) {
3795             var host = this.get(HOST), inst = host.getInstance(),
3796                 html, txt, par , d, sel, btag = inst.Selection.DEFAULT_BLOCK_TAG,
3797                 inHTML, txt2, childs, aNode, index, node2, top, n, sib,
3798                 ps, br, item, p, imgs, t, LAST_CHILD = ':last-child';
3799
3800             switch (e.changedType) {
3801                 case 'enter-up':
3802                     var para = ((this._lastPara) ? this._lastPara : e.changedNode),
3803                         b = para.one('br.yui-cursor');
3804
3805                     if (this._lastPara) {
3806                         delete this._lastPara;
3807                     }
3808
3809                     if (b) {
3810                         if (b.previous() || b.next()) {
3811                             b.remove();
3812                         }
3813                     }
3814                     if (!para.test(btag)) {
3815                         var para2 = para.ancestor(btag);
3816                         if (para2) {
3817                             para = para2;
3818                             para2 = null;
3819                         }
3820                     }
3821                     if (para.test(btag)) {
3822                         var prev = para.previous(), lc, lc2, found = false;
3823                         if (prev) {
3824                             lc = prev.one(LAST_CHILD);
3825                             while (!found) {
3826                                 if (lc) {
3827                                     lc2 = lc.one(LAST_CHILD);
3828                                     if (lc2) {
3829                                         lc = lc2;
3830                                     } else {
3831                                         found = true;
3832                                     }
3833                                 } else {
3834                                     found = true;
3835                                 }
3836                             }
3837                             if (lc) {
3838                                 host.copyStyles(lc, para);
3839                             }
3840                         }
3841                     }
3842                     break;
3843                 case 'enter':
3844                     if (Y.UA.webkit) {
3845                         //Webkit doesn't support shift+enter as a BR, this fixes that.
3846                         if (e.changedEvent.shiftKey) {
3847                             host.execCommand('insertbr');
3848                             e.changedEvent.preventDefault();
3849                         }
3850                     }
3851                     //TODO Move this to a GECKO MODULE - Can't for the moment, requires no change to metadata (YMAIL)
3852                     if (Y.UA.gecko && host.get('defaultblock') !== 'p') {
3853                         par = e.changedNode;
3854
3855                         if (!par.test(LI) && !par.ancestor(LI)) {
3856                             if (!par.test(btag)) {
3857                                 par = par.ancestor(btag);
3858                             }
3859                             d = inst.Node.create('<' + btag + '></' + btag + '>');
3860                             par.insert(d, 'after');
3861                             sel = new inst.Selection();
3862                             if (sel.anchorOffset) {
3863                                 inHTML = sel.anchorNode.get('textContent');
3864
3865                                 txt = inst.one(inst.config.doc.createTextNode(inHTML.substr(0, sel.anchorOffset)));
3866                                 txt2 = inst.one(inst.config.doc.createTextNode(inHTML.substr(sel.anchorOffset)));
3867
3868                                 aNode = sel.anchorNode;
3869                                 aNode.setContent(''); //I
3870                                 node2 = aNode.cloneNode(); //I
3871                                 node2.append(txt2); //text
3872                                 top = false;
3873                                 sib = aNode; //I
3874                                 while (!top) {
3875                                     sib = sib.get(PARENT_NODE); //B
3876                                     if (sib && !sib.test(btag)) {
3877                                         n = sib.cloneNode();
3878                                         n.set('innerHTML', '');
3879                                         n.append(node2);
3880                                         
3881                                         //Get children..
3882                                         childs = sib.get('childNodes');
3883                                         var start = false;
3884                                         childs.each(function(c) {
3885                                             if (start) {
3886                                                 n.append(c);
3887                                             }
3888                                             if (c === aNode) {
3889                                                 start = true;
3890                                             }
3891                                         });
3892
3893                                         aNode = sib; //Top sibling
3894                                         node2 = n;
3895                                     } else {
3896                                         top = true;
3897                                     }
3898                                 }
3899                                 txt2 = node2;
3900                                 sel.anchorNode.append(txt);
3901
3902                                 if (txt2) {
3903                                     d.append(txt2);
3904                                 }
3905                             }
3906                             if (d.get(FC)) {
3907                                 d = d.get(FC);
3908                             }
3909                             d.prepend(inst.Selection.CURSOR);
3910                             sel.focusCursor(true, true);
3911                             html = inst.Selection.getText(d);
3912                             if (html !== '') {
3913                                 inst.Selection.cleanCursor();
3914                             }
3915                             e.changedEvent.preventDefault();
3916                         }
3917                     }
3918                     break;
3919                 case 'keydown':
3920                     if (inst.config.doc.childNodes.length < 2) {
3921                         var cont = inst.config.doc.body.innerHTML;
3922                         if (cont && cont.length < 5 && cont.toLowerCase() == BR) {
3923                             this._fixFirstPara();
3924                         }
3925                     }
3926                     break;
3927                 case 'backspace-up':
3928                 case 'backspace-down':
3929                 case 'delete-up':
3930                     if (!Y.UA.ie) {
3931                         ps = inst.all(FIRST_P);
3932                         item = inst.one(BODY);
3933                         if (ps.item(0)) {
3934                             item = ps.item(0);
3935                         }
3936                         br = item.one('br');
3937                         if (br) {
3938                             br.removeAttribute('id');
3939                             br.removeAttribute('class');
3940                         }
3941
3942                         txt = inst.Selection.getText(item);
3943                         txt = txt.replace(/ /g, '').replace(/\n/g, '');
3944                         imgs = item.all('img');
3945                         
3946                         if (txt.length === 0 && !imgs.size()) {
3947                             //God this is horrible..
3948                             if (!item.test(P)) {
3949                                 this._fixFirstPara();
3950                             }
3951                             p = null;
3952                             if (e.changedNode && e.changedNode.test(P)) {
3953                                 p = e.changedNode;
3954                             }
3955                             if (!p && host._lastPara && host._lastPara.inDoc()) {
3956                                 p = host._lastPara;
3957                             }
3958                             if (p && !p.test(P)) {
3959                                 p = p.ancestor(P);
3960                             }
3961                             if (p) {
3962                                 if (!p.previous() && p.get(PARENT_NODE) && p.get(PARENT_NODE).test(BODY)) {
3963                                     e.changedEvent.frameEvent.halt();
3964                                 }
3965                             }
3966                         }
3967                         if (Y.UA.webkit) {
3968                             if (e.changedNode) {
3969                                 item = e.changedNode;
3970                                 if (item.test('li') && (!item.previous() && !item.next())) {
3971                                     html = item.get('innerHTML').replace(BR, '');
3972                                     if (html === '') {
3973                                         if (item.get(PARENT_NODE)) {
3974                                             item.get(PARENT_NODE).replace(inst.Node.create(BR));
3975                                             e.changedEvent.frameEvent.halt();
3976                                             e.preventDefault();
3977                                             inst.Selection.filterBlocks();
3978                                         }
3979                                     }
3980                                 }
3981                             }
3982                         }
3983                     }
3984                     if (Y.UA.gecko) {
3985                         /**
3986                         * This forced FF to redraw the content on backspace.
3987                         * On some occasions FF will leave a cursor residue after content has been deleted.
3988                         * Dropping in the empty textnode and then removing it causes FF to redraw and
3989                         * remove the "ghost cursors"
3990                         */
3991                         d = e.changedNode;
3992                         t = inst.config.doc.createTextNode(' ');
3993                         d.appendChild(t);
3994                         d.removeChild(t);
3995                     }
3996                     break;
3997             }
3998             if (Y.UA.gecko) {
3999                 if (e.changedNode && !e.changedNode.test(btag)) {
4000                     var p = e.changedNode.ancestor(btag);
4001                     if (p) {
4002                         this._lastPara = p;
4003                     }
4004                 }
4005             }
4006             
4007         },
4008         /**
4009         * Performs a block element filter when the Editor is first ready
4010         * @private
4011         * @method _afterEditorReady
4012         */
4013         _afterEditorReady: function() {
4014             var host = this.get(HOST), inst = host.getInstance(), btag;
4015             if (inst) {
4016                 inst.Selection.filterBlocks();
4017                 btag = inst.Selection.DEFAULT_BLOCK_TAG;
4018                 FIRST_P = BODY + ' > ' + btag;
4019                 P = btag;
4020             }
4021         },
4022         /**
4023         * Performs a block element filter when the Editor after an content change
4024         * @private
4025         * @method _afterContentChange
4026         */
4027         _afterContentChange: function() {
4028             var host = this.get(HOST), inst = host.getInstance();
4029             if (inst && inst.Selection) {
4030                 inst.Selection.filterBlocks();
4031             }
4032         },
4033         /**
4034         * Performs block/paste filtering after paste.
4035         * @private
4036         * @method _afterPaste
4037         */
4038         _afterPaste: function() {
4039             var host = this.get(HOST), inst = host.getInstance(),
4040                 sel = new inst.Selection();
4041
4042             Y.later(50, host, function() {
4043                 inst.Selection.filterBlocks();
4044             });
4045             
4046         },
4047         initializer: function() {
4048             var host = this.get(HOST);
4049             if (host.editorBR) {
4050                 Y.error('Can not plug EditorPara and EditorBR at the same time.');
4051                 return;
4052             }
4053
4054             host.on(NODE_CHANGE, Y.bind(this._onNodeChange, this));
4055             host.after('ready', Y.bind(this._afterEditorReady, this));
4056             host.after('contentChange', Y.bind(this._afterContentChange, this));
4057             if (Y.Env.webkit) {
4058                 host.after('dom:paste', Y.bind(this._afterPaste, this));
4059             }
4060         }
4061     }, {
4062         /**
4063         * editorPara
4064         * @static
4065         * @property NAME
4066         */
4067         NAME: 'editorPara',
4068         /**
4069         * editorPara
4070         * @static
4071         * @property NS
4072         */
4073         NS: 'editorPara',
4074         ATTRS: {
4075             host: {
4076                 value: false
4077             }
4078         }
4079     });
4080     
4081     Y.namespace('Plugin');
4082     
4083     Y.Plugin.EditorPara = EditorPara;
4084
4085
4086
4087 }, '3.3.0' ,{requires:['node'], skinnable:false});
4088 YUI.add('editor-br', function(Y) {
4089
4090
4091
4092     /**
4093      * Plugin for Editor to normalize BR's.
4094      * @module editor
4095      * @submodule editor-br
4096      */     
4097     /**
4098      * Plugin for Editor to normalize BR's.
4099      * @class Plugin.EditorBR
4100      * @extends Base
4101      * @constructor
4102      */
4103
4104
4105     var EditorBR = function() {
4106         EditorBR.superclass.constructor.apply(this, arguments);
4107     }, HOST = 'host', LI = 'li';
4108
4109
4110     Y.extend(EditorBR, Y.Base, {
4111         /**
4112         * Frame keyDown handler that normalizes BR's when pressing ENTER.
4113         * @private
4114         * @method _onKeyDown
4115         */
4116         _onKeyDown: function(e) {
4117             if (e.stopped) {
4118                 e.halt();
4119                 return;
4120             }
4121             if (e.keyCode == 13) {
4122                 var host = this.get(HOST), inst = host.getInstance(),
4123                     sel = new inst.Selection(),
4124                     last = '';
4125
4126                 if (sel) {
4127                     if (Y.UA.ie) {
4128                         if (!sel.anchorNode || (!sel.anchorNode.test(LI) && !sel.anchorNode.ancestor(LI))) {
4129                             sel._selection.pasteHTML('<br>');
4130                             sel._selection.collapse(false);
4131                             sel._selection.select();
4132                             e.halt();
4133                         }
4134                     }
4135                     if (Y.UA.webkit) {
4136                         if (!sel.anchorNode.test(LI) && !sel.anchorNode.ancestor(LI)) {
4137                             host.frame._execCommand('insertlinebreak', null);
4138                             e.halt();
4139                         }
4140                     }
4141                 }
4142             }
4143         },
4144         /**
4145         * Adds listeners for keydown in IE and Webkit. Also fires insertbeonreturn for supporting browsers.
4146         * @private
4147         * @method _afterEditorReady
4148         */
4149         _afterEditorReady: function() {
4150             var inst = this.get(HOST).getInstance();
4151             try {
4152                 inst.config.doc.execCommand('insertbronreturn', null, true);
4153             } catch (bre) {};
4154
4155             if (Y.UA.ie || Y.UA.webkit) {
4156                 inst.on('keydown', Y.bind(this._onKeyDown, this), inst.config.doc);
4157             }
4158         },
4159         /**
4160         * Adds a nodeChange listener only for FF, in the event of a backspace or delete, it creates an empy textNode
4161         * inserts it into the DOM after the e.changedNode, then removes it. Causing FF to redraw the content.
4162         * @private
4163         * @method _onNodeChange
4164         * @param {Event} e The nodeChange event.
4165         */
4166         _onNodeChange: function(e) {
4167             switch (e.changedType) {
4168                 case 'backspace-up':
4169                 case 'backspace-down':
4170                 case 'delete-up':
4171                     /**
4172                     * This forced FF to redraw the content on backspace.
4173                     * On some occasions FF will leave a cursor residue after content has been deleted.
4174                     * Dropping in the empty textnode and then removing it causes FF to redraw and
4175                     * remove the "ghost cursors"
4176                     */
4177                     var inst = this.get(HOST).getInstance();
4178                     var d = e.changedNode;
4179                     var t = inst.config.doc.createTextNode(' ');
4180                     d.appendChild(t);
4181                     d.removeChild(t);
4182                     break;
4183             }
4184         },
4185         initializer: function() {
4186             var host = this.get(HOST);
4187             if (host.editorPara) {
4188                 Y.error('Can not plug EditorBR and EditorPara at the same time.');
4189                 return;
4190             }
4191             host.after('ready', Y.bind(this._afterEditorReady, this));
4192             if (Y.UA.gecko) {
4193                 host.on('nodeChange', Y.bind(this._onNodeChange, this));
4194             }
4195         }
4196     }, {
4197         /**
4198         * editorBR
4199         * @static
4200         * @property NAME
4201         */
4202         NAME: 'editorBR',
4203         /**
4204         * editorBR
4205         * @static
4206         * @property NS
4207         */
4208         NS: 'editorBR',
4209         ATTRS: {
4210             host: {
4211                 value: false
4212             }
4213         }
4214     });
4215     
4216     Y.namespace('Plugin');
4217     
4218     Y.Plugin.EditorBR = EditorBR;
4219
4220
4221
4222 }, '3.3.0' ,{requires:['node'], skinnable:false});
4223
4224
4225 YUI.add('editor', function(Y){}, '3.3.0' ,{skinnable:false, use:['frame', 'selection', 'exec-command', 'editor-base', 'editor-para', 'editor-br', 'editor-bidi', 'createlink-base']});
4226