JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.5.3
[ckeditor.git] / _source / core / dom / range.js
index a47173f..4c0067b 100644 (file)
@@ -1,19 +1,90 @@
 /*\r
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
 For licensing, see LICENSE.html or http://ckeditor.com/license\r
 */\r
 \r
 /**\r
- * @class\r
+ * Creates a CKEDITOR.dom.range instance that can be used inside a specific\r
+ * DOM Document.\r
+ * @class Represents a delimited piece of content in a DOM Document.\r
+ * It is contiguous in the sense that it can be characterized as selecting all\r
+ * of the content between a pair of boundary-points.<br>\r
+ * <br>\r
+ * This class shares much of the W3C\r
+ * <a href="http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html">Document Object Model Range</a>\r
+ * ideas and features, adding several range manipulation tools to it, but it's\r
+ * not intended to be compatible with it.\r
+ * @param {CKEDITOR.dom.document} document The document into which the range\r
+ *             features will be available.\r
+ * @example\r
+ * // Create a range for the entire contents of the editor document body.\r
+ * var range = new CKEDITOR.dom.range( editor.document );\r
+ * range.selectNodeContents( editor.document.getBody() );\r
+ * // Delete the contents.\r
+ * range.deleteContents();\r
  */\r
 CKEDITOR.dom.range = function( document )\r
 {\r
+       /**\r
+        * Node within which the range begins.\r
+        * @type {CKEDITOR.NODE_ELEMENT|CKEDITOR.NODE_TEXT}\r
+        * @example\r
+        * var range = new CKEDITOR.dom.range( editor.document );\r
+        * range.selectNodeContents( editor.document.getBody() );\r
+        * alert( range.startContainer.getName() );  // "body"\r
+        */\r
        this.startContainer     = null;\r
+\r
+       /**\r
+        * Offset within the starting node of the range.\r
+        * @type {Number}\r
+        * @example\r
+        * var range = new CKEDITOR.dom.range( editor.document );\r
+        * range.selectNodeContents( editor.document.getBody() );\r
+        * alert( range.startOffset );  // "0"\r
+        */\r
        this.startOffset        = null;\r
+\r
+       /**\r
+        * Node within which the range ends.\r
+        * @type {CKEDITOR.NODE_ELEMENT|CKEDITOR.NODE_TEXT}\r
+        * @example\r
+        * var range = new CKEDITOR.dom.range( editor.document );\r
+        * range.selectNodeContents( editor.document.getBody() );\r
+        * alert( range.endContainer.getName() );  // "body"\r
+        */\r
        this.endContainer       = null;\r
+\r
+       /**\r
+        * Offset within the ending node of the range.\r
+        * @type {Number}\r
+        * @example\r
+        * var range = new CKEDITOR.dom.range( editor.document );\r
+        * range.selectNodeContents( editor.document.getBody() );\r
+        * alert( range.endOffset );  // == editor.document.getBody().getChildCount()\r
+        */\r
        this.endOffset          = null;\r
+\r
+       /**\r
+        * Indicates that this is a collapsed range. A collapsed range has it's\r
+        * start and end boudaries at the very same point so nothing is contained\r
+        * in it.\r
+        * @example\r
+        * var range = new CKEDITOR.dom.range( editor.document );\r
+        * range.selectNodeContents( editor.document.getBody() );\r
+        * alert( range.collapsed );  // "false"\r
+        * range.collapse();\r
+        * alert( range.collapsed );  // "true"\r
+        */\r
        this.collapsed          = true;\r
 \r
+       /**\r
+        * The document within which the range can be used.\r
+        * @type {CKEDITOR.dom.document}\r
+        * @example\r
+        * // Selects the body contents of the range document.\r
+        * range.selectNodeContents( range.document.getBody() );\r
+        */\r
        this.document = document;\r
 };\r
 \r
@@ -32,7 +103,7 @@ CKEDITOR.dom.range = function( document )
        // This is a shared function used to delete, extract and clone the range\r
        // contents.\r
        // V2\r
-       var execContentsAction = function( range, action, docFrag )\r
+       var execContentsAction = function( range, action, docFrag, mergeThen )\r
        {\r
                range.optimizeBookmark();\r
 \r
@@ -247,7 +318,17 @@ CKEDITOR.dom.range = function( document )
                                if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )\r
                                        endIndex--;\r
 \r
-                               range.setStart( topEnd.getParent(), endIndex );\r
+                               // Merge splitted parents.\r
+                               if ( mergeThen && topStart.type == CKEDITOR.NODE_ELEMENT )\r
+                               {\r
+                                       var span = CKEDITOR.dom.element.createFromHtml( '<span ' +\r
+                                               'data-cke-bookmark="1" style="display:none">&nbsp;</span>', range.document );\r
+                                       span.insertAfter( topStart );\r
+                                       topStart.mergeSiblings( false );\r
+                                       range.moveToBookmark( { startNode : span } );\r
+                               }\r
+                               else\r
+                                       range.setStart( topEnd.getParent(), endIndex );\r
                        }\r
 \r
                        // Collapse it to the start.\r
@@ -367,25 +448,27 @@ CKEDITOR.dom.range = function( document )
 \r
                /**\r
                 * Deletes the content nodes of the range permanently from the DOM tree.\r
+                * @param {Boolean} [mergeThen] Merge any splitted elements result in DOM true due to partial selection.\r
                 */\r
