JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.3.2
[ckeditor.git] / _source / plugins / selection / plugin.js
index 04be631..3d707fc 100644 (file)
@@ -17,7 +17,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        // In IE, the "selectionchange" event may still get thrown when\r
                        // releasing the WYSIWYG mode, so we need to check it first.\r
                        var sel = this.getSelection();\r
-                       if ( !sel )\r
+                       if ( !sel || !sel.document.getWindow().$ )\r
                                return;\r
 \r
                        var firstElement = sel.getStartElement();\r
@@ -105,7 +105,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        editor.on( 'contentDom', function()\r
                                {\r
                                        var doc = editor.document,\r
-                                               body = doc.getBody();\r
+                                               body = doc.getBody(),\r
+                                               html = doc.getDocumentElement();\r
 \r
                                        if ( CKEDITOR.env.ie )\r
                                        {\r
@@ -115,7 +116,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                                                // than firing the selection change event.\r
 \r
                                                var savedRange,\r
-                                                       saveEnabled;\r
+                                                       saveEnabled,\r
+                                                       restoreEnabled = 1;\r
 \r
                                                // "onfocusin" is fired before "onfocus". It makes it\r
                                                // possible to restore the selection before click\r
@@ -131,13 +133,16 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                                                                // point.\r
                                                                if ( savedRange )\r
                                                                {\r
-                                                                       // Well not break because of this.\r
-                                                                       try\r
+                                                                       if ( restoreEnabled )\r
                                                                        {\r
-                                                                               savedRange.select();\r
+                                                                               // Well not break because of this.\r
+                                                                               try\r
+                                                                               {\r
+                                                                                       savedRange.select();\r
+                                                                               }\r
+                                                                               catch (e)\r
+                                                                               {}\r
                                                                        }\r
-                                                                       catch (e)\r
-                                                                       {}\r
 \r
                                                                        savedRange = null;\r
                                                                }\r
@@ -160,21 +165,55 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 \r
                                                                // Disable selections from being saved.\r
                                                                saveEnabled = false;\r
+                                                               restoreEnabled = 1;\r
                                                        });\r
 \r
                                                // IE before version 8 will leave cursor blinking inside the document after\r
                                                // editor blurred unless we clean up the selection. (#4716)\r
                                                if ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 )\r
                                                {\r
-                                                       doc.getWindow().on( 'blur', function( evt )\r
+                                                       editor.on( 'blur', function( evt )\r
                                                        {\r
-                                                               editor.document.$.selection.empty();\r
+                                                               editor.document && editor.document.$.selection.empty();\r
+                                                       });\r
+                                               }\r
+\r
+                                               // Listening on document element ensures that\r
+                                               // scrollbar is included. (#5280)\r
+                                               html.on( 'mousedown', function ()\r
+                                               {\r
+                                                       // Lock restore selection now, as we have\r
+                                                       // a followed 'click' event which introduce\r
+                                                       // new selection. (#5735)\r
+                                                       restoreEnabled = 0;\r
+                                               });\r
+\r
+                                               html.on( 'mouseup', function ()\r
+                                               {\r
+                                                       restoreEnabled = 1;\r
+                                               });\r
+\r
+                                               // In IE6/7 the blinking cursor appears, but contents are\r
+                                               // not editable. (#5634)\r
+                                               if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.version < 8 || CKEDITOR.env.quirks ) )\r
+                                               {\r
+                                                       // The 'click' event is not fired when clicking the\r
+                                                       // scrollbars, so we can use it to check whether\r
+                                                       // the empty space following <body> has been clicked.\r
+                                                       html.on( 'click', function( evt )\r
+                                                       {\r
+                                                               if ( evt.data.getTarget().getName() == 'html' )\r
+                                                                       editor.getSelection().getRanges()[ 0 ].select();\r
                                                        });\r
                                                }\r
 \r
                                                // IE fires the "selectionchange" event when clicking\r
                                                // inside a selection. We don't want to capture that.\r
