~lutostag/ubuntu/utopic/maas/1.5.2

« back to all changes in this revision

Viewing changes to src/maasserver/static/jslibs/yui/3.4.1/build/selection/selection-debug.js

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2012-03-15 18:14:08 UTC
  • mfrom: (1.1.3)
  • Revision ID: package-import@ubuntu.com-20120315181408-zgl94hzo0x4n99an
Tags: 0.1+bzr295+dfsg-0ubuntu2
* debian/patches:
  - 01-fix-database-settings.patch: Update to set PSERV_URL.
  - 02-pserv-config.patch: Set port to 8001.
* debian/maas.postinst: Run maas-import-isos on install.
* debian/control: Depends on rabbitmq-server.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
YUI 3.4.1 (build 4118)
 
3
Copyright 2011 Yahoo! Inc. All rights reserved.
 
4
Licensed under the BSD License.
 
5
http://yuilibrary.com/license/
 
6
*/
 
7
YUI.add('selection', function(Y) {
 
8
 
 
9
    /**
 
10
     * Wraps some common Selection/Range functionality into a simple object
 
11
     * @class Selection
 
12
     * @constructor
 
13
     * @module editor
 
14
     * @submodule selection
 
15
     */
 
16
    
 
17
    //TODO This shouldn't be there, Y.Node doesn't normalize getting textnode content.
 
18
    var textContent = 'textContent',
 
19
    INNER_HTML = 'innerHTML',
 
20
    FONT_FAMILY = 'fontFamily';
 
21
 
 
22
    if (Y.UA.ie) {
 
23
        textContent = 'nodeValue';
 
24
    }
 
25
 
 
26
    Y.Selection = function(domEvent) {
 
27
        var sel, par, ieNode, nodes, rng, i;
 
28
 
 
29
        if (Y.config.win.getSelection) {
 
30
                sel = Y.config.win.getSelection();
 
31
        } else if (Y.config.doc.selection) {
 
32
            sel = Y.config.doc.selection.createRange();
 
33
        }
 
34
        this._selection = sel;
 
35
 
 
36
        if (sel.pasteHTML) {
 
37
            this.isCollapsed = (sel.compareEndPoints('StartToEnd', sel)) ? false : true;
 
38
            if (this.isCollapsed) {
 
39
                this.anchorNode = this.focusNode = Y.one(sel.parentElement());
 
40
 
 
41
                if (domEvent) {
 
42
                    ieNode = Y.config.doc.elementFromPoint(domEvent.clientX, domEvent.clientY);
 
43
                }
 
44
                rng = sel.duplicate();
 
45
                if (!ieNode) {
 
46
                    par = sel.parentElement();
 
47
                    nodes = par.childNodes;
 
48
 
 
49
                    for (i = 0; i < nodes.length; i++) {
 
50
                        //This causes IE to not allow a selection on a doubleclick
 
51
                        //rng.select(nodes[i]);
 
52
                        if (rng.inRange(sel)) {
 
53
                            if (!ieNode) {
 
54
                                ieNode = nodes[i];
 
55
                            }
 
56
                        }
 
57
                    }
 
58
                }
 
59
 
 
60
                this.ieNode = ieNode;
 
61
                
 
62
                if (ieNode) {
 
63
                    if (ieNode.nodeType !== 3) {
 
64
                        if (ieNode.firstChild) {
 
65
                            ieNode = ieNode.firstChild;
 
66
                        }
 
67
                        if (ieNode && ieNode.tagName && ieNode.tagName.toLowerCase() === 'body') {
 
68
                            if (ieNode.firstChild) {
 
69
                                ieNode = ieNode.firstChild;
 
70
                            }
 
71
                        }
 
72
                    }
 
73
                    this.anchorNode = this.focusNode = Y.Selection.resolve(ieNode);
 
74
                    
 
75
                    rng.moveToElementText(sel.parentElement());
 
76
                    var comp = sel.compareEndPoints('StartToStart', rng),
 
77
                    moved = 0;
 
78
                    if (comp) {
 
79
                        //We are not at the beginning of the selection.
 
80
                        //Setting the move to something large, may need to increase it later
 
81
                        moved = Math.abs(sel.move('character', -9999));
 
82
                    }
 
83
                    
 
84
                    this.anchorOffset = this.focusOffset = moved;
 
85
                    
 
86
                    this.anchorTextNode = this.focusTextNode = Y.one(ieNode);
 
87
                }
 
88
                
 
89
                
 
90
            } else {
 
91
                //This helps IE deal with a selection and nodeChange events
 
92
                if (sel.htmlText && sel.htmlText !== '') {
 
93
                    var n = Y.Node.create(sel.htmlText);
 
94
                    if (n && n.get('id')) {
 
95
                        var id = n.get('id');
 
96
                        this.anchorNode = this.focusNode = Y.one('#' + id);
 
97
                    } else if (n) {
 
98
                        n = n.get('childNodes');
 
99
                        this.anchorNode = this.focusNode = n.item(0);
 
100
                    }
 
101
                }
 
102
            }
 
103
 
 
104
            //var self = this;
 
105
            //debugger;
 
106
        } else {
 
107
            this.isCollapsed = sel.isCollapsed;
 
108
            this.anchorNode = Y.Selection.resolve(sel.anchorNode);
 
109
            this.focusNode = Y.Selection.resolve(sel.focusNode);
 
110
            this.anchorOffset = sel.anchorOffset;
 
111
            this.focusOffset = sel.focusOffset;
 
112
            
 
113
            this.anchorTextNode = Y.one(sel.anchorNode);
 
114
            this.focusTextNode = Y.one(sel.focusNode);
 
115
        }
 
116
        if (Y.Lang.isString(sel.text)) {
 
117
            this.text = sel.text;
 
118
        } else {
 
119
            if (sel.toString) {
 
120
                this.text = sel.toString();
 
121
            } else {
 
122
                this.text = '';
 
123
            }
 
124
        }
 
125
    };
 
126
    
 
127
    /**
 
128
    * Utility method to remove dead font-family styles from an element.
 
129
    * @static
 
130
    * @method removeFontFamily
 
131
    */
 
132
    Y.Selection.removeFontFamily = function(n) {
 
133
        n.removeAttribute('face');
 
134
        var s = n.getAttribute('style').toLowerCase();
 
135
        if (s === '' || (s == 'font-family: ')) {
 
136
            n.removeAttribute('style');
 
137
        }
 
138
        if (s.match(Y.Selection.REG_FONTFAMILY)) {
 
139
            s = s.replace(Y.Selection.REG_FONTFAMILY, '');
 
140
            n.setAttribute('style', s);
 
141
        }
 
142
    };
 
143
 
 
144
    /**
 
145
    * Performs a prefilter on all nodes in the editor. Looks for nodes with a style: fontFamily or font face
 
146
    * It then creates a dynamic class assigns it and removed the property. This is so that we don't lose
 
147
    * the fontFamily when selecting nodes.
 
148
    * @static
 
149
    * @method filter
 
150
    */
 
151
    Y.Selection.filter = function(blocks) {
 
152
        var startTime = (new Date()).getTime();
 
153
        Y.log('Filtering nodes', 'info', 'selection');
 
154
 
 
155
        var nodes = Y.all(Y.Selection.ALL),
 
156
            baseNodes = Y.all('strong,em'),
 
157
            doc = Y.config.doc, hrs,
 
158
            classNames = {}, cssString = '',
 
159
            ls;
 
160
 
 
161
        var startTime1 = (new Date()).getTime();
 
162
        nodes.each(function(n) {
 
163
            var raw = Y.Node.getDOMNode(n);
 
164
            if (raw.style[FONT_FAMILY]) {
 
165
                classNames['.' + n._yuid] = raw.style[FONT_FAMILY];
 
166
                n.addClass(n._yuid);
 
167
 
 
168
                Y.Selection.removeFontFamily(raw);
 
169
            }
 
170
            /*
 
171
            if (n.getStyle(FONT_FAMILY)) {
 
172
                classNames['.' + n._yuid] = n.getStyle(FONT_FAMILY);
 
173
                n.addClass(n._yuid);
 
174
                n.removeAttribute('face');
 
175
                n.setStyle(FONT_FAMILY, '');
 
176
                if (n.getAttribute('style') === '') {
 
177
                    n.removeAttribute('style');
 
178
                }
 
179
                //This is for IE
 
180
                if (n.getAttribute('style').toLowerCase() === 'font-family: ') {
 
181
                    n.removeAttribute('style');
 
182
                }
 
183
            }
 
184
            */
 
185
        });
 
186
        var endTime1 = (new Date()).getTime();
 
187
        Y.log('Node Filter Timer: ' + (endTime1 - startTime1) + 'ms', 'info', 'selection');
 
188
 
 
189
        Y.all('.hr').addClass('yui-skip').addClass('yui-non');
 
190
        
 
191
        if (Y.UA.ie) {
 
192
            hrs = doc.getElementsByTagName('hr');
 
193
            Y.each(hrs, function(hr) {
 
194
                var el = doc.createElement('div');
 
195
                    el.className = 'hr yui-non yui-skip';
 
196
                    
 
197
                    el.setAttribute('readonly', true);
 
198
                    el.setAttribute('contenteditable', false); //Keep it from being Edited
 
199
                    if (hr.parentNode) {
 
200
                        hr.parentNode.replaceChild(el, hr);
 
201
                    }
 
202
                    //Had to move to inline style. writes for ie's < 8. They don't render el.setAttribute('style');
 
203
                    var s = el.style;
 
204
                    s.border = '1px solid #ccc';
 
205
                    s.lineHeight = '0';
 
206
                    s.height = '0';
 
207
                    s.fontSize = '0';
 
208
                    s.marginTop = '5px';
 
209
                    s.marginBottom = '5px';
 
210
                    s.marginLeft = '0px';
 
211
                    s.marginRight = '0px';
 
212
                    s.padding = '0';
 
213
            });
 
214
        }
 
215
        
 
216
 
 
217
        Y.each(classNames, function(v, k) {
 
218
            cssString += k + ' { font-family: ' + v.replace(/"/gi, '') + '; }';
 
219
        });
 
220
        Y.StyleSheet(cssString, 'editor');
 
221
 
 
222
        
 
223
        //Not sure about this one?
 
224
        baseNodes.each(function(n, k) {
 
225
            var t = n.get('tagName').toLowerCase(),
 
226
                newTag = 'i';
 
227
            if (t === 'strong') {
 
228
                newTag = 'b';
 
229
            }
 
230
            Y.Selection.prototype._swap(baseNodes.item(k), newTag);
 
231
        });
 
232
 
 
233
        //Filter out all the empty UL/OL's
 
234
        ls = Y.all('ol,ul');
 
235
        ls.each(function(v, k) {
 
236
            var lis = v.all('li');
 
237
            if (!lis.size()) {
 
238
                v.remove();
 
239
            }
 
240
        });
 
241
        
 
242
        if (blocks) {
 
243
            Y.Selection.filterBlocks();
 
244
        }
 
245
        var endTime = (new Date()).getTime();
 
246
        Y.log('Filter Timer: ' + (endTime - startTime) + 'ms', 'info', 'selection');
 
247
    };
 
248
 
 
249
    /**
 
250
    * Method attempts to replace all "orphined" text nodes in the main body by wrapping them with a <p>. Called from filter.
 
251
    * @static
 
252
    * @method filterBlocks
 
253
    */
 
254
    Y.Selection.filterBlocks = function() {
 
255
        var startTime = (new Date()).getTime();
 
256
        Y.log('RAW filter blocks', 'info', 'selection');
 
257
        var childs = Y.config.doc.body.childNodes, i, node, wrapped = false, doit = true,
 
258
            sel, single, br, divs, spans, c, s;
 
259
 
 
260
        if (childs) {
 
261
            for (i = 0; i < childs.length; i++) {
 
262
                node = Y.one(childs[i]);
 
263
                if (!node.test(Y.Selection.BLOCKS)) {
 
264
                    doit = true;
 
265
                    if (childs[i].nodeType == 3) {
 
266
                        c = childs[i][textContent].match(Y.Selection.REG_CHAR);
 
267
                        s = childs[i][textContent].match(Y.Selection.REG_NON);
 
268
                        if (c === null && s) {
 
269
                            doit = false;
 
270
                            
 
271
                        }
 
272
                    }
 
273
                    if (doit) {
 
274
                        if (!wrapped) {
 
275
                            wrapped = [];
 
276
                        }
 
277
                        wrapped.push(childs[i]);
 
278
                    }
 
279
                } else {
 
280
                    wrapped = Y.Selection._wrapBlock(wrapped);
 
281
                }
 
282
            }
 
283
            wrapped = Y.Selection._wrapBlock(wrapped);
 
284
        }
 
285
 
 
286
        single = Y.all(Y.Selection.DEFAULT_BLOCK_TAG);
 
287
        if (single.size() === 1) {
 
288
            Y.log('Only One default block tag (' + Y.Selection.DEFAULT_BLOCK_TAG + '), focus it..', 'info', 'selection');
 
289
            br = single.item(0).all('br');
 
290
            if (br.size() === 1) {
 
291
                if (!br.item(0).test('.yui-cursor')) {
 
292
                    br.item(0).remove();
 
293
                }
 
294
                var html = single.item(0).get('innerHTML');
 
295
                if (html === '' || html === ' ') {
 
296
                    Y.log('Paragraph empty, focusing cursor', 'info', 'selection');
 
297
                    single.set('innerHTML', Y.Selection.CURSOR);
 
298
                    sel = new Y.Selection();
 
299
                    sel.focusCursor(true, true);
 
300
                }
 
301
                if (br.item(0).test('.yui-cursor') && Y.UA.ie) {
 
302
                    br.item(0).remove();
 
303
                }
 
304
            }
 
305
        } else {
 
306
            single.each(function(p) {
 
307
                var html = p.get('innerHTML');
 
308
                if (html === '') {
 
309
                    Y.log('Empty Paragraph Tag Found, Removing It', 'info', 'selection');
 
310
                    p.remove();
 
311
                }
 
312
            });
 
313
        }
 
314
        
 
315
        if (!Y.UA.ie) {
 
316
            /*
 
317
            divs = Y.all('div, p');
 
318
            divs.each(function(d) {
 
319
                if (d.hasClass('yui-non')) {
 
320
                    return;
 
321
                }
 
322
                var html = d.get('innerHTML');
 
323
                if (html === '') {
 
324
                    Y.log('Empty DIV/P Tag Found, Removing It', 'info', 'selection');
 
325
                    d.remove();
 
326
                } else {
 
327
                    Y.log('DIVS/PS Count: ' + d.get('childNodes').size(), 'info', 'selection');
 
328
                    if (d.get('childNodes').size() == 1) {
 
329
                        Y.log('This Div/P only has one Child Node', 'info', 'selection');
 
330
                        if (d.ancestor('p')) {
 
331
                            Y.log('This Div/P is a child of a paragraph, remove it..', 'info', 'selection');
 
332
                            d.replace(d.get('firstChild'));
 
333
                        }
 
334
                    }
 
335
                }
 
336
            });*/
 
337
 
 
338
            /* Removed this, as it was causing Pasting to be funky in Safari
 
339
            spans = Y.all('.Apple-style-span, .apple-style-span');
 
340
            Y.log('Apple Spans found: ' + spans.size(), 'info', 'selection');
 
341
            spans.each(function(s) {
 
342
                s.setAttribute('style', '');
 
343
            });
 
344
            */
 
345
        }
 
346
 
 
347
 
 
348
        var endTime = (new Date()).getTime();
 
349
        Y.log('FilterBlocks Timer: ' + (endTime - startTime) + 'ms', 'info', 'selection');
 
350
    };
 
351
 
 
352
    /**
 
353
    * Regular Expression used to find dead font-family styles
 
354
    * @static
 
355
    * @property REG_FONTFAMILY
 
356
    */   
 
357
    Y.Selection.REG_FONTFAMILY = /font-family: ;/;
 
358
 
 
359
    /**
 
360
    * Regular Expression to determine if a string has a character in it
 
361
    * @static
 
362
    * @property REG_CHAR
 
363
    */   
 
364
    Y.Selection.REG_CHAR = /[a-zA-Z-0-9_!@#\$%\^&*\(\)-=_+\[\]\\{}|;':",.\/<>\?]/gi;
 
365
 
 
366
    /**
 
367
    * Regular Expression to determine if a string has a non-character in it
 
368
    * @static
 
369
    * @property REG_NON
 
370
    */
 
371
    Y.Selection.REG_NON = /[\s|\n|\t]/gi;
 
372
 
 
373
    /**
 
374
    * Regular Expression to remove all HTML from a string
 
375
    * @static
 
376
    * @property REG_NOHTML
 
377
    */
 
378
    Y.Selection.REG_NOHTML = /<\S[^><]*>/g;
 
379
 
 
380
 
 
381
    /**
 
382
    * Wraps an array of elements in a Block level tag
 
383
    * @static
 
384
    * @private
 
385
    * @method _wrapBlock
 
386
    */
 
387
    Y.Selection._wrapBlock = function(wrapped) {
 
388
        if (wrapped) {
 
389
            var newChild = Y.Node.create('<' + Y.Selection.DEFAULT_BLOCK_TAG + '></' + Y.Selection.DEFAULT_BLOCK_TAG + '>'),
 
390
                firstChild = Y.one(wrapped[0]), i;
 
391
 
 
392
            for (i = 1; i < wrapped.length; i++) {
 
393
                newChild.append(wrapped[i]);
 
394
            }
 
395
            firstChild.replace(newChild);
 
396
            newChild.prepend(firstChild);
 
397
        }
 
398
        return false;
 
399
    };
 
400
 
 
401
    /**
 
402
    * Undoes what filter does enough to return the HTML from the Editor, then re-applies the filter.
 
403
    * @static
 
404
    * @method unfilter
 
405
    * @return {String} The filtered HTML
 
406
    */
 
407
    Y.Selection.unfilter = function() {
 
408
        var nodes = Y.all('body [class]'),
 
409
            html = '', nons, ids,
 
410
            body = Y.one('body');
 
411
        
 
412
        Y.log('UnFiltering nodes', 'info', 'selection');
 
413
        
 
414
        nodes.each(function(n) {
 
415
            if (n.hasClass(n._yuid)) {
 
416
                //One of ours
 
417
                n.setStyle(FONT_FAMILY, n.getStyle(FONT_FAMILY));
 
418
                n.removeClass(n._yuid);
 
419
                if (n.getAttribute('class') === '') {
 
420
                    n.removeAttribute('class');
 
421
                }
 
422
            }
 
423
        });
 
424
 
 
425
        nons = Y.all('.yui-non');
 
426
        nons.each(function(n) {
 
427
            if (!n.hasClass('yui-skip') && n.get('innerHTML') === '') {
 
428
                n.remove();
 
429
            } else {
 
430
                n.removeClass('yui-non').removeClass('yui-skip');
 
431
            }
 
432
        });
 
433
 
 
434
        ids = Y.all('body [id]');
 
435
        ids.each(function(n) {
 
436
            if (n.get('id').indexOf('yui_3_') === 0) {
 
437
                n.removeAttribute('id');
 
438
                n.removeAttribute('_yuid');
 
439
            }
 
440
        });
 
441
        
 
442
        if (body) {
 
443
            html = body.get('innerHTML');
 
444
        }
 
445
        
 
446
        Y.all('.hr').addClass('yui-skip').addClass('yui-non');
 
447
        
 
448
        /*
 
449
        nodes.each(function(n) {
 
450
            n.addClass(n._yuid);
 
451
            n.setStyle(FONT_FAMILY, '');
 
452
            if (n.getAttribute('style') === '') {
 
453
                n.removeAttribute('style');
 
454
            }
 
455
        });
 
456
        */
 
457
        
 
458
        return html;
 
459
    };
 
460
    /**
 
461
    * Resolve a node from the selection object and return a Node instance
 
462
    * @static
 
463
    * @method resolve
 
464
    * @param {HTMLElement} n The HTMLElement to resolve. Might be a TextNode, gives parentNode.
 
465
    * @return {Node} The Resolved node
 
466
    */
 
467
    Y.Selection.resolve = function(n) {
 
468
        if (n && n.nodeType === 3) {
 
469
            //Adding a try/catch here because in rare occasions IE will
 
470
            //Throw a error accessing the parentNode of a stranded text node.
 
471
            //In the case of Ctrl+Z (Undo)
 
472
            try {
 
473
                n = n.parentNode;
 
474
            } catch (re) {
 
475
                n = 'body';
 
476
            }
 
477
        }
 
478
        return Y.one(n);
 
479
    };
 
480
 
 
481
    /**
 
482
    * Returns the innerHTML of a node with all HTML tags removed.
 
483
    * @static
 
484
    * @method getText
 
485
    * @param {Node} node The Node instance to remove the HTML from
 
486
    * @return {String} The string of text
 
487
    */
 
488
    Y.Selection.getText = function(node) {
 
489
        var txt = node.get('innerHTML').replace(Y.Selection.REG_NOHTML, '');
 
490
        //Clean out the cursor subs to see if the Node is empty
 
491
        txt = txt.replace('<span><br></span>', '').replace('<br>', '');
 
492
        return txt;
 
493
    };
 
494
 
 
495
    //Y.Selection.DEFAULT_BLOCK_TAG = 'div';
 
496
    Y.Selection.DEFAULT_BLOCK_TAG = 'p';
 
497
 
 
498
    /**
 
499
    * The selector to use when looking for Nodes to cache the value of: [style],font[face]
 
500
    * @static
 
501
    * @property ALL
 
502
    */
 
503
    Y.Selection.ALL = '[style],font[face]';
 
504
 
 
505
    /**
 
506
    * The selector to use when looking for block level items.
 
507
    * @static
 
508
    * @property BLOCKS
 
509
    */
 
510
    Y.Selection.BLOCKS = 'p,div,ul,ol,table,style';
 
511
    /**
 
512
    * The temporary fontname applied to a selection to retrieve their values: yui-tmp
 
513
    * @static
 
514
    * @property TMP
 
515
    */
 
516
    Y.Selection.TMP = 'yui-tmp';
 
517
    /**
 
518
    * The default tag to use when creating elements: span
 
519
    * @static
 
520
    * @property DEFAULT_TAG
 
521
    */
 
522
    Y.Selection.DEFAULT_TAG = 'span';
 
523
 
 
524
    /**
 
525
    * The id of the outer cursor wrapper
 
526
    * @static
 
527
    * @property DEFAULT_TAG
 
528
    */
 
529
    Y.Selection.CURID = 'yui-cursor';
 
530
 
 
531
    /**
 
532
    * The id used to wrap the inner space of the cursor position
 
533
    * @static
 
534
    * @property CUR_WRAPID
 
535
    */
 
536
    Y.Selection.CUR_WRAPID = 'yui-cursor-wrapper';
 
537
 
 
538
    /**
 
539
    * The default HTML used to focus the cursor..
 
540
    * @static
 
541
    * @property CURSOR
 
542
    */
 
543
    Y.Selection.CURSOR = '<span><br class="yui-cursor"></span>';
 
544
 
 
545
    Y.Selection.hasCursor = function() {
 
546
        var cur = Y.all('#' + Y.Selection.CUR_WRAPID);
 
547
        Y.log('Has Cursor: ' + cur.size(), 'info', 'selection');
 
548
        return cur.size();
 
549
    };
 
550
 
 
551
    /**
 
552
    * Called from Editor keydown to remove the "extra" space before the cursor.
 
553
    * @static
 
554
    * @method cleanCursor
 
555
    */
 
556
    Y.Selection.cleanCursor = function() {
 
557
        //Y.log('Cleaning Cursor', 'info', 'Selection');
 
558
        var cur, sel = 'br.yui-cursor';
 
559
        cur = Y.all(sel);
 
560
        if (cur.size()) {
 
561
            cur.each(function(b) {
 
562
                var c = b.get('parentNode.parentNode.childNodes'), html;
 
563
                if (c.size()) {
 
564
                    b.remove();
 
565
                } else {
 
566
                    html = Y.Selection.getText(c.item(0));
 
567
                    if (html !== '') {
 
568
                        b.remove();
 
569
                    }
 
570
                }
 
571
            });
 
572
        }
 
573
        /*
 
574
        var cur = Y.all('#' + Y.Selection.CUR_WRAPID);
 
575
        if (cur.size()) {
 
576
            cur.each(function(c) {
 
577
                var html = c.get('innerHTML');
 
578
                if (html == '&nbsp;' || html == '<br>') {
 
579
                    if (c.previous() || c.next()) {
 
580
                        c.remove();
 
581
                    }
 
582
                }
 
583
            });
 
584
        }
 
585
        */
 
586
    };
 
587
 
 
588
    Y.Selection.prototype = {
 
589
        /**
 
590
        * Range text value
 
591
        * @property text
 
592
        * @type String
 
593
        */
 
594
        text: null,
 
595
        /**
 
596
        * Flag to show if the range is collapsed or not
 
597
        * @property isCollapsed
 
598
        * @type Boolean
 
599
        */
 
600
        isCollapsed: null,
 
601
        /**
 
602
        * A Node instance of the parentNode of the anchorNode of the range
 
603
        * @property anchorNode
 
604
        * @type Node
 
605
        */
 
606
        anchorNode: null,
 
607
        /**
 
608
        * The offset from the range object
 
609
        * @property anchorOffset
 
610
        * @type Number
 
611
        */
 
612
        anchorOffset: null,
 
613
        /**
 
614
        * A Node instance of the actual textNode of the range.
 
615
        * @property anchorTextNode
 
616
        * @type Node
 
617
        */
 
618
        anchorTextNode: null,
 
619
        /**
 
620
        * A Node instance of the parentNode of the focusNode of the range
 
621
        * @property focusNode
 
622
        * @type Node
 
623
        */
 
624
        focusNode: null,
 
625
        /**
 
626
        * The offset from the range object
 
627
        * @property focusOffset
 
628
        * @type Number
 
629
        */
 
630
        focusOffset: null,
 
631
        /**
 
632
        * A Node instance of the actual textNode of the range.
 
633
        * @property focusTextNode
 
634
        * @type Node
 
635
        */
 
636
        focusTextNode: null,
 
637
        /**
 
638
        * The actual Selection/Range object
 
639
        * @property _selection
 
640
        * @private
 
641
        */
 
642
        _selection: null,
 
643
        /**
 
644
        * Wrap an element, with another element 
 
645
        * @private
 
646
        * @method _wrap
 
647
        * @param {HTMLElement} n The node to wrap 
 
648
        * @param {String} tag The tag to use when creating the new element.
 
649
        * @return {HTMLElement} The wrapped node
 
650
        */
 
651
        _wrap: function(n, tag) {
 
652
            var tmp = Y.Node.create('<' + tag + '></' + tag + '>');
 
653
            tmp.set(INNER_HTML, n.get(INNER_HTML));
 
654
            n.set(INNER_HTML, '');
 
655
            n.append(tmp);
 
656
            return Y.Node.getDOMNode(tmp);
 
657
        },
 
658
        /**
 
659
        * Swap an element, with another element 
 
660
        * @private
 
661
        * @method _swap
 
662
        * @param {HTMLElement} n The node to swap 
 
663
        * @param {String} tag The tag to use when creating the new element.
 
664
        * @return {HTMLElement} The new node
 
665
        */
 
666
        _swap: function(n, tag) {
 
667
            var tmp = Y.Node.create('<' + tag + '></' + tag + '>');
 
668
            tmp.set(INNER_HTML, n.get(INNER_HTML));
 
669
            n.replace(tmp, n);
 
670
            return Y.Node.getDOMNode(tmp);
 
671
        },
 
672
        /**
 
673
        * Get all the nodes in the current selection. This method will actually perform a filter first.
 
674
        * Then it calls doc.execCommand('fontname', null, 'yui-tmp') to touch all nodes in the selection.
 
675
        * The it compiles a list of all nodes affected by the execCommand and builds a NodeList to return.
 
676
        * @method getSelected
 
677
        * @return {NodeList} A NodeList of all items in the selection.
 
678
        */
 
679
        getSelected: function() {
 
680
            Y.Selection.filter();
 
681
            Y.config.doc.execCommand('fontname', null, Y.Selection.TMP);
 
682
            var nodes = Y.all(Y.Selection.ALL),
 
683
                items = [];
 
684
            
 
685
            nodes.each(function(n, k) {
 
686
                if (n.getStyle(FONT_FAMILY) ==  Y.Selection.TMP) {
 
687
                    n.setStyle(FONT_FAMILY, '');
 
688
                    Y.Selection.removeFontFamily(n);
 
689
                    if (!n.test('body')) {
 
690
                        items.push(Y.Node.getDOMNode(nodes.item(k)));
 
691
                    }
 
692
                }
 
693
            });
 
694
            return Y.all(items);
 
695
        },
 
696
        /**
 
697
        * Insert HTML at the current cursor position and return a Node instance of the newly inserted element.
 
698
        * @method insertContent
 
699
        * @param {String} html The HTML to insert.
 
700
        * @return {Node} The inserted Node.
 
701
        */
 
702
        insertContent: function(html) {
 
703
            return this.insertAtCursor(html, this.anchorTextNode, this.anchorOffset, true);
 
704
        },
 
705
        /**
 
706
        * 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.
 
707
        * @method insertAtCursor
 
708
        * @param {String} html The HTML to insert.
 
709
        * @param {Node} node The text node to break when inserting.
 
710
        * @param {Number} offset The left offset of the text node to break and insert the new content.
 
711
        * @param {Boolean} collapse Should the range be collapsed after insertion. default: false
 
712
        * @return {Node} The inserted Node.
 
713
        */
 
714
        insertAtCursor: function(html, node, offset, collapse) {
 
715
            var cur = Y.Node.create('<' + Y.Selection.DEFAULT_TAG + ' class="yui-non"></' + Y.Selection.DEFAULT_TAG + '>'),
 
716
                inHTML, txt, txt2, newNode, range = this.createRange(), b;
 
717
 
 
718
            if (node && node.test('body')) {
 
719
                b = Y.Node.create('<span></span>');
 
720
                node.append(b);
 
721
                node = b;
 
722
            }
 
723
 
 
724
            
 
725
            if (range.pasteHTML) {
 
726
                if (offset === 0 && node && !node.previous() && node.get('nodeType') === 3) {
 
727
                    /*
 
728
                    * For some strange reason, range.pasteHTML fails if the node is a textNode and
 
729
                    * the offset is 0. (The cursor is at the beginning of the line)
 
730
                    * It will always insert the new content at position 1 instead of 
 
731
                    * position 0. Here we test for that case and do it the hard way.
 
732
                    */
 
733
                    node.insert(html, 'before');
 
734
                    if (range.moveToElementText) {
 
735
                        range.moveToElementText(Y.Node.getDOMNode(node.previous()));
 
736
                    }
 
737
                    //Move the cursor after the new node
 
738
                    range.collapse(false);
 
739
                    range.select();
 
740
                    return node.previous();
 
741
                } else {
 
742
                    newNode = Y.Node.create(html);
 
743
                    try {
 
744
                        range.pasteHTML('<span id="rte-insert"></span>');
 
745
                    } catch (e) {}
 
746
                    inHTML = Y.one('#rte-insert');
 
747
                    if (inHTML) {
 
748
                        inHTML.set('id', '');
 
749
                        inHTML.replace(newNode);
 
750
                        if (range.moveToElementText) {
 
751
                            range.moveToElementText(Y.Node.getDOMNode(newNode));
 
752
                        }
 
753
                        range.collapse(false);
 
754
                        range.select();
 
755
                        return newNode;
 
756
                    } else {
 
757
                        Y.on('available', function() {
 
758
                            inHTML.set('id', '');
 
759
                            inHTML.replace(newNode);
 
760
                            if (range.moveToElementText) {
 
761
                                range.moveToElementText(Y.Node.getDOMNode(newNode));
 
762
                            }
 
763
                            range.collapse(false);
 
764
                            range.select();
 
765
                        }, '#rte-insert');
 
766
                    }
 
767
                }
 
768
            } else {
 
769
                //TODO using Y.Node.create here throws warnings & strips first white space character
 
770
                //txt = Y.one(Y.Node.create(inHTML.substr(0, offset)));
 
771
                //txt2 = Y.one(Y.Node.create(inHTML.substr(offset)));
 
772
                if (offset > 0) {
 
773
                    inHTML = node.get(textContent);
 
774
 
 
775
                    txt = Y.one(Y.config.doc.createTextNode(inHTML.substr(0, offset)));
 
776
                    txt2 = Y.one(Y.config.doc.createTextNode(inHTML.substr(offset)));
 
777
 
 
778
                    node.replace(txt, node);
 
779
                    newNode = Y.Node.create(html);
 
780
                    if (newNode.get('nodeType') === 11) {
 
781
                        b = Y.Node.create('<span></span>');
 
782
                        b.append(newNode);
 
783
                        newNode = b;
 
784
                    }
 
785
                    txt.insert(newNode, 'after');
 
786
                    //if (txt2 && txt2.get('length')) {
 
787
                    if (txt2) {
 
788
                        newNode.insert(cur, 'after');
 
789
                        cur.insert(txt2, 'after');
 
790
                        this.selectNode(cur, collapse);
 
791
                    }
 
792
                } else {
 
793
                    if (node.get('nodeType') === 3) {
 
794
                        node = node.get('parentNode');
 
795
                    }
 
796
                    newNode = Y.Node.create(html);
 
797
                    html = node.get('innerHTML').replace(/\n/gi, '');
 
798
                    if (html === '' || html === '<br>') {
 
799
                        node.append(newNode);
 
800
                    } else {
 
801
                        if (newNode.get('parentNode')) {
 
802
                            node.insert(newNode, 'before');
 
803
                        } else {
 
804
                            Y.one('body').prepend(newNode);
 
805
                        }
 
806
                    }
 
807
                    if (node.get('firstChild').test('br')) {
 
808
                        node.get('firstChild').remove();
 
809
                    }
 
810
                }
 
811
            }
 
812
            return newNode;
 
813
        },
 
814
        /**
 
815
        * Get all elements inside a selection and wrap them with a new element and return a NodeList of all elements touched.
 
816
        * @method wrapContent
 
817
        * @param {String} tag The tag to wrap all selected items with.
 
818
        * @return {NodeList} A NodeList of all items in the selection.
 
819
        */
 
820
        wrapContent: function(tag) {
 
821
            tag = (tag) ? tag : Y.Selection.DEFAULT_TAG;
 
822
 
 
823
            if (!this.isCollapsed) {
 
824
                Y.log('Wrapping selection with: ' + tag, 'info', 'selection');
 
825
                var items = this.getSelected(),
 
826
                    changed = [], range, last, first, range2;
 
827
 
 
828
                items.each(function(n, k) {
 
829
                    var t = n.get('tagName').toLowerCase();
 
830
                    if (t === 'font') {
 
831
                        changed.push(this._swap(items.item(k), tag));
 
832
                    } else {
 
833
                        changed.push(this._wrap(items.item(k), tag));
 
834
                    }
 
835
                }, this);
 
836
                
 
837
                        range = this.createRange();
 
838
                first = changed[0];
 
839
                last = changed[changed.length - 1];
 
840
                if (this._selection.removeAllRanges) {
 
841
                    range.setStart(changed[0], 0);
 
842
                    range.setEnd(last, last.childNodes.length);
 
843
                    this._selection.removeAllRanges();
 
844
                    this._selection.addRange(range);
 
845
                } else {
 
846
                    if (range.moveToElementText) {
 
847
                        range.moveToElementText(Y.Node.getDOMNode(first));
 
848
                        range2 = this.createRange();
 
849
                        range2.moveToElementText(Y.Node.getDOMNode(last));
 
850
                        range.setEndPoint('EndToEnd', range2);
 
851
                    }
 
852
                    range.select();
 
853
                }
 
854
 
 
855
                changed = Y.all(changed);
 
856
                Y.log('Returning NodeList with (' + changed.size() + ') item(s)' , 'info', 'selection');
 
857
                return changed;
 
858
 
 
859
 
 
860
            } else {
 
861
                Y.log('Can not wrap a collapsed selection, use insertContent', 'error', 'selection');
 
862
                return Y.all([]);
 
863
            }
 
864
        },
 
865
        /**
 
866
        * Find and replace a string inside a text node and replace it with HTML focusing the node after 
 
867
        * to allow you to continue to type.
 
868
        * @method replace
 
869
        * @param {String} se The string to search for.
 
870
        * @param {String} re The string of HTML to replace it with.
 
871
        * @return {Node} The node inserted.
 
872
        */
 
873
        replace: function(se,re) {
 
874
            Y.log('replacing (' + se + ') with (' + re + ')');
 
875
            var range = this.createRange(), node, txt, index, newNode;
 
876
 
 
877
            if (range.getBookmark) {
 
878
                index = range.getBookmark();
 
879
                txt = this.anchorNode.get('innerHTML').replace(se, re);
 
880
                this.anchorNode.set('innerHTML', txt);
 
881
                range.moveToBookmark(index);
 
882
                newNode = Y.one(range.parentElement());
 
883
            } else {
 
884
                node = this.anchorTextNode;
 
885
                txt = node.get(textContent);
 
886
                index = txt.indexOf(se);
 
887
 
 
888
                txt = txt.replace(se, '');
 
889
                node.set(textContent, txt);
 
890
                newNode = this.insertAtCursor(re, node, index, true);
 
891
            }
 
892
            return newNode;
 
893
        },
 
894
        /**
 
895
        * Destroy the range.
 
896
        * @method remove
 
897
        * @chainable
 
898
        * @return {Selection}
 
899
        */
 
900
        remove: function() {
 
901
            this._selection.removeAllRanges();
 
902
            return this;
 
903
        },
 
904
        /**
 
905
        * Wrapper for the different range creation methods.
 
906
        * @method createRange
 
907
        * @return {RangeObject}
 
908
        */
 
909
        createRange: function() {
 
910
            if (Y.config.doc.selection) {
 
911
                return Y.config.doc.selection.createRange();
 
912
            } else {
 
913
                        return Y.config.doc.createRange();
 
914
            }
 
915
        },
 
916
        /**
 
917
        * Select a Node (hilighting it).
 
918
        * @method selectNode
 
919
        * @param {Node} node The node to select
 
920
        * @param {Boolean} collapse Should the range be collapsed after insertion. default: false
 
921
        * @chainable
 
922
        * @return {Selection}
 
923
        */
 
924
        selectNode: function(node, collapse, end) {
 
925
            if (!node) {
 
926
                Y.log('Node passed to selectNode is null', 'error', 'selection');
 
927
                return;
 
928
            }
 
929
            end = end || 0;
 
930
            node = Y.Node.getDOMNode(node);
 
931
                    var range = this.createRange();
 
932
            if (range.selectNode) {
 
933
                range.selectNode(node);
 
934
                this._selection.removeAllRanges();
 
935
                this._selection.addRange(range);
 
936
                if (collapse) {
 
937
                    try {
 
938
                        this._selection.collapse(node, end);
 
939
                    } catch (err) {
 
940
                        this._selection.collapse(node, 0);
 
941
                    }
 
942
                }
 
943
            } else {
 
944
                if (node.nodeType === 3) {
 
945
                    node = node.parentNode;
 
946
                }
 
947
                try {
 
948
                    range.moveToElementText(node);
 
949
                } catch(e) {}
 
950
                if (collapse) {
 
951
                    range.collapse(((end) ? false : true));
 
952
                }
 
953
                range.select();
 
954
            }
 
955
            return this;
 
956
        },
 
957
        /**
 
958
        * Put a placeholder in the DOM at the current cursor position.
 
959
        * @method setCursor
 
960
        * @return {Node}
 
961
        */
 
962
        setCursor: function() {
 
963
            this.removeCursor(false);
 
964
            return this.insertContent(Y.Selection.CURSOR);
 
965
        },
 
966
        /**
 
967
        * Get the placeholder in the DOM at the current cursor position.
 
968
        * @method getCursor
 
969
        * @return {Node}
 
970
        */
 
971
        getCursor: function() {
 
972
            return Y.all('#' + Y.Selection.CURID);
 
973
        },
 
974
        /**
 
975
        * Remove the cursor placeholder from the DOM.
 
976
        * @method removeCursor
 
977
        * @param {Boolean} keep Setting this to true will keep the node, but remove the unique parts that make it the cursor.
 
978
        * @return {Node}
 
979
        */
 
980
        removeCursor: function(keep) {
 
981
            var cur = this.getCursor();
 
982
            if (cur) {
 
983
                if (keep) {
 
984
                    cur.removeAttribute('id');
 
985
                    cur.set('innerHTML', '<br class="yui-cursor">');
 
986
                } else {
 
987
                    cur.remove();
 
988
                }
 
989
            }
 
990
            return cur;
 
991
        },
 
992
        /**
 
993
        * Gets a stored cursor and focuses it for editing, must be called sometime after setCursor
 
994
        * @method focusCursor
 
995
        * @return {Node}
 
996
        */
 
997
        focusCursor: function(collapse, end) {
 
998
            if (collapse !== false) {
 
999
                collapse = true;
 
1000
            }
 
1001
            if (end !== false) {
 
1002
                end = true;
 
1003
            }
 
1004
            var cur = this.removeCursor(true);
 
1005
            if (cur) {
 
1006
                cur.each(function(c) {
 
1007
                    this.selectNode(c, collapse, end);
 
1008
                }, this);
 
1009
            }
 
1010
        },
 
1011
        /**
 
1012
        * Generic toString for logging.
 
1013
        * @method toString
 
1014
        * @return {String}
 
1015
        */
 
1016
        toString: function() {
 
1017
            return 'Selection Object';
 
1018
        }
 
1019
    };
 
1020
 
 
1021
 
 
1022
}, '3.4.1' ,{skinnable:false, requires:['node']});