/*\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
// 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
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"> </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
// If there's any visible text, then we're not at the start.\r
if ( CKEDITOR.tools.trim( node.getText() ).length )\r
return false;\r
- }\r
+ }\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
return node.type != CKEDITOR.NODE_TEXT\r
&& node.getName() in CKEDITOR.dtd.$removeEmpty\r
|| !CKEDITOR.tools.trim( node.getText() )\r
- || node.getParent().hasAttribute( '_cke_bookmark' );\r
+ || !!node.getParent().data( 'cke-bookmark' );\r
}\r
\r
var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(),\r
\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
var collapsed = this.collapsed;\r
\r
startNode = this.document.createElement( 'span' );\r
- startNode.setAttribute( '_cke_bookmark', 1 );\r
+ startNode.data( 'cke-bookmark', 1 );\r
startNode.setStyle( 'display', 'none' );\r
\r
// For IE, it must have something inside, otherwise it may be\r
endNode = this.endContainer;\r
\r
if ( startNode.is && startNode.is( 'span' )\r
- && startNode.hasAttribute( '_cke_bookmark' ) )\r
+ && startNode.data( 'cke-bookmark' ) )\r
this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );\r
if ( endNode && endNode.is && endNode.is( 'span' )\r
- && endNode.hasAttribute( '_cke_bookmark' ) )\r
+ && endNode.data( 'cke-bookmark' ) )\r
this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END );\r
},\r
\r
}\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
// 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.getAttribute( '_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
// 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.getAttribute( '_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
},\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
* "<p><b><i></i></b> Text</p>", the start editing point is\r