-                                               body.on( 'mousedown', disableSave );\r
+                                               body.on( 'mousedown', function ()\r
+                                               {\r
+                                                       disableSave();\r
+                                               });\r
+\r
                                                body.on( 'mouseup',\r
                                                        function()\r
                                                        {\r
@@ -235,9 +274,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 \r
                                                                // Avoid saving selection from within text input. (#5747)\r
                                                                var parentTag;\r
-                                                               if ( nativeSel.type == 'Text'\r
-                                                                       && ( parentTag = nativeSel.createRange().parentElement().nodeName.toLowerCase() )\r
-                                                                       && parentTag in { input: 1, textarea : 1 } )\r
+                                                               if ( nativeSel && nativeSel.type && nativeSel.type != 'Control'\r
+                                                                       && ( parentTag = nativeSel.createRange() )\r
+                                                                       && ( parentTag = parentTag.parentElement() )\r
+                                                                       && ( parentTag = parentTag.nodeName )\r
+                                                                       && parentTag.toLowerCase() in { input: 1, textarea : 1 } )\r
                                                                {\r
                                                                        return;\r
                                                                }\r
@@ -475,6 +516,15 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                                        return ( cache.type = type );\r
                                },\r
 \r
+               /**\r
+                * Retrieve the {@link CKEDITOR.dom.range} instances that represent the current selection.\r
+                * Note: Some browsers returns multiple ranges even on a sequent selection, e.g. Firefox returns\r
+                * one range for each table cell when one or more table row is selected.\r
+                * @return {Array}\r
+                * @example\r
+                * var ranges = selection.getRanges();\r
+                * alert(ranges.length);\r
+                */\r
                getRanges :\r
                        CKEDITOR.env.ie ?\r
                                ( function()\r
@@ -836,6 +886,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        this._.cache = {};\r
                },\r
 \r
+               /**\r
+                *  Make the current selection of type {@link CKEDITOR.SELECTION_ELEMENT} by enclosing the specified element.\r
+                * @param element\r
+                */\r
                selectElement : function( element )\r
                {\r
                        if ( this.isLocked )\r
@@ -892,6 +946,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        }\r
                },\r
 \r
+               /**\r
+                *  Adding the specified ranges to document selection preceding\r
+                * by clearing up the original selection.\r
+                * @param {CKEDITOR.dom.range} ranges\r
+                */\r
                selectRanges : function( ranges )\r
                {\r
                        if ( this.isLocked )\r
@@ -946,6 +1005,12 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        }\r
                },\r
 \r
+               /**\r
+                *  Create bookmark for every single of this selection range (from #getRanges)\r
+                * by calling the {@link CKEDITOR.dom.range.prototype.createBookmark} method,\r
+                * with extra cares to avoid interferon among those ranges. Same arguments are\r
+                * received as with the underlay range method.\r
+                */\r
                createBookmarks : function( serializable )\r
                {\r
                        var retval = [],\r
@@ -978,6 +1043,12 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        return retval;\r
                },\r
 \r
+               /**\r
+                *  Create bookmark for every single of this selection range (from #getRanges)\r
+                * by calling the {@link CKEDITOR.dom.range.prototype.createBookmark2} method,\r
+                * with extra cares to avoid interferon among those ranges. Same arguments are\r
+                * received as with the underlay range method.\r
+                */\r
                createBookmarks2 : function( normalized )\r
                {\r
                        var bookmarks = [],\r
@@ -989,6 +1060,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        return bookmarks;\r
                },\r
 \r
+               /**\r
+                * Select the virtual ranges denote by the bookmarks by calling #selectRanges.\r
+                * @param bookmarks\r
+                */\r
                selectBookmarks : function( bookmarks )\r
                {\r
                        var ranges = [];\r
@@ -1002,6 +1077,9 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        return this;\r
                },\r
 \r
+               /**\r
+                * Retrieve the common ancestor node of the first range and the last range.\r
+                */\r
                getCommonAncestor : function()\r
                {\r
                        var ranges = this.getRanges(),\r
@@ -1010,7 +1088,9 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        return startNode.getCommonAncestor( endNode );\r
                },\r
 \r
