JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.2.2
[ckeditor.git] / _source / core / dom / range.js
index a8b102d..5a05ee1 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
-Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
 For licensing, see LICENSE.html or http://ckeditor.com/license\r
 */\r
 \r
@@ -133,7 +133,7 @@ CKEDITOR.dom.range = function( document )
 \r
                        currentNode = levelStartNode.getNext();\r
 \r
-                       while( currentNode )\r
+                       while ( currentNode )\r
                        {\r
                                // Stop processing when the current node matches a node in the\r
                                // endParents tree or if it is the endNode.\r
@@ -180,7 +180,7 @@ CKEDITOR.dom.range = function( document )
                        {\r
                                currentNode = levelStartNode.getPrevious();\r
 \r
-                               while( currentNode )\r
+                               while ( currentNode )\r
                                {\r
                                        // Stop processing when the current node matches a node in the\r
                                        // startParents tree or if it is the startNode.\r
@@ -252,10 +252,10 @@ CKEDITOR.dom.range = function( document )
                }\r
 \r
                // Cleanup any marked node.\r
-               if( removeStartNode )\r
+               if ( removeStartNode )\r
                        startNode.remove();\r
 \r
-               if( removeEndNode && endNode.$.parentNode )\r
+               if ( removeEndNode && endNode.$.parentNode )\r
                        endNode.remove();\r
        };\r
 \r
@@ -278,7 +278,7 @@ CKEDITOR.dom.range = function( document )
                                if ( CKEDITOR.tools.trim( node.getText() ).length )\r
                                        return false;\r
                                }\r
-                       else if( node.type == CKEDITOR.NODE_ELEMENT )\r
+                       else if ( node.type == CKEDITOR.NODE_ELEMENT )\r
                        {\r
                                // If there are non-empty inline elements (e.g. <img />), then we're not\r
                                // at the start.\r
@@ -742,15 +742,21 @@ CKEDITOR.dom.range = function( document )
 \r
                                        startOffset = startContainer.getIndex() + 1;\r
                                        startContainer = startContainer.getParent();\r
-                                       // Check if it is necessary to update the end boundary.\r
-                                       if ( !collapsed && this.startContainer.equals( this.endContainer ) )\r
+\r
+                                       // Check all necessity of updating the end boundary.\r
+                                       if ( this.startContainer.equals( this.endContainer ) )\r
                                                this.setEnd( nextText, this.endOffset - this.startOffset );\r
+                                       else if ( startContainer.equals( this.endContainer ) )\r
+                                               this.endOffset += 1;\r
                                }\r
 \r
                                this.setStart( startContainer, startOffset );\r
 \r
                                if ( collapsed )\r
+                               {\r
                                        this.collapse( true );\r
+                                       return;\r
+                               }\r
                        }\r
 \r
                        var endContainer = this.endContainer;\r
@@ -919,7 +925,7 @@ CKEDITOR.dom.range = function( document )
 \r
                                                                                siblingText = sibling.getText();\r
 \r
