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