-               // Moving scroll bar to the current selection's start position.\r
+               /**\r
+                * Moving scroll bar to the current selection's start position.\r
+                */\r
                scrollIntoView : function()\r
                {\r
                        // If we have split the block, adds a temporary span at the\r
@@ -1020,150 +1100,151 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                }\r
        };\r
 })();\r
+\r
 ( function()\r
 {\r
-var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true );\r
-var fillerTextRegex = /\ufeff|\u00a0/;\r
-var nonCells = { table:1,tbody:1,tr:1 };\r
-\r
-CKEDITOR.dom.range.prototype.select =\r
-       CKEDITOR.env.ie ?\r
-               // V2\r
-               function( forceExpand )\r
-               {\r
-                       var collapsed = this.collapsed;\r
-                       var isStartMarkerAlone;\r
-                       var dummySpan;\r
-\r
-                       // IE doesn't support selecting the entire table row/cell, move the selection into cells, e.g.\r
-                       // <table><tbody><tr>[<td>cell</b></td>... => <table><tbody><tr><td>[cell</td>...\r
-                       if ( this.startContainer.type == CKEDITOR.NODE_ELEMENT && this.startContainer.getName() in nonCells\r
-                               || this.endContainer.type == CKEDITOR.NODE_ELEMENT && this.endContainer.getName() in nonCells )\r
+       var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),\r
+                       fillerTextRegex = /\ufeff|\u00a0/,\r
+                       nonCells = { table:1,tbody:1,tr:1 };\r
+\r
+       CKEDITOR.dom.range.prototype.select =\r
+               CKEDITOR.env.ie ?\r
+                       // V2\r
+                       function( forceExpand )\r
                        {\r
-                               this.shrink( CKEDITOR.NODE_ELEMENT, true );\r
-                       }\r
+                               var collapsed = this.collapsed;\r
+                               var isStartMarkerAlone;\r
+                               var dummySpan;\r
+\r
+                               // IE doesn't support selecting the entire table row/cell, move the selection into cells, e.g.\r
+                               // <table><tbody><tr>[<td>cell</b></td>... => <table><tbody><tr><td>[cell</td>...\r
+                               if ( this.startContainer.type == CKEDITOR.NODE_ELEMENT && this.startContainer.getName() in nonCells\r
+                                       || this.endContainer.type == CKEDITOR.NODE_ELEMENT && this.endContainer.getName() in nonCells )\r
+                               {\r
+                                       this.shrink( CKEDITOR.NODE_ELEMENT, true );\r
+                               }\r
 \r
-                       var bookmark = this.createBookmark();\r
+                               var bookmark = this.createBookmark();\r
 \r
-                       // Create marker tags for the start and end boundaries.\r
-                       var startNode = bookmark.startNode;\r
+                               // Create marker tags for the start and end boundaries.\r
+                               var startNode = bookmark.startNode;\r
 \r
-                       var endNode;\r
-                       if ( !collapsed )\r
-                               endNode = bookmark.endNode;\r
+                               var endNode;\r
+                               if ( !collapsed )\r
+                                       endNode = bookmark.endNode;\r
 \r
-                       // Create the main range which will be used for the selection.\r
-                       var ieRange = this.document.$.body.createTextRange();\r
+                               // Create the main range which will be used for the selection.\r
+                               var ieRange = this.document.$.body.createTextRange();\r
 \r
-                       // Position the range at the start boundary.\r
-                       ieRange.moveToElementText( startNode.$ );\r
-                       ieRange.moveStart( 'character', 1 );\r
+                               // Position the range at the start boundary.\r
+                               ieRange.moveToElementText( startNode.$ );\r
+                               ieRange.moveStart( 'character', 1 );\r
 \r
-                       if ( endNode )\r
-                       {\r
-                               // Create a tool range for the end.\r
-                               var ieRangeEnd = this.document.$.body.createTextRange();\r
+                               if ( endNode )\r
+                               {\r
+                                       // Create a tool range for the end.\r
+                                       var ieRangeEnd = this.document.$.body.createTextRange();\r
 \r
-                               // Position the tool range at the end.\r
-                               ieRangeEnd.moveToElementText( endNode.$ );\r
+                                       // Position the tool range at the end.\r
+                                       ieRangeEnd.moveToElementText( endNode.$ );\r
 \r
-                               // Move the end boundary of the main range to match the tool range.\r
-                               ieRange.setEndPoint( 'EndToEnd', ieRangeEnd );\r
-                               ieRange.moveEnd( 'character', -1 );\r
-                       }\r
-                       else\r
-                       {\r
-                               // The isStartMarkerAlone logic comes from V2. It guarantees that the lines\r
-                               // will expand and that the cursor will be blinking on the right place.\r
-                               // Actually, we are using this flag just to avoid using this hack in all\r
-                               // situations, but just on those needed.\r
-                               var next = startNode.getNext( notWhitespaces );\r
-                               isStartMarkerAlone = ( !( next && next.getText && next.getText().match( fillerTextRegex ) )     // already a filler there?\r
-                                                                         && ( forceExpand || !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) ) );\r
-\r
-                               // Append a temporary <span>&#65279;</span> before the selection.\r
-                               // This is needed to avoid IE destroying selections inside empty\r
-                               // inline elements, like <b></b> (#253).\r
-                               // It is also needed when placing the selection right after an inline\r
-                               // element to avoid the selection moving inside of it.\r
-                               dummySpan = this.document.createElement( 'span' );\r
-                               dummySpan.setHtml( '&#65279;' );        // Zero Width No-Break Space (U+FEFF). See #1359.\r
-                               dummySpan.insertBefore( startNode );\r
-\r
-                               if ( isStartMarkerAlone )\r
+                                       // Move the end boundary of the main range to match the tool range.\r
+                                       ieRange.setEndPoint( 'EndToEnd', ieRangeEnd );\r
+                                       ieRange.moveEnd( 'character', -1 );\r
+                               }\r
+                               else\r
                                {\r
-                                       // To expand empty blocks or line spaces after <br>, we need\r
-                                       // instead to have any char, which will be later deleted using the\r
-                                       // selection.\r
-                                       // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359)\r
-                                       this.document.createText( '\ufeff' ).insertBefore( startNode );\r
+                                       // The isStartMarkerAlone logic comes from V2. It guarantees that the lines\r
+                                       // will expand and that the cursor will be blinking on the right place.\r
+                                       // Actually, we are using this flag just to avoid using this hack in all\r
+                                       // situations, but just on those needed.\r
+                                       var next = startNode.getNext( notWhitespaces );\r
+                                       isStartMarkerAlone = ( !( next && next.getText && next.getText().match( fillerTextRegex ) )     // already a filler there?\r
+                                                                                 && ( forceExpand || !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) ) );\r
+\r
+                                       // Append a temporary <span>&#65279;</span> before the selection.\r
+                                       // This is needed to avoid IE destroying selections inside empty\r
+                                       // inline elements, like <b></b> (#253).\r
+                                       // It is also needed when placing the selection right after an inline\r
+                                       // element to avoid the selection moving inside of it.\r
+                                       dummySpan = this.document.createElement( 'span' );\r
+                                       dummySpan.setHtml( '&#65279;' );        // Zero Width No-Break Space (U+FEFF). See #1359.\r
+                                       dummySpan.insertBefore( startNode );\r
+\r
+                                       if ( isStartMarkerAlone )\r
+                                       {\r
+                                               // To expand empty blocks or line spaces after <br>, we need\r
+                                               // instead to have any char, which will be later deleted using the\r
+                                               // selection.\r
+                                               // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359)\r
+                                               this.document.createText( '\ufeff' ).insertBefore( startNode );\r
+                                       }\r
                                }\r