-                                                                               if ( !(/[^\s\ufeff]/).test( siblingText ) )     // Spaces + Zero Width No-Break Space (U+FEFF)\r
+                                                                               if ( (/[^\s\ufeff]/).test( siblingText ) )      // Spaces + Zero Width No-Break Space (U+FEFF)\r
                                                                                        sibling = null;\r
                                                                                else\r
                                                                                {\r
@@ -1078,7 +1084,7 @@ CKEDITOR.dom.range = function( document )
 \r
                                                                                siblingText = sibling.getText();\r
 \r
-                                                                               if ( !(/[^\s\ufeff]/).test( siblingText ) )\r
+                                                                               if ( (/[^\s\ufeff]/).test( siblingText ) )\r
                                                                                        sibling = null;\r
                                                                                else\r
                                                                                {\r
@@ -1230,6 +1236,100 @@ CKEDITOR.dom.range = function( document )
                },\r
 \r
                /**\r
+                *  Descrease the range to make sure that boundaries\r
+                *  always anchor beside text nodes or innermost element.\r
+                * @param {Number} mode  ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode.\r
+                */\r
+               shrink : function( mode, selectContents )\r
+               {\r
+                       // Unable to shrink a collapsed range.\r
+                       if ( !this.collapsed )\r
+                       {\r
+                               mode = mode || CKEDITOR.SHRINK_TEXT;\r
+\r
+                               var walkerRange = this.clone();\r
+\r
+                               var startContainer = this.startContainer,\r
+                                       endContainer = this.endContainer,\r
+                                       startOffset = this.startOffset,\r
+                                       endOffset = this.endOffset,\r
+                                       collapsed = this.collapsed;\r
+\r
+                               // Whether the start/end boundary is moveable.\r
+                               var moveStart = 1,\r
+                                               moveEnd = 1;\r
+\r
+                               if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )\r
+                               {\r
+                                       if ( !startOffset )\r
+                                               walkerRange.setStartBefore( startContainer );\r
+                                       else if ( startOffset >= startContainer.getLength( ) )\r
+                                               walkerRange.setStartAfter( startContainer );\r
+                                       else\r
+                                       {\r
+                                               // Enlarge the range properly to avoid walker making\r
+                                               // DOM changes caused by triming the text nodes later.\r
+                                               walkerRange.setStartBefore( startContainer );\r
+                                               moveStart = 0;\r
+                                       }\r
+                               }\r
+\r
+                               if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )\r
+                               {\r
+                                       if ( !endOffset )\r
+                                               walkerRange.setEndBefore( endContainer );\r
+                                       else if ( endOffset >= endContainer.getLength( ) )\r
+                                               walkerRange.setEndAfter( endContainer );\r
+                                       else\r
+                                       {\r
+                                               walkerRange.setEndAfter( endContainer );\r
+                                               moveEnd = 0;\r
+                                       }\r
+                               }\r
+\r
+                               var walker = new CKEDITOR.dom.walker( walkerRange );\r
+\r
+                               walker.evaluator = function( node )\r
+                               {\r
+                                       return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ?\r
+                                               CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );\r
+                               };\r
+\r
+                               var currentElement;\r
+                               walker.guard = function( node, movingOut )\r
+                               {\r
+                                       // Stop when we're shrink in element mode while encountering a text node.\r
+                                       if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT )\r
+                                               return false;\r
+\r
+                                       // Stop when we've already walked "through" an element.\r
+                                       if ( movingOut && node.equals( currentElement ) )\r
+                                               return false;\r
+\r
+                                       if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT )\r
+                                               currentElement = node;\r
+\r
+                                       return true;\r
+                               };\r
+\r
+                               if ( moveStart )\r
+                               {\r
+                                       var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next']();\r
+                                       textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START );\r
+                               }\r
+\r
+                               if ( moveEnd )\r
+                               {\r
+                                       walker.reset();\r
+                                       var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();\r
+                                       textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END );\r
+                               }\r
+\r
+                               return !!( moveStart || moveEnd );\r
+                       }\r
+               },\r
+\r
+               /**\r
                 * Inserts a node at the start of the range. The range will be expanded\r
                 * the contain the node.\r
                 */\r
@@ -1282,6 +1382,11 @@ CKEDITOR.dom.range = function( document )
                        // we will not need this check for our use of this class so we can\r
                        // ignore it for now.\r
 \r
+                       // Fixing invalid range start inside dtd empty elements.\r
+                       if( startNode.type == CKEDITOR.NODE_ELEMENT\r
+                               && CKEDITOR.dtd.$empty[ startNode.getName() ] )\r
+                               startNode = startNode.getParent(), startOffset = startNode.getIndex();\r
+\r
                        this.startContainer     = startNode;\r
                        this.startOffset        = startOffset;\r
 \r
@@ -1308,6 +1413,11 @@ CKEDITOR.dom.range = function( document )
                        // will not need this check for our use of this class so we can ignore\r
                        // it for now.\r
 \r
+                       // Fixing invalid range end inside dtd empty elements.\r
+                       if( endNode.type == CKEDITOR.NODE_ELEMENT\r
+                               && CKEDITOR.dtd.$empty[ endNode.getName() ] )\r
+                               endNode = endNode.getParent(), endOffset = endNode.getIndex() + 1;\r
+\r
                        this.endContainer       = endNode;\r
                        this.endOffset          = endOffset;\r
 \r
