Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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