-                       }\r
 \r
-                       // Remove the markers (reset the position, because of the changes in the DOM tree).\r
-                       this.setStartBefore( startNode );\r
-                       startNode.remove();\r
+                               // Remove the markers (reset the position, because of the changes in the DOM tree).\r
+                               this.setStartBefore( startNode );\r
+                               startNode.remove();\r
 \r
-                       if ( collapsed )\r
-                       {\r
-                               if ( isStartMarkerAlone )\r
+                               if ( collapsed )\r
                                {\r
-                                       // Move the selection start to include the temporary \ufeff.\r
-                                       ieRange.moveStart( 'character', -1 );\r
+                                       if ( isStartMarkerAlone )\r
+                                       {\r
+                                               // Move the selection start to include the temporary \ufeff.\r
+                                               ieRange.moveStart( 'character', -1 );\r
 \r
-                                       ieRange.select();\r
+                                               ieRange.select();\r
+\r
+                                               // Remove our temporary stuff.\r
+                                               this.document.$.selection.clear();\r
+                                       }\r
+                                       else\r
+                                               ieRange.select();\r
 \r
-                                       // Remove our temporary stuff.\r
-                                       this.document.$.selection.clear();\r
+                                       this.moveToPosition( dummySpan, CKEDITOR.POSITION_BEFORE_START );\r
+                                       dummySpan.remove();\r
                                }\r
                                else\r
+                               {\r
+                                       this.setEndBefore( endNode );\r
+                                       endNode.remove();\r
                                        ieRange.select();\r
+                               }\r
 \r
-                               this.moveToPosition( dummySpan, CKEDITOR.POSITION_BEFORE_START );\r
-                               dummySpan.remove();\r
+                               this.document.fire( 'selectionchange' );\r
                        }\r
