~ted/lazr-js/annoying-debug-message

« back to all changes in this revision

Viewing changes to src-js/lazrjs/yui/editor/editor-debug.js

  • Committer: Launchpad Patch Queue Manager
  • Date: 2010-09-09 14:20:30 UTC
  • mfrom: (182.1.3 yui-3.2)
  • Revision ID: launchpad@pqm.canonical.com-20100909142030-13w6vo0ixfysxc15
[r=beuno] Update lazr-js to yui-3.2

Show diffs side-by-side

added added

removed removed

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