]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/editor/editor-base.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / editor / editor-base.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('editor-base', function(Y) {
9
10
11     /**
12      * Base class for Editor. Handles the business logic of Editor, no GUI involved only utility methods and events.
13      * @module editor
14      * @submodule editor-base
15      */     
16     /**
17      * Base class for Editor. Handles the business logic of Editor, no GUI involved only utility methods and events.
18      * @class EditorBase
19      * @for EditorBase
20      * @extends Base
21      * @constructor
22      */
23     
24     var EditorBase = function() {
25         EditorBase.superclass.constructor.apply(this, arguments);
26     }, LAST_CHILD = ':last-child', BODY = 'body';
27
28     Y.extend(EditorBase, Y.Base, {
29         /**
30         * Internal reference to the Y.Frame instance
31         * @property frame
32         */
33         frame: null,
34         initializer: function() {
35             var frame = new Y.Frame({
36                 designMode: true,
37                 title: EditorBase.STRINGS.title,
38                 use: EditorBase.USE,
39                 dir: this.get('dir'),
40                 extracss: this.get('extracss'),
41                 linkedcss: this.get('linkedcss'),
42                 defaultblock: this.get('defaultblock'),
43                 host: this
44             }).plug(Y.Plugin.ExecCommand);
45
46
47             frame.after('ready', Y.bind(this._afterFrameReady, this));
48             frame.addTarget(this);
49
50             this.frame = frame;
51
52             this.publish('nodeChange', {
53                 emitFacade: true,
54                 bubbles: true,
55                 defaultFn: this._defNodeChangeFn
56             });
57             
58             //this.plug(Y.Plugin.EditorPara);
59         },
60         destructor: function() {
61             this.frame.destroy();
62
63             this.detachAll();
64         },
65         /**
66         * Copy certain styles from one node instance to another (used for new paragraph creation mainly)
67         * @method copyStyles
68         * @param {Node} from The Node instance to copy the styles from 
69         * @param {Node} to The Node instance to copy the styles to
70         */
71         copyStyles: function(from, to) {
72             if (from.test('a')) {
73                 //Don't carry the A styles
74                 return;
75             }
76             var styles = ['color', 'fontSize', 'fontFamily', 'backgroundColor', 'fontStyle' ],
77                 newStyles = {};
78
79             Y.each(styles, function(v) {
80                 newStyles[v] = from.getStyle(v);
81             });
82             if (from.ancestor('b,strong')) {
83                 newStyles.fontWeight = 'bold';
84             }
85             if (from.ancestor('u')) {
86                 if (!newStyles.textDecoration) {
87                     newStyles.textDecoration = 'underline';
88                 }
89             }
90             to.setStyles(newStyles);
91         },
92         /**
93         * Holder for the selection bookmark in IE.
94         * @property _lastBookmark
95         * @private
96         */
97         _lastBookmark: null,
98         /**
99         * Resolves the e.changedNode in the nodeChange event if it comes from the document. If
100         * the event came from the document, it will get the last child of the last child of the document
101         * and return that instead.
102         * @method _resolveChangedNode
103         * @param {Node} n The node to resolve
104         * @private
105         */
106         _resolveChangedNode: function(n) {
107             var inst = this.getInstance(), lc, lc2, found;
108             if (inst && n && n.test('html')) {
109                 lc = inst.one(BODY).one(LAST_CHILD);
110                 while (!found) {
111                     if (lc) {
112                         lc2 = lc.one(LAST_CHILD);
113                         if (lc2) {
114                             lc = lc2;
115                         } else {
116                             found = true;
117                         }
118                     } else {
119                         found = true;
120                     }
121                 }
122                 if (lc) {
123                     if (lc.test('br')) {
124                         if (lc.previous()) {
125                             lc = lc.previous();
126                         } else {
127                             lc = lc.get('parentNode');
128                         }
129                     }
130                     if (lc) {
131                         n = lc;
132                     }
133                 }
134                 
135             }
136             return n;
137         },
138         /**
139         * The default handler for the nodeChange event.
140         * @method _defNodeChangeFn
141         * @param {Event} e The event
142         * @private
143         */
144         _defNodeChangeFn: function(e) {
145             var startTime = (new Date()).getTime();
146             var inst = this.getInstance(), sel, cur,
147                 btag = inst.Selection.DEFAULT_BLOCK_TAG;
148
149             if (Y.UA.ie) {
150                 try {
151                     sel = inst.config.doc.selection.createRange();
152                     if (sel.getBookmark) {
153                         this._lastBookmark = sel.getBookmark();
154                     }
155                 } catch (ie) {}
156             }
157
158             e.changedNode = this._resolveChangedNode(e.changedNode);
159
160             /*
161             * @TODO
162             * This whole method needs to be fixed and made more dynamic.
163             * Maybe static functions for the e.changeType and an object bag
164             * to walk through and filter to pass off the event to before firing..
165             */
166             
167             switch (e.changedType) {
168                 case 'keydown':
169                     if (!Y.UA.gecko) {
170                         if (!EditorBase.NC_KEYS[e.changedEvent.keyCode] && !e.changedEvent.shiftKey && !e.changedEvent.ctrlKey && (e.changedEvent.keyCode !== 13)) {
171                             //inst.later(100, inst, inst.Selection.cleanCursor);
172                         }
173                     }
174                     break;
175                 case 'tab':
176                     if (!e.changedNode.test('li, li *') && !e.changedEvent.shiftKey) {
177                         e.changedEvent.frameEvent.preventDefault();
178                         if (Y.UA.webkit) {
179                             this.execCommand('inserttext', '\t');
180                         } else if (Y.UA.gecko) {
181                             this.frame.exec._command('inserthtml', '<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>');
182                         } else if (Y.UA.ie) {
183                             sel = new inst.Selection();
184                             sel._selection.pasteHTML(EditorBase.TABKEY);
185                         }
186                     }
187                     break;
188             }
189             if (Y.UA.webkit && e.commands && (e.commands.indent || e.commands.outdent)) {
190                 /**
191                 * When executing execCommand 'indent or 'outdent' Webkit applies
192                 * a class to the BLOCKQUOTE that adds left/right margin to it
193                 * This strips that style so it is just a normal BLOCKQUOTE
194                 */
195                 var bq = inst.all('.webkit-indent-blockquote');
196                 if (bq.size()) {
197                     bq.setStyle('margin', '');
198                 }
199             }
200
201             var changed = this.getDomPath(e.changedNode, false),
202                 cmds = {}, family, fsize, classes = [],
203                 fColor = '', bColor = '';
204
205             if (e.commands) {
206                 cmds = e.commands;
207             }
208             
209             Y.each(changed, function(el) {
210                 var tag = el.tagName.toLowerCase(),
211                     cmd = EditorBase.TAG2CMD[tag];
212
213                 if (cmd) {
214                     cmds[cmd] = 1;
215                 }
216
217                 //Bold and Italic styles
218                 var s = el.currentStyle || el.style;
219                 if ((''+s.fontWeight) == 'bold') { //Cast this to a string
220                     cmds.bold = 1;
221                 }
222                 if (Y.UA.ie) {
223                     if (s.fontWeight > 400) {
224                         cmds.bold = 1;
225                     }
226                 }
227                 if (s.fontStyle == 'italic') {
228                     cmds.italic = 1;
229                 }
230                 if (s.textDecoration == 'underline') {
231                     cmds.underline = 1;
232                 }
233                 if (s.textDecoration == 'line-through') {
234                     cmds.strikethrough = 1;
235                 }
236                 
237                 var n = inst.one(el);
238                 if (n.getStyle('fontFamily')) {
239                     var family2 = n.getStyle('fontFamily').split(',')[0].toLowerCase();
240                     if (family2) {
241                         family = family2;
242                     }
243                     if (family) {
244                         family = family.replace(/'/g, '').replace(/"/g, '');
245                     }
246                 }
247
248                 fsize = EditorBase.NORMALIZE_FONTSIZE(n);
249
250
251                 var cls = el.className.split(' ');
252
253                 Y.each(cls, function(v) {
254                     if (v !== '' && (v.substr(0, 4) !== 'yui_')) {
255                         classes.push(v);
256                     }
257                 });
258
259                 fColor = EditorBase.FILTER_RGB(n.getStyle('color'));
260                 var bColor2 = EditorBase.FILTER_RGB(s.backgroundColor);
261                 if (bColor2 !== 'transparent') {
262                     if (bColor2 !== '') {
263                         bColor = bColor2;
264                     }
265                 }
266                 
267             });
268             
269             e.dompath = inst.all(changed);
270             e.classNames = classes;
271             e.commands = cmds;
272
273             //TODO Dont' like this, not dynamic enough..
274             if (!e.fontFamily) {
275                 e.fontFamily = family;
276             }
277             if (!e.fontSize) {
278                 e.fontSize = fsize;
279             }
280             if (!e.fontColor) {
281                 e.fontColor = fColor;
282             }
283             if (!e.backgroundColor) {
284                 e.backgroundColor = bColor;
285             }
286
287             var endTime = (new Date()).getTime();
288         },
289         /**
290         * Walk the dom tree from this node up to body, returning a reversed array of parents.
291         * @method getDomPath
292         * @param {Node} node The Node to start from 
293         */
294         getDomPath: function(node, nodeList) {
295                         var domPath = [], domNode,
296                 inst = this.frame.getInstance();
297
298             domNode = inst.Node.getDOMNode(node);
299             //return inst.all(domNode);
300
301             while (domNode !== null) {
302                 
303                 if ((domNode === inst.config.doc.documentElement) || (domNode === inst.config.doc) || !domNode.tagName) {
304                     domNode = null;
305                     break;
306                 }
307                 
308                 if (!inst.DOM.inDoc(domNode)) {
309                     domNode = null;
310                     break;
311                 }
312                 
313                 //Check to see if we get el.nodeName and nodeType
314                 if (domNode.nodeName && domNode.nodeType && (domNode.nodeType == 1)) {
315                     domPath.push(domNode);
316                 }
317
318                 if (domNode == inst.config.doc.body) {
319                     domNode = null;
320                     break;
321                 }
322
323                 domNode = domNode.parentNode;
324             }
325
326             /*{{{ Using Node 
327             while (node !== null) {
328                 if (node.test('html') || node.test('doc') || !node.get('tagName')) {
329                     node = null;
330                     break;
331                 }
332                 if (!node.inDoc()) {
333                     node = null;
334                     break;
335                 }
336                 //Check to see if we get el.nodeName and nodeType
337                 if (node.get('nodeName') && node.get('nodeType') && (node.get('nodeType') == 1)) {
338                     domPath.push(inst.Node.getDOMNode(node));
339                 }
340
341                 if (node.test('body')) {
342                     node = null;
343                     break;
344                 }
345
346                 node = node.get('parentNode');
347             }
348             }}}*/
349
350             if (domPath.length === 0) {
351                 domPath[0] = inst.config.doc.body;
352             }
353
354             if (nodeList) {
355                 return inst.all(domPath.reverse());
356             } else {
357                 return domPath.reverse();
358             }
359
360         },
361         /**
362         * After frame ready, bind mousedown & keyup listeners
363         * @method _afterFrameReady
364         * @private
365         */
366         _afterFrameReady: function() {
367             var inst = this.frame.getInstance();
368             
369             this.frame.on('dom:mouseup', Y.bind(this._onFrameMouseUp, this));
370             this.frame.on('dom:mousedown', Y.bind(this._onFrameMouseDown, this));
371             this.frame.on('dom:keydown', Y.bind(this._onFrameKeyDown, this));
372
373             if (Y.UA.ie) {
374                 this.frame.on('dom:activate', Y.bind(this._onFrameActivate, this));
375                 this.frame.on('dom:beforedeactivate', Y.bind(this._beforeFrameDeactivate, this));
376             }
377             this.frame.on('dom:keyup', Y.bind(this._onFrameKeyUp, this));
378             this.frame.on('dom:keypress', Y.bind(this._onFrameKeyPress, this));
379
380             inst.Selection.filter();
381             this.fire('ready');
382         },
383         /**
384         * Caches the current cursor position in IE.
385         * @method _beforeFrameDeactivate
386         * @private
387         */
388         _beforeFrameDeactivate: function() {
389             var inst = this.getInstance(),
390                 sel = inst.config.doc.selection.createRange();
391             
392             if ((!sel.compareEndPoints('StartToEnd', sel))) {
393                 sel.pasteHTML('<var id="yui-ie-cursor">');
394             }
395         },
396         /**
397         * Moves the cached selection bookmark back so IE can place the cursor in the right place.
398         * @method _onFrameActivate
399         * @private
400         */
401         _onFrameActivate: function() {
402             var inst = this.getInstance(),
403                 sel = new inst.Selection(),
404                 range = sel.createRange(),
405                 cur = inst.all('#yui-ie-cursor');
406
407             if (cur.size()) {
408                 cur.each(function(n) {
409                     n.set('id', '');
410                     range.moveToElementText(n._node);
411                     range.move('character', -1);
412                     range.move('character', 1);
413                     range.select();
414                     range.text = '';
415                     n.remove();
416                 });
417             }
418         },
419         /**
420         * Fires nodeChange event
421         * @method _onFrameMouseUp
422         * @private
423         */
424         _onFrameMouseUp: function(e) {
425             this.fire('nodeChange', { changedNode: e.frameTarget, changedType: 'mouseup', changedEvent: e.frameEvent  });
426         },
427         /**
428         * Fires nodeChange event
429         * @method _onFrameMouseDown
430         * @private
431         */
432         _onFrameMouseDown: function(e) {
433             this.fire('nodeChange', { changedNode: e.frameTarget, changedType: 'mousedown', changedEvent: e.frameEvent  });
434         },
435         /**
436         * Caches a copy of the selection for key events. Only creating the selection on keydown
437         * @property _currentSelection
438         * @private
439         */
440         _currentSelection: null,
441         /**
442         * Holds the timer for selection clearing
443         * @property _currentSelectionTimer
444         * @private
445         */
446         _currentSelectionTimer: null,
447         /**
448         * Flag to determine if we can clear the selection or not.
449         * @property _currentSelectionClear
450         * @private
451         */
452         _currentSelectionClear: null,
453         /**
454         * Fires nodeChange event
455         * @method _onFrameKeyDown
456         * @private
457         */
458         _onFrameKeyDown: function(e) {
459             var inst, sel;
460             if (!this._currentSelection) {
461                 if (this._currentSelectionTimer) {
462                     this._currentSelectionTimer.cancel();
463                 }
464                 this._currentSelectionTimer = Y.later(850, this, function() {
465                     this._currentSelectionClear = true;
466                 });
467                 
468                 inst = this.frame.getInstance();
469                 sel = new inst.Selection(e);
470
471                 this._currentSelection = sel;
472             } else {
473                 sel = this._currentSelection;
474             }
475
476             inst = this.frame.getInstance();
477             sel = new inst.Selection();
478
479             this._currentSelection = sel;
480             
481             if (sel && sel.anchorNode) {
482                 this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: 'keydown', changedEvent: e.frameEvent });
483                 if (EditorBase.NC_KEYS[e.keyCode]) {
484                     this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: EditorBase.NC_KEYS[e.keyCode], changedEvent: e.frameEvent });
485                     this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: EditorBase.NC_KEYS[e.keyCode] + '-down', changedEvent: e.frameEvent });
486                 }
487             }
488         },
489         /**
490         * Fires nodeChange event
491         * @method _onFrameKeyPress
492         * @private
493         */
494         _onFrameKeyPress: function(e) {
495             var sel = this._currentSelection;
496
497             if (sel && sel.anchorNode) {
498                 this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: 'keypress', changedEvent: e.frameEvent });
499                 if (EditorBase.NC_KEYS[e.keyCode]) {
500                     this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: EditorBase.NC_KEYS[e.keyCode] + '-press', changedEvent: e.frameEvent });
501                 }
502             }
503         },
504         /**
505         * Fires nodeChange event for keyup on specific keys
506         * @method _onFrameKeyUp
507         * @private
508         */
509         _onFrameKeyUp: function(e) {
510             var sel = this._currentSelection;
511
512             if (sel && sel.anchorNode) {
513                 this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: 'keyup', selection: sel, changedEvent: e.frameEvent  });
514                 if (EditorBase.NC_KEYS[e.keyCode]) {
515                     this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: EditorBase.NC_KEYS[e.keyCode] + '-up', selection: sel, changedEvent: e.frameEvent  });
516                 }
517             }
518             if (this._currentSelectionClear) {
519                 this._currentSelectionClear = this._currentSelection = null;
520             }
521         },
522         /**
523         * Pass through to the frame.execCommand method
524         * @method execCommand
525         * @param {String} cmd The command to pass: inserthtml, insertimage, bold
526         * @param {String} val The optional value of the command: Helvetica
527         * @return {Node/NodeList} The Node or Nodelist affected by the command. Only returns on override commands, not browser defined commands.
528         */
529         execCommand: function(cmd, val) {
530             var ret = this.frame.execCommand(cmd, val),
531                 inst = this.frame.getInstance(),
532                 sel = new inst.Selection(), cmds = {},
533                 e = { changedNode: sel.anchorNode, changedType: 'execcommand', nodes: ret };
534
535             switch (cmd) {
536                 case 'forecolor':
537                     e.fontColor = val;
538                     break;
539                 case 'backcolor':
540                     e.backgroundColor = val;
541                     break;
542                 case 'fontsize':
543                     e.fontSize = val;
544                     break;
545                 case 'fontname':
546                     e.fontFamily = val;
547                     break;
548             }
549
550             cmds[cmd] = 1;
551             e.commands = cmds;
552
553             this.fire('nodeChange', e);
554
555             return ret;
556         },
557         /**
558         * Get the YUI instance of the frame
559         * @method getInstance
560         * @return {YUI} The YUI instance bound to the frame.
561         */
562         getInstance: function() {
563             return this.frame.getInstance();
564         },
565         /**
566         * Renders the Y.Frame to the passed node.
567         * @method render
568         * @param {Selector/HTMLElement/Node} node The node to append the Editor to
569         * @return {EditorBase}
570         * @chainable
571         */
572         render: function(node) {
573             this.frame.set('content', this.get('content'));
574             this.frame.render(node);
575             return this;
576         },
577         /**
578         * Focus the contentWindow of the iframe
579         * @method focus
580         * @param {Function} fn Callback function to execute after focus happens
581         * @return {EditorBase}
582         * @chainable
583         */
584         focus: function(fn) {
585             this.frame.focus(fn);
586             return this;
587         },
588         /**
589         * Handles the showing of the Editor instance. Currently only handles the iframe
590         * @method show
591         * @return {EditorBase}
592         * @chainable
593         */
594         show: function() {
595             this.frame.show();
596             return this;
597         },
598         /**
599         * Handles the hiding of the Editor instance. Currently only handles the iframe
600         * @method hide
601         * @return {EditorBase}
602         * @chainable
603         */
604         hide: function() {
605             this.frame.hide();
606             return this;
607         },
608         /**
609         * (Un)Filters the content of the Editor, cleaning YUI related code. //TODO better filtering
610         * @method getContent
611         * @return {String} The filtered content of the Editor
612         */
613         getContent: function() {
614             var html = '', inst = this.getInstance();
615             if (inst && inst.Selection) {
616                 html = inst.Selection.unfilter();
617             }
618             //Removing the _yuid from the objects in IE
619             html = html.replace(/ _yuid="([^>]*)"/g, '');
620             return html;
621         }
622     }, {
623         /**
624         * @static
625         * @method NORMALIZE_FONTSIZE
626         * @description Pulls the fontSize from a node, then checks for string values (x-large, x-small)
627         * and converts them to pixel sizes. If the parsed size is different from the original, it calls
628         * node.setStyle to update the node with a pixel size for normalization.
629         */
630         NORMALIZE_FONTSIZE: function(n) {
631             var size = n.getStyle('fontSize'), oSize = size;
632             
633             switch (size) {
634                 case '-webkit-xxx-large':
635                     size = '48px';
636                     break;
637                 case 'xx-large':
638                     size = '32px';
639                     break;
640                 case 'x-large':
641                     size = '24px';
642                     break;
643                 case 'large':
644                     size = '18px';
645                     break;
646                 case 'medium':
647                     size = '16px';
648                     break;
649                 case 'small':
650                     size = '13px';
651                     break;
652                 case 'x-small':
653                     size = '10px';
654                     break;
655             }
656             if (oSize !== size) {
657                 n.setStyle('fontSize', size);
658             }
659             return size;
660         },
661         /**
662         * @static
663         * @property TABKEY
664         * @description The HTML markup to use for the tabkey
665         */
666         TABKEY: '<span class="tab">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>',
667         /**
668         * @static
669         * @method FILTER_RGB
670         * @param String css The CSS string containing rgb(#,#,#);
671         * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
672         * @return String
673         */
674         FILTER_RGB: function(css) {
675             if (css.toLowerCase().indexOf('rgb') != -1) {
676                 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
677                 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
678             
679                 if (rgb.length == 5) {
680                     var r = parseInt(rgb[1], 10).toString(16);
681                     var g = parseInt(rgb[2], 10).toString(16);
682                     var b = parseInt(rgb[3], 10).toString(16);
683
684                     r = r.length == 1 ? '0' + r : r;
685                     g = g.length == 1 ? '0' + g : g;
686                     b = b.length == 1 ? '0' + b : b;
687
688                     css = "#" + r + g + b;
689                 }
690             }
691             return css;
692         },        
693         /**
694         * @static
695         * @property TAG2CMD
696         * @description A hash table of tags to their execcomand's
697         */
698         TAG2CMD: {
699             'b': 'bold',
700             'strong': 'bold',
701             'i': 'italic',
702             'em': 'italic',
703             'u': 'underline',
704             'sup': 'superscript',
705             'sub': 'subscript',
706             'img': 'insertimage',
707             'a' : 'createlink',
708             'ul' : 'insertunorderedlist',
709             'ol' : 'insertorderedlist'
710         },
711         /**
712         * Hash table of keys to fire a nodeChange event for.
713         * @static
714         * @property NC_KEYS
715         * @type Object
716         */
717         NC_KEYS: {
718             8: 'backspace',
719             9: 'tab',
720             13: 'enter',
721             32: 'space',
722             33: 'pageup',
723             34: 'pagedown',
724             35: 'end',
725             36: 'home',
726             37: 'left',
727             38: 'up',
728             39: 'right',
729             40: 'down',
730             46: 'delete'
731         },
732         /**
733         * The default modules to use inside the Frame
734         * @static
735         * @property USE
736         * @type Array
737         */
738         USE: ['substitute', 'node', 'selector-css3', 'selection', 'stylesheet'],
739         /**
740         * The Class Name: editorBase
741         * @static
742         * @property NAME
743         */
744         NAME: 'editorBase',
745         /**
746         * Editor Strings
747         * @static
748         * @property STRINGS
749         */
750         STRINGS: {
751             /**
752             * Title of frame document: Rich Text Editor
753             * @static
754             * @property STRINGS.title
755             */
756             title: 'Rich Text Editor'
757         },
758         ATTRS: {
759             /**
760             * The content to load into the Editor Frame
761             * @attribute content
762             */
763             content: {
764                 value: '<br class="yui-cursor">',
765                 setter: function(str) {
766                     if (str.substr(0, 1) === "\n") {
767                         str = str.substr(1);
768                     }
769                     if (str === '') {
770                         str = '<br class="yui-cursor">';
771                     }
772                     if (str === ' ') {
773                         if (Y.UA.gecko) {
774                             str = '<br class="yui-cursor">';
775                         }
776                     }
777                     return this.frame.set('content', str);
778                 },
779                 getter: function() {
780                     return this.frame.get('content');
781                 }
782             },
783             /**
784             * The value of the dir attribute on the HTML element of the frame. Default: ltr
785             * @attribute dir
786             */
787             dir: {
788                 writeOnce: true,
789                 value: 'ltr'
790             },
791             /**
792             * @attribute linkedcss
793             * @description An array of url's to external linked style sheets
794             * @type String
795             */            
796             linkedcss: {
797                 value: '',
798                 setter: function(css) {
799                     if (this.frame) {
800                         this.frame.set('linkedcss', css);
801                     }
802                     return css;
803                 }
804             },
805             /**
806             * @attribute extracss
807             * @description A string of CSS to add to the Head of the Editor
808             * @type String
809             */            
810             extracss: {
811                 value: false,
812                 setter: function(css) {
813                     if (this.frame) {
814                         this.frame.set('extracss', css);
815                     }
816                     return css;
817                 }
818             },
819             /**
820             * @attribute defaultblock
821             * @description The default tag to use for block level items, defaults to: p
822             * @type String
823             */            
824             defaultblock: {
825                 value: 'p'
826             }
827         }
828     });
829
830     Y.EditorBase = EditorBase;
831
832     /**
833     * @event nodeChange
834     * @description Fired from mouseup & keyup.
835     * @param {Event.Facade} event An Event Facade object with the following specific properties added:
836     * <dl>
837     *   <dt>changedEvent</dt><dd>The event that caused the nodeChange</dd>
838     *   <dt>changedNode</dt><dd>The node that was interacted with</dd>
839     *   <dt>changedType</dt><dd>The type of change: mousedown, mouseup, right, left, backspace, tab, enter, etc..</dd>
840     *   <dt>commands</dt><dd>The list of execCommands that belong to this change and the dompath that's associated with the changedNode</dd>
841     *   <dt>classNames</dt><dd>An array of classNames that are applied to the changedNode and all of it's parents</dd>
842     *   <dt>dompath</dt><dd>A sorted array of node instances that make up the DOM path from the changedNode to body.</dd>
843     *   <dt>backgroundColor</dt><dd>The cascaded backgroundColor of the changedNode</dd>
844     *   <dt>fontColor</dt><dd>The cascaded fontColor of the changedNode</dd>
845     *   <dt>fontFamily</dt><dd>The cascaded fontFamily of the changedNode</dd>
846     *   <dt>fontSize</dt><dd>The cascaded fontSize of the changedNode</dd>
847     * </dl>
848     * @type {Event.Custom}
849     */
850
851     /**
852     * @event ready
853     * @description Fired after the frame is ready.
854     * @param {Event.Facade} event An Event Facade object.
855     * @type {Event.Custom}
856     */
857
858
859
860
861
862 }, '3.3.0' ,{requires:['base', 'frame', 'node', 'exec-command', 'selection', 'editor-para'], skinnable:false});