-                       else\r
+               :\r
+                       function()\r
                        {\r
-                               this.setEndBefore( endNode );\r
-                               endNode.remove();\r
-                               ieRange.select();\r
-                       }\r
-\r
-                       this.document.fire( 'selectionchange' );\r
-               }\r
-       :\r
-               function()\r
-               {\r
-                       var startContainer = this.startContainer;\r
+                               var startContainer = this.startContainer;\r
 \r
-                       // If we have a collapsed range, inside an empty element, we must add\r
-                       // something to it, otherwise the caret will not be visible.\r
-                       if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() )\r
-                               startContainer.append( new CKEDITOR.dom.text( '' ) );\r
+                               // If we have a collapsed range, inside an empty element, we must add\r
+                               // something to it, otherwise the caret will not be visible.\r
+                               if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() )\r
+                                       startContainer.append( new CKEDITOR.dom.text( '' ) );\r
 \r
-                       var nativeRange = this.document.$.createRange();\r
-                       nativeRange.setStart( startContainer.$, this.startOffset );\r
+                               var nativeRange = this.document.$.createRange();\r
+                               nativeRange.setStart( startContainer.$, this.startOffset );\r
 \r
-                       try\r
-                       {\r
-                               nativeRange.setEnd( this.endContainer.$, this.endOffset );\r
-                       }\r
-                       catch ( e )\r
-                       {\r
-                               // There is a bug in Firefox implementation (it would be too easy\r
-                               // otherwise). The new start can't be after the end (W3C says it can).\r
-                               // So, let's create a new range and collapse it to the desired point.\r
-                               if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 )\r
+                               try\r
                                {\r
-                                       this.collapse( true );\r
                                        nativeRange.setEnd( this.endContainer.$, this.endOffset );\r
                                }\r
-                               else\r
-                                       throw( e );\r
-                       }\r
+                               catch ( e )\r
+                               {\r
+                                       // There is a bug in Firefox implementation (it would be too easy\r
+                                       // otherwise). The new start can't be after the end (W3C says it can).\r
+                                       // So, let's create a new range and collapse it to the desired point.\r
+                                       if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 )\r
+                                       {\r
+                                               this.collapse( true );\r
+                                               nativeRange.setEnd( this.endContainer.$, this.endOffset );\r
+                                       }\r
+                                       else\r
+                                               throw( e );\r
+                               }\r
 \r
-                       var selection = this.document.getSelection().getNative();\r
-                       selection.removeAllRanges();\r
-                       selection.addRange( nativeRange );\r
-               };\r
+                               var selection = this.document.getSelection().getNative();\r
+                               selection.removeAllRanges();\r
+                               selection.addRange( nativeRange );\r
+                       };\r
 } )();\r