@@ -1468,6 +1578,7 @@ CKEDITOR.dom.range = function( document )
                                else\r
                                {\r
                                        endBlock = this.splitElement( startBlock );\r
+\r
                                        // In Gecko, the last child node must be a bogus <br>.\r
                                        // Note: bogus <br> added under <ul> or <ol> would cause\r
                                        // lists to be incorrectly rendered.\r
@@ -1604,41 +1715,52 @@ CKEDITOR.dom.range = function( document )
                },\r
 \r
                /**\r
-                * Moves the range boundaries to the first editing point inside an\r
+                * Moves the range boundaries to the first/end editing point inside an\r
                 * element. For example, in an element tree like\r
                 * "&lt;p&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt; Text&lt;/p&gt;", the start editing point is\r
                 * "&lt;p&gt;&lt;b&gt;&lt;i&gt;^&lt;/i&gt;&lt;/b&gt; Text&lt;/p&gt;" (inside &lt;i&gt;).\r
                 * @param {CKEDITOR.dom.element} el The element into which look for the\r
                 *              editing spot.\r
+                * @param {Boolean} isMoveToEnd Whether move to the end editable position.\r
                 */\r
-               moveToElementEditStart : function( el )\r
+               moveToElementEditablePosition : function( el, isMoveToEnd )\r
                {\r
                        var isEditable;\r
 \r
+                       // Empty elements are rejected.\r
+                       if ( CKEDITOR.dtd.$empty[ el.getName() ] )\r
+                               return false;\r
+\r
                        while ( el && el.type == CKEDITOR.NODE_ELEMENT )\r
                        {\r
                                isEditable = el.isEditable();\r
 \r
                                // If an editable element is found, move inside it.\r
                                if ( isEditable )\r
-                                       this.moveToPosition( el, CKEDITOR.POSITION_AFTER_START );\r
+                                       this.moveToPosition( el, isMoveToEnd ?\r
+                                                                CKEDITOR.POSITION_BEFORE_END :\r
+                                                                CKEDITOR.POSITION_AFTER_START );\r
                                // Stop immediately if we've found a non editable inline element (e.g <img>).\r
                                else if ( CKEDITOR.dtd.$inline[ el.getName() ] )\r
                                {\r
-                                       this.moveToPosition( el, CKEDITOR.POSITION_BEFORE_START );\r
+                                       this.moveToPosition( el, isMoveToEnd ?\r
+                                                                CKEDITOR.POSITION_AFTER_END :\r
+                                                                CKEDITOR.POSITION_BEFORE_START );\r
                                        return true;\r
                                }\r
 \r
                                // Non-editable non-inline elements are to be bypassed, getting the next one.\r
                                if ( CKEDITOR.dtd.$empty[ el.getName() ] )\r
-                                       el = el.getNext( nonWhitespaceOrBookmarkEval );\r
+                                       el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval );\r
                                else\r
-                                       el = el.getFirst( nonWhitespaceOrBookmarkEval );\r
+                                       el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval );\r
 \r
                                // Stop immediately if we've found a text node.\r
                                if ( el && el.type == CKEDITOR.NODE_TEXT )\r
                                {\r
-                                       this.moveToPosition( el, CKEDITOR.POSITION_BEFORE_START );\r
+                                       this.moveToPosition( el, isMoveToEnd ?\r
+                                                                CKEDITOR.POSITION_AFTER_END :\r
+                                                                CKEDITOR.POSITION_BEFORE_START );\r
                                        return true;\r
                                }\r
                        }\r
@@ -1647,6 +1769,22 @@ CKEDITOR.dom.range = function( document )
                },\r
 \r
                /**\r
+                *@see {CKEDITOR.dom.range.moveToElementEditablePosition}\r
+                */\r
+               moveToElementEditStart : function( target )\r
+               {\r
+                       return this.moveToElementEditablePosition( target );\r
+               },\r
+\r
+               /**\r
+                *@see {CKEDITOR.dom.range.moveToElementEditablePosition}\r
+                */\r
+               moveToElementEditEnd : function( target )\r
+               {\r
+                       return this.moveToElementEditablePosition( target, true );\r
+               },\r
+\r
+               /**\r
                 * Get the single node enclosed within the range if there's one.\r
                 */\r
                getEnclosedNode : function()\r
@@ -1703,3 +1841,6 @@ CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
 CKEDITOR.START = 1;\r
 CKEDITOR.END = 2;\r
 CKEDITOR.STARTEND = 3;\r
+\r
+CKEDITOR.SHRINK_ELEMENT = 1;\r
+CKEDITOR.SHRINK_TEXT = 2;\r