-               deleteContents : function()\r
+               deleteContents : function( mergeThen )\r
                {\r
                        if ( this.collapsed )\r
                                return;\r
 \r
-                       execContentsAction( this, 0 );\r
+                       execContentsAction( this, 0, null, mergeThen );\r
                },\r
 \r
                /**\r
                 *  The content nodes of the range are cloned and added to a document fragment,\r
                 * meanwhile they're removed permanently from the DOM tree.\r
+                * @param {Boolean} [mergeThen] Merge any splitted elements result in DOM true due to partial selection.\r
                 */\r
-               extractContents : function()\r
+               extractContents : function( mergeThen )\r
                {\r
                        var docFrag = new CKEDITOR.dom.documentFragment( this.document );\r
 \r
                        if ( !this.collapsed )\r
-                               execContentsAction( this, 1, docFrag );\r
+                               execContentsAction( this, 1, docFrag, mergeThen );\r
 \r
                        return docFrag;\r
                },\r
@@ -506,6 +589,10 @@ CKEDITOR.dom.range = function( document )
                                                startContainer = child;\r
                                                startOffset = 0;\r
                                        }\r
+\r
+                                       // Get the normalized offset.\r
+                                       if ( child && child.type == CKEDITOR.NODE_ELEMENT )\r
+                                               startOffset = child.getIndex( 1 );\r
                                }\r
 \r
                                // Normalize the start.\r
@@ -534,6 +621,10 @@ CKEDITOR.dom.range = function( document )
                                                        endContainer = child;\r
                                                        endOffset = 0;\r
                                                }\r
+\r
+                                               // Get the normalized offset.\r
+                                               if ( child && child.type == CKEDITOR.NODE_ELEMENT )\r
+                                                       endOffset = child.getIndex( 1 );\r
                                        }\r
 \r
                                        // Normalize the end.\r
@@ -811,7 +902,12 @@ CKEDITOR.dom.range = function( document )
                        }\r
                },\r
 \r
-               enlarge : function( unit )\r
+               /**\r
+                * Expands the range so that partial units are completely contained.\r
+                * @param unit {Number} The unit type to expand with.\r
+                * @param {Boolean} [excludeBrs=false] Whether include line-breaks when expanding.\r
+                */\r
+               enlarge : function( unit, excludeBrs )\r
                {\r
                        switch ( unit )\r
                        {\r
@@ -932,7 +1028,8 @@ CKEDITOR.dom.range = function( document )
                                                                // If this is a visible element.\r
                                                                // We need to check for the bookmark attribute because IE insists on\r
                                                                // rendering the display:none nodes we use for bookmarks. (#3363)\r
-                                                               if ( sibling.$.offsetWidth > 0 && !sibling.data( 'cke-bookmark' ) )\r
+                                                               // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041)\r
+                                                               if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) )\r
                                                                {\r
                                                                        // We'll accept it only if we need\r
                                                                        // whitespace, and this is an inline\r
@@ -1091,7 +1188,8 @@ CKEDITOR.dom.range = function( document )
                                                                // If this is a visible element.\r
                                                                // We need to check for the bookmark attribute because IE insists on\r
                                                                // rendering the display:none nodes we use for bookmarks. (#3363)\r
-                                                               if ( sibling.$.offsetWidth > 0 && !sibling.data( 'cke-bookmark' ) )\r
+                                                               // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041)\r
+                                                               if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) )\r
                                                                {\r
                                                                        // We'll accept it only if we need\r
                                                                        // whitespace, and this is an inline\r
@@ -1755,6 +1853,47 @@ CKEDITOR.dom.range = function( document )
                },\r
 \r
                /**\r
+                * Check if elements at which the range boundaries anchor are read-only,\r
+                * with respect to "contenteditable" attribute.\r
+                */\r
+               checkReadOnly : ( function()\r
+               {\r
+                       function checkNodesEditable( node, anotherEnd )\r
+                       {\r
+                               while( node )\r
+                               {\r
+                                       if ( node.type == CKEDITOR.NODE_ELEMENT )\r
+                                       {\r
+                                               if ( node.getAttribute( 'contentEditable' ) == 'false'\r
+                                                       && !node.data( 'cke-editable' ) )\r
+                                               {\r
+                                                       return 0;\r
+                                               }\r
+                                               // Range enclosed entirely in an editable element.\r
+                                               else if ( node.is( 'body' )\r
+                                                       || node.getAttribute( 'contentEditable' ) == 'true'\r
+                                                       && ( node.contains( anotherEnd ) || node.equals( anotherEnd ) ) )\r
+                                               {\r
+                                                       break;\r
+                                               }\r
+                                       }\r
+                                       node = node.getParent();\r
+                               }\r
+\r
+                               return 1;\r
+                       }\r
+\r
+                       return function()\r
+                       {\r
+                               var startNode = this.startContainer,\r
+                                       endNode = this.endContainer;\r
+\r
+                               // Check if elements path at both boundaries are editable.\r
+                               return !( checkNodesEditable( startNode, endNode ) && checkNodesEditable( endNode, startNode ) );\r
+                       };\r
+               })(),\r
+\r
+               /**\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