source: sandbox/3.0/phpgwapi/js/ckeditor/_source/plugins/selection/plugin.js @ 2862

Revision 2862, 31.5 KB checked in by rodsouza, 14 years ago (diff)

Ticket #663 - Atualizando e centralizando o CKEditor (v. 3.2.1)

Line 
1/*
2Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.html or http://ckeditor.com/license
4*/
5
6(function()
7{
8        // #### checkSelectionChange : START
9
10        // The selection change check basically saves the element parent tree of
11        // the current node and check it on successive requests. If there is any
12        // change on the tree, then the selectionChange event gets fired.
13        function checkSelectionChange()
14        {
15                try
16                {
17                        // In IE, the "selectionchange" event may still get thrown when
18                        // releasing the WYSIWYG mode, so we need to check it first.
19                        var sel = this.getSelection();
20                        if ( !sel )
21                                return;
22
23                        var firstElement = sel.getStartElement();
24                        var currentPath = new CKEDITOR.dom.elementPath( firstElement );
25
26                        if ( !currentPath.compare( this._.selectionPreviousPath ) )
27                        {
28                                this._.selectionPreviousPath = currentPath;
29                                this.fire( 'selectionChange', { selection : sel, path : currentPath, element : firstElement } );
30                        }
31                }
32                catch (e)
33                {}
34        }
35
36        var checkSelectionChangeTimer,
37                checkSelectionChangeTimeoutPending;
38
39        function checkSelectionChangeTimeout()
40        {
41                // Firing the "OnSelectionChange" event on every key press started to
42                // be too slow. This function guarantees that there will be at least
43                // 200ms delay between selection checks.
44
45                checkSelectionChangeTimeoutPending = true;
46
47                if ( checkSelectionChangeTimer )
48                        return;
49
50                checkSelectionChangeTimeoutExec.call( this );
51
52                checkSelectionChangeTimer = CKEDITOR.tools.setTimeout( checkSelectionChangeTimeoutExec, 200, this );
53        }
54
55        function checkSelectionChangeTimeoutExec()
56        {
57                checkSelectionChangeTimer = null;
58
59                if ( checkSelectionChangeTimeoutPending )
60                {
61                        // Call this with a timeout so the browser properly moves the
62                        // selection after the mouseup. It happened that the selection was
63                        // being moved after the mouseup when clicking inside selected text
64                        // with Firefox.
65                        CKEDITOR.tools.setTimeout( checkSelectionChange, 0, this );
66
67                        checkSelectionChangeTimeoutPending = false;
68                }
69        }
70
71        // #### checkSelectionChange : END
72
73        var selectAllCmd =
74        {
75                modes : { wysiwyg : 1, source : 1 },
76                exec : function( editor )
77                {
78                        switch ( editor.mode )
79                        {
80                                case 'wysiwyg' :
81                                        editor.document.$.execCommand( 'SelectAll', false, null );
82                                        break;
83                                case 'source' :
84                                        // Select the contents of the textarea
85                                        var textarea = editor.textarea.$ ;
86                                        if ( CKEDITOR.env.ie )
87                                        {
88                                                textarea.createTextRange().execCommand( 'SelectAll' ) ;
89                                        }
90                                        else
91                                        {
92                                                textarea.selectionStart = 0 ;
93                                                textarea.selectionEnd = textarea.value.length ;
94                                        }
95                                        textarea.focus() ;
96                        }
97                },
98                canUndo : false
99        };
100
101        CKEDITOR.plugins.add( 'selection',
102        {
103                init : function( editor )
104                {
105                        editor.on( 'contentDom', function()
106                                {
107                                        var doc = editor.document,
108                                                body = doc.getBody();
109
110                                        if ( CKEDITOR.env.ie )
111                                        {
112                                                // Other browsers don't loose the selection if the
113                                                // editor document loose the focus. In IE, we don't
114                                                // have support for it, so we reproduce it here, other
115                                                // than firing the selection change event.
116
117                                                var savedRange,
118                                                        saveEnabled;
119
120                                                // "onfocusin" is fired before "onfocus". It makes it
121                                                // possible to restore the selection before click
122                                                // events get executed.
123                                                body.on( 'focusin', function( evt )
124                                                        {
125                                                                // If there are elements with layout they fire this event but
126                                                                // it must be ignored to allow edit its contents #4682
127                                                                if ( evt.data.$.srcElement.nodeName != 'BODY' )
128                                                                        return;
129
130                                                                // If we have saved a range, restore it at this
131                                                                // point.
132                                                                if ( savedRange )
133                                                                {
134                                                                        // Well not break because of this.
135                                                                        try
136                                                                        {
137                                                                                savedRange.select();
138                                                                        }
139                                                                        catch (e)
140                                                                        {}
141
142                                                                        savedRange = null;
143                                                                }
144                                                        });
145
146                                                body.on( 'focus', function()
147                                                        {
148                                                                // Enable selections to be saved.
149                                                                saveEnabled = true;
150
151                                                                saveSelection();
152                                                        });
153
154                                                body.on( 'beforedeactivate', function( evt )
155                                                        {
156                                                                // Ignore this event if it's caused by focus switch between
157                                                                // internal editable control type elements, e.g. layouted paragraph. (#4682)
158                                                                if ( evt.data.$.toElement )
159                                                                        return;
160
161                                                                // Disable selections from being saved.
162                                                                saveEnabled = false;
163                                                        });
164
165                                                // IE before version 8 will leave cursor blinking inside the document after
166                                                // editor blurred unless we clean up the selection. (#4716)
167                                                if ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 )
168                                                {
169                                                        doc.getWindow().on( 'blur', function( evt )
170                                                        {
171                                                                editor.document.$.selection.empty();
172                                                        });
173                                                }
174
175                                                // IE fires the "selectionchange" event when clicking
176                                                // inside a selection. We don't want to capture that.
177                                                body.on( 'mousedown', disableSave );
178                                                body.on( 'mouseup',
179                                                        function()
180                                                        {
181                                                                saveEnabled = true;
182                                                                setTimeout( function()
183                                                                        {
184                                                                                saveSelection( true );
185                                                                        },
186                                                                        0 );
187                                                        });
188
189                                                body.on( 'keydown', disableSave );
190                                                body.on( 'keyup',
191                                                        function()
192                                                        {
193                                                                saveEnabled = true;
194                                                                saveSelection();
195                                                        });
196
197
198                                                // IE is the only to provide the "selectionchange"
199                                                // event.
200                                                doc.on( 'selectionchange', saveSelection );
201
202                                                function disableSave()
203                                                {
204                                                        saveEnabled = false;
205                                                }
206
207                                                function saveSelection( testIt )
208                                                {
209                                                        if ( saveEnabled )
210                                                        {
211                                                                var doc = editor.document,
212                                                                        sel = doc && doc.$.selection;
213
214                                                                // There is a very specific case, when clicking
215                                                                // inside a text selection. In that case, the
216                                                                // selection collapses at the clicking point,
217                                                                // but the selection object remains in an
218                                                                // unknown state, making createRange return a
219                                                                // range at the very start of the document. In
220                                                                // such situation we have to test the range, to
221                                                                // be sure it's valid.
222                                                                if ( testIt && sel && sel.type == 'None' )
223                                                                {
224                                                                        // The "InsertImage" command can be used to
225                                                                        // test whether the selection is good or not.
226                                                                        // If not, it's enough to give some time to
227                                                                        // IE to put things in order for us.
228                                                                        if ( !doc.$.queryCommandEnabled( 'InsertImage' ) )
229                                                                        {
230                                                                                CKEDITOR.tools.setTimeout( saveSelection, 50, this, true );
231                                                                                return;
232                                                                        }
233                                                                }
234
235                                                                savedRange = sel && sel.createRange();
236
237                                                                checkSelectionChangeTimeout.call( editor );
238                                                        }
239                                                }
240                                        }
241                                        else
242                                        {
243                                                // In other browsers, we make the selection change
244                                                // check based on other events, like clicks or keys
245                                                // press.
246
247                                                doc.on( 'mouseup', checkSelectionChangeTimeout, editor );
248                                                doc.on( 'keyup', checkSelectionChangeTimeout, editor );
249                                        }
250                                });
251
252                        editor.addCommand( 'selectAll', selectAllCmd );
253                        editor.ui.addButton( 'SelectAll',
254                                {
255                                        label : editor.lang.selectAll,
256                                        command : 'selectAll'
257                                });
258
259                        editor.selectionChange = checkSelectionChangeTimeout;
260                }
261        });
262
263        /**
264         * Gets the current selection from the editing area when in WYSIWYG mode.
265         * @returns {CKEDITOR.dom.selection} A selection object or null if not on
266         *              WYSIWYG mode or no selection is available.
267         * @example
268         * var selection = CKEDITOR.instances.editor1.<b>getSelection()</b>;
269         * alert( selection.getType() );
270         */
271        CKEDITOR.editor.prototype.getSelection = function()
272        {
273                return this.document && this.document.getSelection();
274        };
275
276        CKEDITOR.editor.prototype.forceNextSelectionCheck = function()
277        {
278                delete this._.selectionPreviousPath;
279        };
280
281        /**
282         * Gets the current selection from the document.
283         * @returns {CKEDITOR.dom.selection} A selection object.
284         * @example
285         * var selection = CKEDITOR.instances.editor1.document.<b>getSelection()</b>;
286         * alert( selection.getType() );
287         */
288        CKEDITOR.dom.document.prototype.getSelection = function()
289        {
290                var sel = new CKEDITOR.dom.selection( this );
291                return ( !sel || sel.isInvalid ) ? null : sel;
292        };
293
294        /**
295         * No selection.
296         * @constant
297         * @example
298         * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE )
299         *     alert( 'Nothing is selected' );
300         */
301        CKEDITOR.SELECTION_NONE         = 1;
302
303        /**
304         * Text or collapsed selection.
305         * @constant
306         * @example
307         * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT )
308         *     alert( 'Text is selected' );
309         */
310        CKEDITOR.SELECTION_TEXT         = 2;
311
312        /**
313         * Element selection.
314         * @constant
315         * @example
316         * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT )
317         *     alert( 'An element is selected' );
318         */
319        CKEDITOR.SELECTION_ELEMENT      = 3;
320
321        /**
322         * Manipulates the selection in a DOM document.
323         * @constructor
324         * @example
325         */
326        CKEDITOR.dom.selection = function( document )
327        {
328                var lockedSelection = document.getCustomData( 'cke_locked_selection' );
329
330                if ( lockedSelection )
331                        return lockedSelection;
332
333                this.document = document;
334                this.isLocked = false;
335                this._ =
336                {
337                        cache : {}
338                };
339
340                /**
341                 * IE BUG: The selection's document may be a different document than the
342                 * editor document. Return null if that's the case.
343                 */
344                if ( CKEDITOR.env.ie )
345                {
346                        var range = this.getNative().createRange();
347                        if ( !range
348                                || ( range.item && range.item(0).ownerDocument != this.document.$ )
349                                || ( range.parentElement && range.parentElement().ownerDocument != this.document.$ ) )
350                        {
351                                this.isInvalid = true;
352                        }
353                }
354
355                return this;
356        };
357
358        var styleObjectElements =
359        {
360                img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,
361                a:1, input:1, form:1, select:1, textarea:1, button:1, fieldset:1, th:1, thead:1, tfoot:1
362        };
363
364        CKEDITOR.dom.selection.prototype =
365        {
366                /**
367                 * Gets the native selection object from the browser.
368                 * @function
369                 * @returns {Object} The native selection object.
370                 * @example
371                 * var selection = editor.getSelection().<b>getNative()</b>;
372                 */
373                getNative :
374                        CKEDITOR.env.ie ?
375                                function()
376                                {
377                                        return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.$.selection );
378                                }
379                        :
380                                function()
381                                {
382                                        return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.getWindow().$.getSelection() );
383                                },
384
385                /**
386                 * Gets the type of the current selection. The following values are
387                 * available:
388                 * <ul>
389                 *              <li>{@link CKEDITOR.SELECTION_NONE} (1): No selection.</li>
390                 *              <li>{@link CKEDITOR.SELECTION_TEXT} (2): Text is selected or
391                 *                      collapsed selection.</li>
392                 *              <li>{@link CKEDITOR.SELECTION_ELEMENT} (3): A element
393                 *                      selection.</li>
394                 * </ul>
395                 * @function
396                 * @returns {Number} One of the following constant values:
397                 *              {@link CKEDITOR.SELECTION_NONE}, {@link CKEDITOR.SELECTION_TEXT} or
398                 *              {@link CKEDITOR.SELECTION_ELEMENT}.
399                 * @example
400                 * if ( editor.getSelection().<b>getType()</b> == CKEDITOR.SELECTION_TEXT )
401                 *     alert( 'Text is selected' );
402                 */
403                getType :
404                        CKEDITOR.env.ie ?
405                                function()
406                                {
407                                        var cache = this._.cache;
408                                        if ( cache.type )
409                                                return cache.type;
410
411                                        var type = CKEDITOR.SELECTION_NONE;
412
413                                        try
414                                        {
415                                                var sel = this.getNative(),
416                                                        ieType = sel.type;
417
418                                                if ( ieType == 'Text' )
419                                                        type = CKEDITOR.SELECTION_TEXT;
420
421                                                if ( ieType == 'Control' )
422                                                        type = CKEDITOR.SELECTION_ELEMENT;
423
424                                                // It is possible that we can still get a text range
425                                                // object even when type == 'None' is returned by IE.
426                                                // So we'd better check the object returned by
427                                                // createRange() rather than by looking at the type.
428                                                if ( sel.createRange().parentElement )
429                                                        type = CKEDITOR.SELECTION_TEXT;
430                                        }
431                                        catch(e) {}
432
433                                        return ( cache.type = type );
434                                }
435                        :
436                                function()
437                                {
438                                        var cache = this._.cache;
439                                        if ( cache.type )
440                                                return cache.type;
441
442                                        var type = CKEDITOR.SELECTION_TEXT;
443
444                                        var sel = this.getNative();
445
446                                        if ( !sel )
447                                                type = CKEDITOR.SELECTION_NONE;
448                                        else if ( sel.rangeCount == 1 )
449                                        {
450                                                // Check if the actual selection is a control (IMG,
451                                                // TABLE, HR, etc...).
452
453                                                var range = sel.getRangeAt(0),
454                                                        startContainer = range.startContainer;
455
456                                                if ( startContainer == range.endContainer
457                                                        && startContainer.nodeType == 1
458                                                        && ( range.endOffset - range.startOffset ) == 1
459                                                        && styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] )
460                                                {
461                                                        type = CKEDITOR.SELECTION_ELEMENT;
462                                                }
463                                        }
464
465                                        return ( cache.type = type );
466                                },
467
468                getRanges :
469                        CKEDITOR.env.ie ?
470                                ( function()
471                                {
472                                        // Finds the container and offset for a specific boundary
473                                        // of an IE range.
474                                        var getBoundaryInformation = function( range, start )
475                                        {
476                                                // Creates a collapsed range at the requested boundary.
477                                                range = range.duplicate();
478                                                range.collapse( start );
479
480                                                // Gets the element that encloses the range entirely.
481                                                var parent = range.parentElement();
482                                                var siblings = parent.childNodes;
483
484                                                var testRange;
485
486                                                for ( var i = 0 ; i < siblings.length ; i++ )
487                                                {
488                                                        var child = siblings[ i ];
489                                                        if ( child.nodeType == 1 )
490                                                        {
491                                                                testRange = range.duplicate();
492
493                                                                testRange.moveToElementText( child );
494
495                                                                var comparisonStart = testRange.compareEndPoints( 'StartToStart', range ),
496                                                                        comparisonEnd = testRange.compareEndPoints( 'EndToStart', range );
497
498                                                                testRange.collapse();
499
500                                                                if ( comparisonStart > 0 )
501                                                                        break;
502                                                                // When selection stay at the side of certain self-closing elements, e.g. BR,
503                                                                // our comparison will never shows an equality. (#4824)
504                                                                else if ( !comparisonStart
505                                                                        || comparisonEnd == 1 && comparisonStart == -1 )
506                                                                        return { container : parent, offset : i };
507                                                                else if ( !comparisonEnd )
508                                                                        return { container : parent, offset : i + 1 };
509
510                                                                testRange = null;
511                                                        }
512                                                }
513
514                                                if ( !testRange )
515                                                {
516                                                        testRange = range.duplicate();
517                                                        testRange.moveToElementText( parent );
518                                                        testRange.collapse( false );
519                                                }
520
521                                                testRange.setEndPoint( 'StartToStart', range );
522                                                // IE report line break as CRLF with range.text but
523                                                // only LF with textnode.nodeValue, normalize them to avoid
524                                                // breaking character counting logic below. (#3949)
525                                                var distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
526
527                                                try
528                                                {
529                                                        while ( distance > 0 )
530                                                                distance -= siblings[ --i ].nodeValue.length;
531                                                }
532                                                // Measurement in IE could be somtimes wrong because of <select> element. (#4611)
533                                                catch( e )
534                                                {
535                                                        distance = 0;
536                                                }
537
538
539                                                if ( distance === 0 )
540                                                {
541                                                        return {
542                                                                container : parent,
543                                                                offset : i
544                                                        };
545                                                }
546                                                else
547                                                {
548                                                        return {
549                                                                container : siblings[ i ],
550                                                                offset : -distance
551                                                        };
552                                                }
553                                        };
554
555                                        return function()
556                                        {
557                                                var cache = this._.cache;
558                                                if ( cache.ranges )
559                                                        return cache.ranges;
560
561                                                // IE doesn't have range support (in the W3C way), so we
562                                                // need to do some magic to transform selections into
563                                                // CKEDITOR.dom.range instances.
564
565                                                var sel = this.getNative(),
566                                                        nativeRange = sel && sel.createRange(),
567                                                        type = this.getType(),
568                                                        range;
569
570                                                if ( !sel )
571                                                        return [];
572
573                                                if ( type == CKEDITOR.SELECTION_TEXT )
574                                                {
575                                                        range = new CKEDITOR.dom.range( this.document );
576
577                                                        var boundaryInfo = getBoundaryInformation( nativeRange, true );
578                                                        range.setStart( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
579
580                                                        boundaryInfo = getBoundaryInformation( nativeRange );
581                                                        range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
582
583                                                        return ( cache.ranges = [ range ] );
584                                                }
585                                                else if ( type == CKEDITOR.SELECTION_ELEMENT )
586                                                {
587                                                        var retval = this._.cache.ranges = [];
588
589                                                        for ( var i = 0 ; i < nativeRange.length ; i++ )
590                                                        {
591                                                                var element = nativeRange.item( i ),
592                                                                        parentElement = element.parentNode,
593                                                                        j = 0;
594
595                                                                range = new CKEDITOR.dom.range( this.document );
596
597                                                                for (; j < parentElement.childNodes.length && parentElement.childNodes[j] != element ; j++ )
598                                                                { /*jsl:pass*/ }
599
600                                                                range.setStart( new CKEDITOR.dom.node( parentElement ), j );
601                                                                range.setEnd( new CKEDITOR.dom.node( parentElement ), j + 1 );
602                                                                retval.push( range );
603                                                        }
604
605                                                        return retval;
606                                                }
607
608                                                return ( cache.ranges = [] );
609                                        };
610                                })()
611                        :
612                                function()
613                                {
614                                        var cache = this._.cache;
615                                        if ( cache.ranges )
616                                                return cache.ranges;
617
618                                        // On browsers implementing the W3C range, we simply
619                                        // tranform the native ranges in CKEDITOR.dom.range
620                                        // instances.
621
622                                        var ranges = [];
623                                        var sel = this.getNative();
624
625                                        if ( !sel )
626                                                return [];
627
628                                        for ( var i = 0 ; i < sel.rangeCount ; i++ )
629                                        {
630                                                var nativeRange = sel.getRangeAt( i );
631                                                var range = new CKEDITOR.dom.range( this.document );
632
633                                                range.setStart( new CKEDITOR.dom.node( nativeRange.startContainer ), nativeRange.startOffset );
634                                                range.setEnd( new CKEDITOR.dom.node( nativeRange.endContainer ), nativeRange.endOffset );
635                                                ranges.push( range );
636                                        }
637
638                                        return ( cache.ranges = ranges );
639                                },
640
641                /**
642                 * Gets the DOM element in which the selection starts.
643                 * @returns {CKEDITOR.dom.element} The element at the beginning of the
644                 *              selection.
645                 * @example
646                 * var element = editor.getSelection().<b>getStartElement()</b>;
647                 * alert( element.getName() );
648                 */
649                getStartElement : function()
650                {
651                        var cache = this._.cache;
652                        if ( cache.startElement !== undefined )
653                                return cache.startElement;
654
655                        var node,
656                                sel = this.getNative();
657
658                        switch ( this.getType() )
659                        {
660                                case CKEDITOR.SELECTION_ELEMENT :
661                                        return this.getSelectedElement();
662
663                                case CKEDITOR.SELECTION_TEXT :
664
665                                        var range = this.getRanges()[0];
666
667                                        if ( range )
668                                        {
669                                                if ( !range.collapsed )
670                                                {
671                                                        range.optimize();
672
673                                                        // Decrease the range content to exclude particial
674                                                        // selected node on the start which doesn't have
675                                                        // visual impact. ( #3231 )
676                                                        while ( true )
677                                                        {
678                                                                var startContainer = range.startContainer,
679                                                                        startOffset = range.startOffset;
680                                                                // Limit the fix only to non-block elements.(#3950)
681                                                                if ( startOffset == ( startContainer.getChildCount ?
682                                                                         startContainer.getChildCount() : startContainer.getLength() )
683                                                                         && !startContainer.isBlockBoundary() )
684                                                                        range.setStartAfter( startContainer );
685                                                                else break;
686                                                        }
687
688                                                        node = range.startContainer;
689
690                                                        if ( node.type != CKEDITOR.NODE_ELEMENT )
691                                                                return node.getParent();
692
693                                                        node = node.getChild( range.startOffset );
694
695                                                        if ( !node || node.type != CKEDITOR.NODE_ELEMENT )
696                                                                return range.startContainer;
697
698                                                        var child = node.getFirst();
699                                                        while (  child && child.type == CKEDITOR.NODE_ELEMENT )
700                                                        {
701                                                                node = child;
702                                                                child = child.getFirst();
703                                                        }
704
705                                                        return node;
706                                                }
707                                        }
708
709                                        if ( CKEDITOR.env.ie )
710                                        {
711                                                range = sel.createRange();
712                                                range.collapse( true );
713
714                                                node = range.parentElement();
715                                        }
716                                        else
717                                        {
718                                                node = sel.anchorNode;
719
720                                                if ( node && node.nodeType != 1 )
721                                                        node = node.parentNode;
722                                        }
723                        }
724
725                        return cache.startElement = ( node ? new CKEDITOR.dom.element( node ) : null );
726                },
727
728                /**
729                 * Gets the current selected element.
730                 * @returns {CKEDITOR.dom.element} The selected element. Null if no
731                 *              selection is available or the selection type is not
732                 *              {@link CKEDITOR.SELECTION_ELEMENT}.
733                 * @example
734                 * var element = editor.getSelection().<b>getSelectedElement()</b>;
735                 * alert( element.getName() );
736                 */
737                getSelectedElement : function()
738                {
739                        var cache = this._.cache;
740                        if ( cache.selectedElement !== undefined )
741                                return cache.selectedElement;
742
743                        var self = this;
744
745                        var node = CKEDITOR.tools.tryThese(
746                                // Is it native IE control type selection?
747                                function()
748                                {
749                                        return self.getNative().createRange().item( 0 );
750                                },
751                                // Figure it out by checking if there's a single enclosed
752                                // node of the range.
753                                function()
754                                {
755                                        var range  = self.getRanges()[ 0 ];
756                                        range.shrink( CKEDITOR.SHRINK_ELEMENT );
757
758                                        var enclosed;
759                                        if ( range.startContainer.equals( range.endContainer )
760                                                && ( range.endOffset - range.startOffset ) == 1
761                                                && styleObjectElements[ ( enclosed = range.startContainer.getChild( range.startOffset ) ).getName() ] )
762                                        {
763                                                return enclosed.$;
764                                        }
765                                });
766
767                        return cache.selectedElement = ( node ? new CKEDITOR.dom.element( node ) : null );
768                },
769
770                lock : function()
771                {
772                        // Call all cacheable function.
773                        this.getRanges();
774                        this.getStartElement();
775                        this.getSelectedElement();
776
777                        // The native selection is not available when locked.
778                        this._.cache.nativeSel = {};
779
780                        this.isLocked = true;
781
782                        // Save this selection inside the DOM document.
783                        this.document.setCustomData( 'cke_locked_selection', this );
784                },
785
786                unlock : function( restore )
787                {
788                        var doc = this.document,
789                                lockedSelection = doc.getCustomData( 'cke_locked_selection' );
790
791                        if ( lockedSelection )
792                        {
793                                doc.setCustomData( 'cke_locked_selection', null );
794
795                                if ( restore )
796                                {
797                                        var selectedElement = lockedSelection.getSelectedElement(),
798                                                ranges = !selectedElement && lockedSelection.getRanges();
799
800                                        this.isLocked = false;
801                                        this.reset();
802
803                                        doc.getBody().focus();
804
805                                        if ( selectedElement )
806                                                this.selectElement( selectedElement );
807                                        else
808                                                this.selectRanges( ranges );
809                                }
810                        }
811
812                        if  ( !lockedSelection || !restore )
813                        {
814                                this.isLocked = false;
815                                this.reset();
816                        }
817                },
818
819                reset : function()
820                {
821                        this._.cache = {};
822                },
823
824                selectElement : function( element )
825                {
826                        if ( this.isLocked )
827                        {
828                                var range = new CKEDITOR.dom.range( this.document );
829                                range.setStartBefore( element );
830                                range.setEndAfter( element );
831
832                                this._.cache.selectedElement = element;
833                                this._.cache.startElement = element;
834                                this._.cache.ranges = [ range ];
835                                this._.cache.type = CKEDITOR.SELECTION_ELEMENT;
836
837                                return;
838                        }
839
840                        if ( CKEDITOR.env.ie )
841                        {
842                                this.getNative().empty();
843
844                                try
845                                {
846                                        // Try to select the node as a control.
847                                        range = this.document.$.body.createControlRange();
848                                        range.addElement( element.$ );
849                                        range.select();
850                                }
851                                catch(e)
852                                {
853                                        // If failed, select it as a text range.
854                                        range = this.document.$.body.createTextRange();
855                                        range.moveToElementText( element.$ );
856                                        range.select();
857                                }
858                                finally
859                                {
860                                        this.document.fire( 'selectionchange' );
861                                }
862
863                                this.reset();
864                        }
865                        else
866                        {
867                                // Create the range for the element.
868                                range = this.document.$.createRange();
869                                range.selectNode( element.$ );
870
871                                // Select the range.
872                                var sel = this.getNative();
873                                sel.removeAllRanges();
874                                sel.addRange( range );
875
876                                this.reset();
877                        }
878                },
879
880                selectRanges : function( ranges )
881                {
882                        if ( this.isLocked )
883                        {
884                                this._.cache.selectedElement = null;
885                                this._.cache.startElement = ranges[ 0 ].getTouchedStartNode();
886                                this._.cache.ranges = ranges;
887                                this._.cache.type = CKEDITOR.SELECTION_TEXT;
888
889                                return;
890                        }
891
892                        if ( CKEDITOR.env.ie )
893                        {
894                                // IE doesn't accept multiple ranges selection, so we just
895                                // select the first one.
896                                if ( ranges[ 0 ] )
897                                        ranges[ 0 ].select();
898
899                                this.reset();
900                        }
901                        else
902                        {
903                                var sel = this.getNative();
904                                sel.removeAllRanges();
905
906                                for ( var i = 0 ; i < ranges.length ; i++ )
907                                {
908                                        var range = ranges[ i ];
909                                        var nativeRange = this.document.$.createRange();
910                                        var startContainer = range.startContainer;
911
912                                        // In FF2, if we have a collapsed range, inside an empty
913                                        // element, we must add something to it otherwise the caret
914                                        // will not be visible.
915                                        if ( range.collapsed &&
916                                                ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) &&
917                                                startContainer.type == CKEDITOR.NODE_ELEMENT &&
918                                                !startContainer.getChildCount() )
919                                        {
920                                                startContainer.appendText( '' );
921                                        }
922
923                                        nativeRange.setStart( startContainer.$, range.startOffset );
924                                        nativeRange.setEnd( range.endContainer.$, range.endOffset );
925
926                                        // Select the range.
927                                        sel.addRange( nativeRange );
928                                }
929
930                                this.reset();
931                        }
932                },
933
934                createBookmarks : function( serializable )
935                {
936                        var retval = [],
937                                ranges = this.getRanges(),
938                                length = ranges.length,
939                                bookmark;
940                        for ( var i = 0; i < length ; i++ )
941                        {
942                            retval.push( bookmark = ranges[ i ].createBookmark( serializable, true ) );
943
944                                serializable = bookmark.serializable;
945
946                                var bookmarkStart = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
947                                        bookmarkEnd = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
948
949                            // Updating the offset values for rest of ranges which have been mangled(#3256).
950                            for ( var j = i + 1 ; j < length ; j++ )
951                            {
952                                var dirtyRange = ranges[ j ],
953                                       rangeStart = dirtyRange.startContainer,
954                                       rangeEnd = dirtyRange.endContainer;
955
956                               rangeStart.equals( bookmarkStart.getParent() ) && dirtyRange.startOffset++;
957                               rangeStart.equals( bookmarkEnd.getParent() ) && dirtyRange.startOffset++;
958                               rangeEnd.equals( bookmarkStart.getParent() ) && dirtyRange.endOffset++;
959                               rangeEnd.equals( bookmarkEnd.getParent() ) && dirtyRange.endOffset++;
960                            }
961                        }
962
963                        return retval;
964                },
965
966                createBookmarks2 : function( normalized )
967                {
968                        var bookmarks = [],
969                                ranges = this.getRanges();
970
971                        for ( var i = 0 ; i < ranges.length ; i++ )
972                                bookmarks.push( ranges[i].createBookmark2( normalized ) );
973
974                        return bookmarks;
975                },
976
977                selectBookmarks : function( bookmarks )
978                {
979                        var ranges = [];
980                        for ( var i = 0 ; i < bookmarks.length ; i++ )
981                        {
982                                var range = new CKEDITOR.dom.range( this.document );
983                                range.moveToBookmark( bookmarks[i] );
984                                ranges.push( range );
985                        }
986                        this.selectRanges( ranges );
987                        return this;
988                },
989
990                getCommonAncestor : function()
991                {
992                        var ranges = this.getRanges(),
993                                startNode = ranges[ 0 ].startContainer,
994                                endNode = ranges[ ranges.length - 1 ].endContainer;
995                        return startNode.getCommonAncestor( endNode );
996                },
997
998                // Moving scroll bar to the current selection's start position.
999                scrollIntoView : function()
1000                {
1001                        // If we have split the block, adds a temporary span at the
1002                        // range position and scroll relatively to it.
1003                        var start = this.getStartElement();
1004                        start.scrollIntoView();
1005                }
1006        };
1007})();
1008( function()
1009{
1010var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true );
1011var fillerTextRegex = /\ufeff|\u00a0/;
1012
1013CKEDITOR.dom.range.prototype.select =
1014        CKEDITOR.env.ie ?
1015                // V2
1016                function( forceExpand )
1017                {
1018                        var collapsed = this.collapsed;
1019                        var isStartMarkerAlone;
1020                        var dummySpan;
1021
1022                        var bookmark = this.createBookmark();
1023
1024                        // Create marker tags for the start and end boundaries.
1025                        var startNode = bookmark.startNode;
1026
1027                        var endNode;
1028                        if ( !collapsed )
1029                                endNode = bookmark.endNode;
1030
1031                        // Create the main range which will be used for the selection.
1032                        var ieRange = this.document.$.body.createTextRange();
1033
1034                        // Position the range at the start boundary.
1035                        ieRange.moveToElementText( startNode.$ );
1036                        ieRange.moveStart( 'character', 1 );
1037
1038                        if ( endNode )
1039                        {
1040                                // Create a tool range for the end.
1041                                var ieRangeEnd = this.document.$.body.createTextRange();
1042
1043                                // Position the tool range at the end.
1044                                ieRangeEnd.moveToElementText( endNode.$ );
1045
1046                                // Move the end boundary of the main range to match the tool range.
1047                                ieRange.setEndPoint( 'EndToEnd', ieRangeEnd );
1048                                ieRange.moveEnd( 'character', -1 );
1049                        }
1050                        else
1051                        {
1052                                // The isStartMarkerAlone logic comes from V2. It guarantees that the lines
1053                                // will expand and that the cursor will be blinking on the right place.
1054                                // Actually, we are using this flag just to avoid using this hack in all
1055                                // situations, but just on those needed.
1056                                var next = startNode.getNext( notWhitespaces );
1057                                isStartMarkerAlone = ( !( next && next.getText && next.getText().match( fillerTextRegex ) )     // already a filler there?
1058                                                                          && ( forceExpand || !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) ) );
1059
1060                                // Append a temporary <span>&#65279;</span> before the selection.
1061                                // This is needed to avoid IE destroying selections inside empty
1062                                // inline elements, like <b></b> (#253).
1063                                // It is also needed when placing the selection right after an inline
1064                                // element to avoid the selection moving inside of it.
1065                                dummySpan = this.document.createElement( 'span' );
1066                                dummySpan.setHtml( '&#65279;' );        // Zero Width No-Break Space (U+FEFF). See #1359.
1067                                dummySpan.insertBefore( startNode );
1068
1069                                if ( isStartMarkerAlone )
1070                                {
1071                                        // To expand empty blocks or line spaces after <br>, we need
1072                                        // instead to have any char, which will be later deleted using the
1073                                        // selection.
1074                                        // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359)
1075                                        this.document.createText( '\ufeff' ).insertBefore( startNode );
1076                                }
1077                        }
1078
1079                        // Remove the markers (reset the position, because of the changes in the DOM tree).
1080                        this.setStartBefore( startNode );
1081                        startNode.remove();
1082
1083                        if ( collapsed )
1084                        {
1085                                if ( isStartMarkerAlone )
1086                                {
1087                                        // Move the selection start to include the temporary \ufeff.
1088                                        ieRange.moveStart( 'character', -1 );
1089
1090                                        ieRange.select();
1091
1092                                        // Remove our temporary stuff.
1093                                        this.document.$.selection.clear();
1094                                }
1095                                else
1096                                        ieRange.select();
1097
1098                                this.moveToPosition( dummySpan, CKEDITOR.POSITION_BEFORE_START );
1099                                dummySpan.remove();
1100                        }
1101                        else
1102                        {
1103                                this.setEndBefore( endNode );
1104                                endNode.remove();
1105                                ieRange.select();
1106                        }
1107
1108                        this.document.fire( 'selectionchange' );
1109                }
1110        :
1111                function()
1112                {
1113                        var startContainer = this.startContainer;
1114
1115                        // If we have a collapsed range, inside an empty element, we must add
1116                        // something to it, otherwise the caret will not be visible.
1117                        if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() )
1118                                startContainer.append( new CKEDITOR.dom.text( '' ) );
1119
1120                        var nativeRange = this.document.$.createRange();
1121                        nativeRange.setStart( startContainer.$, this.startOffset );
1122
1123                        try
1124                        {
1125                                nativeRange.setEnd( this.endContainer.$, this.endOffset );
1126                        }
1127                        catch ( e )
1128                        {
1129                                // There is a bug in Firefox implementation (it would be too easy
1130                                // otherwise). The new start can't be after the end (W3C says it can).
1131                                // So, let's create a new range and collapse it to the desired point.
1132                                if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 )
1133                                {
1134                                        this.collapse( true );
1135                                        nativeRange.setEnd( this.endContainer.$, this.endOffset );
1136                                }
1137                                else
1138                                        throw( e );
1139                        }
1140
1141                        var selection = this.document.getSelection().getNative();
1142                        selection.removeAllRanges();
1143                        selection.addRange( nativeRange );
1144                };
1145} )();
Note: See TracBrowser for help on using the repository browser.