X-Git-Url: https://jasonwoof.com/gitweb/?a=blobdiff_plain;f=_source%2Fplugins%2Fselection%2Fplugin.js;h=a42ef66f3617031c1cc52514d8cd1132407dc52c;hb=4e70ea24db840898be8cc21c950363a52a2a6aba;hp=3d707fc99ed284ad564f0e5475ec6480595fb59a;hpb=055b6b0792ce7dc53d47af606b367c04b927c2ab;p=ckeditor.git diff --git a/_source/plugins/selection/plugin.js b/_source/plugins/selection/plugin.js index 3d707fc..a42ef66 100644 --- a/_source/plugins/selection/plugin.js +++ b/_source/plugins/selection/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -70,38 +70,165 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // #### checkSelectionChange : END + function rangeRequiresFix( range ) + { + function isInlineCt( node ) + { + return node && node.type == CKEDITOR.NODE_ELEMENT + && node.getName() in CKEDITOR.dtd.$removeEmpty; + } + + var start = range.startContainer, + offset = range.startOffset; + + if ( start.type == CKEDITOR.NODE_TEXT ) + return false; + + // 1. Empty inline element. ^ + // 2. Adjoin to inline element.

text^

+ return !CKEDITOR.tools.trim( start.getHtml() ) ? isInlineCt( start ) : isInlineCt( start.getChild( offset - 1 ) ) || isInlineCt( start.getChild( offset ) ); + } + var selectAllCmd = { modes : { wysiwyg : 1, source : 1 }, + readOnly : CKEDITOR.env.ie || CKEDITOR.env.webkit, exec : function( editor ) { switch ( editor.mode ) { case 'wysiwyg' : editor.document.$.execCommand( 'SelectAll', false, null ); + // Force triggering selectionChange (#7008) + editor.forceNextSelectionCheck(); + editor.selectionChange(); break; case 'source' : // Select the contents of the textarea - var textarea = editor.textarea.$ ; + var textarea = editor.textarea.$; if ( CKEDITOR.env.ie ) - { - textarea.createTextRange().execCommand( 'SelectAll' ) ; - } + textarea.createTextRange().execCommand( 'SelectAll' ); else { - textarea.selectionStart = 0 ; - textarea.selectionEnd = textarea.value.length ; + textarea.selectionStart = 0; + textarea.selectionEnd = textarea.value.length; } - textarea.focus() ; + textarea.focus(); } }, canUndo : false }; + function createFillingChar( doc ) + { + removeFillingChar( doc ); + + var fillingChar = doc.createText( '\u200B' ); + doc.setCustomData( 'cke-fillingChar', fillingChar ); + + return fillingChar; + } + + function getFillingChar( doc ) + { + return doc && doc.getCustomData( 'cke-fillingChar' ); + } + + // Checks if a filling char has been used, eventualy removing it (#1272). + function checkFillingChar( doc ) + { + var fillingChar = doc && getFillingChar( doc ); + if ( fillingChar ) + { + // Use this flag to avoid removing the filling char right after + // creating it. + if ( fillingChar.getCustomData( 'ready' ) ) + removeFillingChar( doc ); + else + fillingChar.setCustomData( 'ready', 1 ); + } + } + + function removeFillingChar( doc ) + { + var fillingChar = doc && doc.removeCustomData( 'cke-fillingChar' ); + if ( fillingChar ) + { + // We can't simply remove the filling node because the user + // will actually enlarge it when typing, so we just remove the + // invisible char from it. + fillingChar.setText( fillingChar.getText().replace( /\u200B/g, '' ) ); + fillingChar = 0; + } + } + CKEDITOR.plugins.add( 'selection', { init : function( editor ) { + // On WebKit only, we need a special "filling" char on some situations + // (#1272). Here we set the events that should invalidate that char. + if ( CKEDITOR.env.webkit ) + { + editor.on( 'selectionChange', function() { checkFillingChar( editor.document ); } ); + editor.on( 'beforeSetMode', function() { removeFillingChar( editor.document ); } ); + editor.on( 'key', function( e ) + { + // Remove the filling char before some keys get + // executed, so they'll not get blocked by it. + switch ( e.data.keyCode ) + { + case 13 : // ENTER + case CKEDITOR.SHIFT + 13 : // SHIFT-ENTER + case 37 : // LEFT-ARROW + case 39 : // RIGHT-ARROW + case 8 : // BACKSPACE + removeFillingChar( editor.document ); + } + }, null, null, 10 ); + + var fillingCharBefore, + resetSelection; + + function beforeData() + { + var doc = editor.document, + fillingChar = getFillingChar( doc ); + + if ( fillingChar ) + { + // If cursor is right blinking by side of the filler node, save it for restoring, + // as the following text substitution will blind it. (#7437) + var sel = doc.$.defaultView.getSelection(); + if ( sel.type == 'Caret' && sel.anchorNode == fillingChar.$ ) + resetSelection = 1; + + fillingCharBefore = fillingChar.getText(); + fillingChar.setText( fillingCharBefore.replace( /\u200B/g, '' ) ); + } + } + function afterData() + { + var doc = editor.document, + fillingChar = getFillingChar( doc ); + + if ( fillingChar ) + { + fillingChar.setText( fillingCharBefore ); + + if ( resetSelection ) + { + doc.$.defaultView.getSelection().setPosition( fillingChar.$,fillingChar.getLength() ); + resetSelection = 0; + } + } + } + editor.on( 'beforeUndoImage', beforeData ); + editor.on( 'afterUndoImage', afterData ); + editor.on( 'beforeGetData', beforeData, null, null, 0 ); + editor.on( 'getData', afterData ); + } + editor.on( 'contentDom', function() { var doc = editor.document, @@ -142,6 +269,14 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } catch (e) {} + + // Update locked selection because of the normalized text nodes. (#6083, #6987) + var lockedSelection = doc.getCustomData( 'cke_locked_selection' ); + if ( lockedSelection ) + { + lockedSelection.unlock(); + lockedSelection.lock(); + } } savedRange = null; @@ -151,7 +286,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license body.on( 'focus', function() { // Enable selections to be saved. - saveEnabled = true; + saveEnabled = 1; saveSelection(); }); @@ -164,7 +299,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license return; // Disable selections from being saved. - saveEnabled = false; + saveEnabled = 0; restoreEnabled = 1; }); @@ -174,13 +309,18 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { editor.on( 'blur', function( evt ) { - editor.document && editor.document.$.selection.empty(); + // Try/Catch to avoid errors if the editor is hidden. (#6375) + try + { + editor.document && editor.document.$.selection.empty(); + } + catch (e) {} }); } // Listening on document element ensures that // scrollbar is included. (#5280) - html.on( 'mousedown', function () + html.on( 'mousedown', function() { // Lock restore selection now, as we have // a followed 'click' event which introduce @@ -188,7 +328,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license restoreEnabled = 0; }); - html.on( 'mouseup', function () + html.on( 'mouseup', function() { restoreEnabled = 1; }); @@ -207,17 +347,35 @@ For licensing, see LICENSE.html or http://ckeditor.com/license }); } + var scroll; // IE fires the "selectionchange" event when clicking // inside a selection. We don't want to capture that. - body.on( 'mousedown', function () + body.on( 'mousedown', function( evt ) { + // IE scrolls document to top on right mousedown + // when editor has no focus, remember this scroll + // position and revert it before context menu opens. (#5778) + if ( evt.data.$.button == 2 ) + { + var sel = editor.document.$.selection; + if ( sel.type == 'None' ) + scroll = editor.window.getScrollPosition(); + } disableSave(); }); body.on( 'mouseup', - function() + function( evt ) { - saveEnabled = true; + // Restore recorded scroll position when needed on right mouseup. + if ( evt.data.$.button == 2 && scroll ) + { + editor.document.$.documentElement.scrollLeft = scroll.x; + editor.document.$.documentElement.scrollTop = scroll.y; + } + scroll = null; + + saveEnabled = 1; setTimeout( function() { saveSelection( true ); @@ -229,7 +387,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license body.on( 'keyup', function() { - saveEnabled = true; + saveEnabled = 1; saveSelection(); }); @@ -240,7 +398,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license function disableSave() { - saveEnabled = false; + saveEnabled = 0; } function saveSelection( testIt ) @@ -300,6 +458,9 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } }); + // Clear the cached range path before unload. (#7174) + editor.on( 'contentDomUnload', editor.forceNextSelectionCheck, editor ); + editor.addCommand( 'selectAll', selectAllCmd ); editor.ui.addButton( 'SelectAll', { @@ -382,7 +543,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license return lockedSelection; this.document = document; - this.isLocked = false; + this.isLocked = 0; this._ = { cache : {} @@ -407,10 +568,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license }; var styleObjectElements = - { - img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1, - a:1, input:1, form:1, select:1, textarea:1, button:1, fieldset:1, th:1, thead:1, tfoot:1 - }; + { + img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1, + a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1 + }; CKEDITOR.dom.selection.prototype = { @@ -525,10 +686,13 @@ For licensing, see LICENSE.html or http://ckeditor.com/license * var ranges = selection.getRanges(); * alert(ranges.length); */ - getRanges : - CKEDITOR.env.ie ? + getRanges : (function() + { + var func = CKEDITOR.env.ie ? ( function() { + function getNodeIndex( node ) { return new CKEDITOR.dom.node( node ).getIndex(); } + // Finds the container and offset for a specific boundary // of an IE range. var getBoundaryInformation = function( range, start ) @@ -538,86 +702,126 @@ For licensing, see LICENSE.html or http://ckeditor.com/license range.collapse( start ); // Gets the element that encloses the range entirely. - var parent = range.parentElement(); - var siblings = parent.childNodes; - - var testRange; - - for ( var i = 0 ; i < siblings.length ; i++ ) + var parent = range.parentElement(), + doc = parent.ownerDocument; + + // Empty parent element, e.g. ^ + if ( !parent.hasChildNodes() ) + return { container : parent, offset : 0 }; + + var siblings = parent.children, + child, + sibling, + testRange = range.duplicate(), + startIndex = 0, + endIndex = siblings.length - 1, + index = -1, + position, + distance; + + // Binary search over all element childs to test the range to see whether + // range is right on the boundary of one element. + while ( startIndex <= endIndex ) { - var child = siblings[ i ]; - if ( child.nodeType == 1 ) + index = Math.floor( ( startIndex + endIndex ) / 2 ); + child = siblings[ index ]; + testRange.moveToElementText( child ); + position = testRange.compareEndPoints( 'StartToStart', range ); + + if ( position > 0 ) + endIndex = index - 1; + else if ( position < 0 ) + startIndex = index + 1; + else { - testRange = range.duplicate(); - - testRange.moveToElementText( child ); - - var comparisonStart = testRange.compareEndPoints( 'StartToStart', range ), - comparisonEnd = testRange.compareEndPoints( 'EndToStart', range ); - - testRange.collapse(); - - if ( comparisonStart > 0 ) - break; - // When selection stay at the side of certain self-closing elements, e.g. BR, - // our comparison will never shows an equality. (#4824) - else if ( !comparisonStart - || comparisonEnd == 1 && comparisonStart == -1 ) - return { container : parent, offset : i }; - else if ( !comparisonEnd ) - return { container : parent, offset : i + 1 }; - - testRange = null; + // IE9 report wrong measurement with compareEndPoints when range anchors between two BRs. + // e.g.

text
^

(#7433) + if ( CKEDITOR.env.ie9Compat && child.tagName == 'BR' ) + { + var bmId = 'cke_range_marker'; + range.execCommand( 'CreateBookmark', false, bmId ); + child = doc.getElementsByName( bmId )[ 0 ]; + var offset = getNodeIndex( child ); + parent.removeChild( child ); + return { container : parent, offset : offset }; + } + else + return { container : parent, offset : getNodeIndex( child ) }; } } - if ( !testRange ) + // All childs are text nodes, + // or to the right hand of test range are all text nodes. (#6992) + if ( index == -1 || index == siblings.length - 1 && position < 0 ) { - testRange = range.duplicate(); + // Adapt test range to embrace the entire parent contents. testRange.moveToElementText( parent ); - testRange.collapse( false ); - } + testRange.setEndPoint( 'StartToStart', range ); - testRange.setEndPoint( 'StartToStart', range ); - // IE report line break as CRLF with range.text but - // only LF with textnode.nodeValue, normalize them to avoid - // breaking character counting logic below. (#3949) - var distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; + // IE report line break as CRLF with range.text but + // only LF with textnode.nodeValue, normalize them to avoid + // breaking character counting logic below. (#3949) + distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; - try - { + siblings = parent.childNodes; + + // Actual range anchor right beside test range at the boundary of text node. + if ( !distance ) + { + child = siblings[ siblings.length - 1 ]; + + if ( child.nodeType == CKEDITOR.NODE_ELEMENT ) + return { container : parent, offset : siblings.length }; + else + return { container : child, offset : child.nodeValue.length }; + } + + // Start the measuring until distance overflows, meanwhile count the text nodes. + var i = siblings.length; while ( distance > 0 ) distance -= siblings[ --i ].nodeValue.length; - } - // Measurement in IE could be somtimes wrong because of element. (#4611) + catch( e ) + { + return { container : parent, offset : getNodeIndex( child ) }; + } + } + + return { container : child, offset : position > 0 ? -distance : child.nodeValue.length + distance }; } }; return function() { - var cache = this._.cache; - if ( cache.ranges ) - return cache.ranges; - // IE doesn't have range support (in the W3C way), so we // need to do some magic to transform selections into // CKEDITOR.dom.range instances. @@ -640,11 +844,18 @@ For licensing, see LICENSE.html or http://ckeditor.com/license boundaryInfo = getBoundaryInformation( nativeRange ); range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); - return ( cache.ranges = [ range ] ); + // Correct an invalid IE range case on empty list item. (#5850) + if ( range.endContainer.getPosition( range.startContainer ) & CKEDITOR.POSITION_PRECEDING + && range.endOffset <= range.startContainer.getIndex() ) + { + range.collapse(); + } + + return [ range ]; } else if ( type == CKEDITOR.SELECTION_ELEMENT ) { - var retval = this._.cache.ranges = []; + var retval = []; for ( var i = 0 ; i < nativeRange.length ; i++ ) { @@ -665,38 +876,140 @@ For licensing, see LICENSE.html or http://ckeditor.com/license return retval; } - return ( cache.ranges = [] ); + return []; }; })() : function() { - var cache = this._.cache; - if ( cache.ranges ) - return cache.ranges; // On browsers implementing the W3C range, we simply // tranform the native ranges in CKEDITOR.dom.range // instances. - var ranges = []; - var sel = this.getNative(); + var ranges = [], + range, + doc = this.document, + sel = this.getNative(); if ( !sel ) - return []; + return ranges; + + // On WebKit, it may happen that we'll have no selection + // available. We normalize it here by replicating the + // behavior of other browsers. + if ( !sel.rangeCount ) + { + range = new CKEDITOR.dom.range( doc ); + range.moveToElementEditStart( doc.getBody() ); + ranges.push( range ); + } for ( var i = 0 ; i < sel.rangeCount ; i++ ) { var nativeRange = sel.getRangeAt( i ); - var range = new CKEDITOR.dom.range( this.document ); + + range = new CKEDITOR.dom.range( doc ); range.setStart( new CKEDITOR.dom.node( nativeRange.startContainer ), nativeRange.startOffset ); range.setEnd( new CKEDITOR.dom.node( nativeRange.endContainer ), nativeRange.endOffset ); ranges.push( range ); } + return ranges; + }; - return ( cache.ranges = ranges ); - }, + return function( onlyEditables ) + { + var cache = this._.cache; + if ( cache.ranges && !onlyEditables ) + return cache.ranges; + else if ( !cache.ranges ) + cache.ranges = new CKEDITOR.dom.rangeList( func.call( this ) ); + + // Split range into multiple by read-only nodes. + if ( onlyEditables ) + { + var ranges = cache.ranges; + for ( var i = 0; i < ranges.length; i++ ) + { + var range = ranges[ i ]; + + // Drop range spans inside one ready-only node. + var parent = range.getCommonAncestor(); + if ( parent.isReadOnly() ) + ranges.splice( i, 1 ); + + if ( range.collapsed ) + continue; + + var startContainer = range.startContainer, + endContainer = range.endContainer, + startOffset = range.startOffset, + endOffset = range.endOffset, + walkerRange = range.clone(); + + // Range may start inside a non-editable element, restart range + // by the end of it. + var readOnly; + if ( ( readOnly = startContainer.isReadOnly() ) ) + range.setStartAfter( readOnly ); + + // Enlarge range start/end with text node to avoid walker + // being DOM destructive, it doesn't interfere our checking + // of elements below as well. + if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT ) + { + if ( startOffset >= startContainer.getLength() ) + walkerRange.setStartAfter( startContainer ); + else + walkerRange.setStartBefore( startContainer ); + } + + if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT ) + { + if ( !endOffset ) + walkerRange.setEndBefore( endContainer ); + else + walkerRange.setEndAfter( endContainer ); + } + + // Looking for non-editable element inside the range. + var walker = new CKEDITOR.dom.walker( walkerRange ); + walker.evaluator = function( node ) + { + if ( node.type == CKEDITOR.NODE_ELEMENT + && node.isReadOnly() ) + { + var newRange = range.clone(); + range.setEndBefore( node ); + + // Drop collapsed range around read-only elements, + // it make sure the range list empty when selecting + // only non-editable elements. + if ( range.collapsed ) + ranges.splice( i--, 1 ); + + // Avoid creating invalid range. + if ( !( node.getPosition( walkerRange.endContainer ) & CKEDITOR.POSITION_CONTAINS ) ) + { + newRange.setStartAfter( node ); + if ( !newRange.collapsed ) + ranges.splice( i + 1, 0, newRange ); + } + + return true; + } + + return false; + }; + + walker.next(); + } + } + + return cache.ranges; + }; + })(), /** * Gets the DOM element in which the selection starts. @@ -733,7 +1046,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // Decrease the range content to exclude particial // selected node on the start which doesn't have // visual impact. ( #3231 ) - while ( true ) + while ( 1 ) { var startContainer = range.startContainer, startOffset = range.startOffset; @@ -753,32 +1066,25 @@ For licensing, see LICENSE.html or http://ckeditor.com/license node = node.getChild( range.startOffset ); if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) - return range.startContainer; - - var child = node.getFirst(); - while ( child && child.type == CKEDITOR.NODE_ELEMENT ) + node = range.startContainer; + else { - node = child; - child = child.getFirst(); + var child = node.getFirst(); + while ( child && child.type == CKEDITOR.NODE_ELEMENT ) + { + node = child; + child = child.getFirst(); + } } - - return node; } - } - - if ( CKEDITOR.env.ie ) - { - range = sel.createRange(); - range.collapse( true ); - - node = range.parentElement(); - } - else - { - node = sel.anchorNode; + else + { + node = range.startContainer; + if ( node.type != CKEDITOR.NODE_ELEMENT ) + node = node.getParent(); + } - if ( node && node.nodeType != 1 ) - node = node.parentNode; + node = node.$; } } @@ -842,7 +1148,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // The native selection is not available when locked. this._.cache.nativeSel = {}; - this.isLocked = true; + this.isLocked = 1; // Save this selection inside the DOM document. this.document.setCustomData( 'cke_locked_selection', this ); @@ -862,7 +1168,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license var selectedElement = lockedSelection.getSelectedElement(), ranges = !selectedElement && lockedSelection.getRanges(); - this.isLocked = false; + this.isLocked = 0; this.reset(); doc.getBody().focus(); @@ -876,7 +1182,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if ( !lockedSelection || !restore ) { - this.isLocked = false; + this.isLocked = 0; this.reset(); } }, @@ -900,50 +1206,20 @@ For licensing, see LICENSE.html or http://ckeditor.com/license this._.cache.selectedElement = element; this._.cache.startElement = element; - this._.cache.ranges = [ range ]; + this._.cache.ranges = new CKEDITOR.dom.rangeList( range ); this._.cache.type = CKEDITOR.SELECTION_ELEMENT; return; } - if ( CKEDITOR.env.ie ) - { - this.getNative().empty(); + range = new CKEDITOR.dom.range( element.getDocument() ); + range.setStartBefore( element ); + range.setEndAfter( element ); + range.select(); - try - { - // Try to select the node as a control. - range = this.document.$.body.createControlRange(); - range.addElement( element.$ ); - range.select(); - } - catch(e) - { - // If failed, select it as a text range. - range = this.document.$.body.createTextRange(); - range.moveToElementText( element.$ ); - range.select(); - } - finally - { - this.document.fire( 'selectionchange' ); - } - - this.reset(); - } - else - { - // Create the range for the element. - range = this.document.$.createRange(); - range.selectNode( element.$ ); + this.document.fire( 'selectionchange' ); + this.reset(); - // Select the range. - var sel = this.getNative(); - sel.removeAllRanges(); - sel.addRange( range ); - - this.reset(); - } }, /** @@ -956,8 +1232,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if ( this.isLocked ) { this._.cache.selectedElement = null; - this._.cache.startElement = ranges[ 0 ].getTouchedStartNode(); - this._.cache.ranges = ranges; + this._.cache.startElement = ranges[ 0 ] && ranges[ 0 ].getTouchedStartNode(); + this._.cache.ranges = new CKEDITOR.dom.rangeList( ranges ); this._.cache.type = CKEDITOR.SELECTION_TEXT; return; @@ -965,8 +1241,14 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if ( CKEDITOR.env.ie ) { - // IE doesn't accept multiple ranges selection, so we just - // select the first one. + if ( ranges.length > 1 ) + { + // IE doesn't accept multiple ranges selection, so we join all into one. + var last = ranges[ ranges.length -1 ] ; + ranges[ 0 ].setEnd( last.endContainer, last.endOffset ); + ranges.length = 1; + } + if ( ranges[ 0 ] ) ranges[ 0 ].select(); @@ -975,10 +1257,49 @@ For licensing, see LICENSE.html or http://ckeditor.com/license else { var sel = this.getNative(); - sel.removeAllRanges(); + + // getNative() returns null if iframe is "display:none" in FF. (#6577) + if ( !sel ) + return; + + if ( ranges.length ) + { + sel.removeAllRanges(); + // Remove any existing filling char first. + CKEDITOR.env.webkit && removeFillingChar( this.document ); + } for ( var i = 0 ; i < ranges.length ; i++ ) { + // Joining sequential ranges introduced by + // readonly elements protection. + if ( i < ranges.length -1 ) + { + var left = ranges[ i ], right = ranges[ i +1 ], + between = left.clone(); + between.setStart( left.endContainer, left.endOffset ); + between.setEnd( right.startContainer, right.startOffset ); + + // Don't confused by Firefox adjancent multi-ranges + // introduced by table cells selection. + if ( !between.collapsed ) + { + between.shrink( CKEDITOR.NODE_ELEMENT, true ); + var ancestor = between.getCommonAncestor(), + enclosed = between.getEnclosedNode(); + + // The following cases has to be considered: + // 1. [placeholder] + // 2. (#6621) + if ( ancestor.isReadOnly() || enclosed && enclosed.isReadOnly() ) + { + right.setStart( left.startContainer, left.startOffset ); + ranges.splice( i--, 1 ); + continue; + } + } + } + var range = ranges[ i ]; var nativeRange = this.document.$.createRange(); var startContainer = range.startContainer; @@ -986,16 +1307,58 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // In FF2, if we have a collapsed range, inside an empty // element, we must add something to it otherwise the caret // will not be visible. + // In Opera instead, the selection will be moved out of the + // element. (#4657) if ( range.collapsed && - ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) && + ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) ) && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() ) { startContainer.appendText( '' ); } - nativeRange.setStart( startContainer.$, range.startOffset ); - nativeRange.setEnd( range.endContainer.$, range.endOffset ); + if ( range.collapsed + && CKEDITOR.env.webkit + && rangeRequiresFix( range ) ) + { + // Append a zero-width space so WebKit will not try to + // move the selection by itself (#1272). + var fillingChar = createFillingChar( this.document ); + range.insertNode( fillingChar ) ; + + var next = fillingChar.getNext(); + + // If the filling char is followed by a
, whithout + // having something before it, it'll not blink. + // Let's remove it in this case. + if ( next && !fillingChar.getPrevious() && next.type == CKEDITOR.NODE_ELEMENT && next.getName() == 'br' ) + { + removeFillingChar( this.document ); + range.moveToPosition( next, CKEDITOR.POSITION_BEFORE_START ); + } + else + range.moveToPosition( fillingChar, CKEDITOR.POSITION_AFTER_END ); + } + + nativeRange.setStart( range.startContainer.$, range.startOffset ); + + try + { + nativeRange.setEnd( range.endContainer.$, range.endOffset ); + } + catch ( e ) + { + // There is a bug in Firefox implementation (it would be too easy + // otherwise). The new start can't be after the end (W3C says it can). + // So, let's create a new range and collapse it to the desired point. + if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 ) + { + range.collapse( 1 ); + nativeRange.setEnd( range.endContainer.$, range.endOffset ); + } + else + throw e; + } // Select the range. sel.addRange( nativeRange ); @@ -1013,34 +1376,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license */ createBookmarks : function( serializable ) { - var retval = [], - ranges = this.getRanges(), - length = ranges.length, - bookmark; - for ( var i = 0; i < length ; i++ ) - { - retval.push( bookmark = ranges[ i ].createBookmark( serializable, true ) ); - - serializable = bookmark.serializable; - - var bookmarkStart = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode, - bookmarkEnd = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode; - - // Updating the offset values for rest of ranges which have been mangled(#3256). - for ( var j = i + 1 ; j < length ; j++ ) - { - var dirtyRange = ranges[ j ], - rangeStart = dirtyRange.startContainer, - rangeEnd = dirtyRange.endContainer; - - rangeStart.equals( bookmarkStart.getParent() ) && dirtyRange.startOffset++; - rangeStart.equals( bookmarkEnd.getParent() ) && dirtyRange.startOffset++; - rangeEnd.equals( bookmarkStart.getParent() ) && dirtyRange.endOffset++; - rangeEnd.equals( bookmarkEnd.getParent() ) && dirtyRange.endOffset++; - } - } - - return retval; + return this.getRanges().createBookmarks( serializable ); }, /** @@ -1051,13 +1387,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license */ createBookmarks2 : function( normalized ) { - var bookmarks = [], - ranges = this.getRanges(); - - for ( var i = 0 ; i < ranges.length ; i++ ) - bookmarks.push( ranges[i].createBookmark2( normalized ) ); - - return bookmarks; + return this.getRanges().createBookmarks2( normalized ); }, /** @@ -1112,9 +1442,22 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // V2 function( forceExpand ) { - var collapsed = this.collapsed; - var isStartMarkerAlone; - var dummySpan; + var collapsed = this.collapsed, + isStartMarkerAlone, dummySpan, ieRange; + + // Try to make a object selection. + var selected = this.getEnclosedNode(); + if ( selected ) + { + try + { + ieRange = this.document.$.body.createControlRange(); + ieRange.addElement( selected.$ ); + ieRange.select(); + return; + } + catch( er ) {} + } // IE doesn't support selecting the entire table row/cell, move the selection into cells, e.g. // [... =>
cell
... @@ -1134,7 +1477,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license endNode = bookmark.endNode; // Create the main range which will be used for the selection. - var ieRange = this.document.$.body.createTextRange(); + ieRange = this.document.$.body.createTextRange(); // Position the range at the start boundary. ieRange.moveToElementText( startNode.$ ); @@ -1215,36 +1558,6 @@ For licensing, see LICENSE.html or http://ckeditor.com/license : function() { - var startContainer = this.startContainer; - - // If we have a collapsed range, inside an empty element, we must add - // something to it, otherwise the caret will not be visible. - if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() ) - startContainer.append( new CKEDITOR.dom.text( '' ) ); - - var nativeRange = this.document.$.createRange(); - nativeRange.setStart( startContainer.$, this.startOffset ); - - try - { - nativeRange.setEnd( this.endContainer.$, this.endOffset ); - } - catch ( e ) - { - // There is a bug in Firefox implementation (it would be too easy - // otherwise). The new start can't be after the end (W3C says it can). - // So, let's create a new range and collapse it to the desired point. - if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 ) - { - this.collapse( true ); - nativeRange.setEnd( this.endContainer.$, this.endOffset ); - } - else - throw( e ); - } - - var selection = this.document.getSelection().getNative(); - selection.removeAllRanges(); - selection.addRange( nativeRange ); + this.document.getSelection().selectRanges( [ this ] ); }; } )();
[cell