123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187 |
- /**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or http://ckeditor.com/license
- */
- ( function() {
- // #### checkSelectionChange : START
- // The selection change check basically saves the element parent tree of
- // the current node and check it on successive requests. If there is any
- // change on the tree, then the selectionChange event gets fired.
- function checkSelectionChange() {
- // A possibly available fake-selection.
- var sel = this._.fakeSelection,
- realSel;
- if ( sel ) {
- realSel = this.getSelection( 1 );
- // If real (not locked/stored) selection was moved from hidden container,
- // then the fake-selection must be invalidated.
- if ( !realSel || !realSel.isHidden() ) {
- // Remove the cache from fake-selection references in use elsewhere.
- sel.reset();
- // Have the code using the native selection.
- sel = 0;
- }
- }
- // If not fake-selection is available then get the native selection.
- if ( !sel ) {
- sel = realSel || this.getSelection( 1 );
- // Editor may have no selection at all.
- if ( !sel || sel.getType() == CKEDITOR.SELECTION_NONE )
- return;
- }
- this.fire( 'selectionCheck', sel );
- var currentPath = this.elementPath();
- if ( !currentPath.compare( this._.selectionPreviousPath ) ) {
- // Cache the active element, which we'll eventually lose on Webkit.
- if ( CKEDITOR.env.webkit )
- this._.previousActive = this.document.getActive();
- this._.selectionPreviousPath = currentPath;
- this.fire( 'selectionChange', { selection: sel, path: currentPath } );
- }
- }
- var checkSelectionChangeTimer, checkSelectionChangeTimeoutPending;
- function checkSelectionChangeTimeout() {
- // Firing the "OnSelectionChange" event on every key press started to
- // be too slow. This function guarantees that there will be at least
- // 200ms delay between selection checks.
- checkSelectionChangeTimeoutPending = true;
- if ( checkSelectionChangeTimer )
- return;
- checkSelectionChangeTimeoutExec.call( this );
- checkSelectionChangeTimer = CKEDITOR.tools.setTimeout( checkSelectionChangeTimeoutExec, 200, this );
- }
- function checkSelectionChangeTimeoutExec() {
- checkSelectionChangeTimer = null;
- if ( checkSelectionChangeTimeoutPending ) {
- // Call this with a timeout so the browser properly moves the
- // selection after the mouseup. It happened that the selection was
- // being moved after the mouseup when clicking inside selected text
- // with Firefox.
- CKEDITOR.tools.setTimeout( checkSelectionChange, 0, this );
- checkSelectionChangeTimeoutPending = false;
- }
- }
- // #### checkSelectionChange : END
- var isVisible = CKEDITOR.dom.walker.invisible( 1 );
- // May absorb the caret if:
- // * is a visible node,
- // * is a non-empty element (this rule will accept elements like <strong></strong> because they
- // they were not accepted by the isVisible() check, not not <br> which cannot absorb the caret).
- // See #12621.
- function mayAbsorbCaret( node ) {
- if ( isVisible( node ) )
- return true;
- if ( node.type == CKEDITOR.NODE_ELEMENT && !node.is( CKEDITOR.dtd.$empty ) )
- return true;
- return false;
- }
- function rangeRequiresFix( range ) {
- // Whether we must prevent from absorbing caret by this context node.
- // Also checks whether there's an editable position next to that node.
- function ctxRequiresFix( node, isAtEnd ) {
- // It's ok for us if a text node absorbs the caret, because
- // the caret container element isn't changed then.
- if ( !node || node.type == CKEDITOR.NODE_TEXT )
- return false;
- var testRng = range.clone();
- return testRng[ 'moveToElementEdit' + ( isAtEnd ? 'End' : 'Start' ) ]( node );
- }
- // Range root must be the editable element, it's to avoid creating filler char
- // on any temporary internal selection.
- if ( !( range.root instanceof CKEDITOR.editable ) )
- return false;
- var ct = range.startContainer;
- var previous = range.getPreviousNode( mayAbsorbCaret, null, ct ),
- next = range.getNextNode( mayAbsorbCaret, null, ct );
- // Any adjacent text container may absorb the caret, e.g.
- // <p><strong>text</strong>^foo</p>
- // <p>foo^<strong>text</strong></p>
- // <div>^<p>foo</p></div>
- if ( ctxRequiresFix( previous ) || ctxRequiresFix( next, 1 ) )
- return true;
- // Empty block/inline element is also affected. <span>^</span>, <p>^</p> (#7222)
- // If you found this line confusing check #12655.
- if ( !( previous || next ) && !( ct.type == CKEDITOR.NODE_ELEMENT && ct.isBlockBoundary() && ct.getBogus() ) )
- return true;
- return false;
- }
- function createFillingChar( element ) {
- removeFillingChar( element, false );
- var fillingChar = element.getDocument().createText( '\u200B' );
- element.setCustomData( 'cke-fillingChar', fillingChar );
- return fillingChar;
- }
- function getFillingChar( element ) {
- return element.getCustomData( 'cke-fillingChar' );
- }
- // Checks if a filling char has been used, eventualy removing it (#1272).
- function checkFillingChar( element ) {
- var fillingChar = getFillingChar( element );
- if ( fillingChar ) {
- // Use this flag to avoid removing the filling char right after
- // creating it.
- if ( fillingChar.getCustomData( 'ready' ) )
- removeFillingChar( element );
- else
- fillingChar.setCustomData( 'ready', 1 );
- }
- }
- function removeFillingChar( element, keepSelection ) {
- var fillingChar = element && element.removeCustomData( 'cke-fillingChar' );
- if ( fillingChar ) {
- // Text selection position might get mangled by
- // subsequent dom modification, save it now for restoring. (#8617)
- if ( keepSelection !== false ) {
- var bm,
- sel = element.getDocument().getSelection().getNative(),
- // Be error proof.
- range = sel && sel.type != 'None' && sel.getRangeAt( 0 );
- if ( fillingChar.getLength() > 1 && range && range.intersectsNode( fillingChar.$ ) ) {
- bm = createNativeSelectionBookmark( sel );
- // Anticipate the offset change brought by the removed char.
- var startAffected = sel.anchorNode == fillingChar.$ && sel.anchorOffset > 0,
- endAffected = sel.focusNode == fillingChar.$ && sel.focusOffset > 0;
- startAffected && bm[ 0 ].offset--;
- endAffected && bm[ 1 ].offset--;
- }
- }
- // 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( replaceFillingChar( fillingChar.getText() ) );
- // Restore the bookmark preserving selection's direction.
- if ( bm ) {
- moveNativeSelectionToBookmark( element.getDocument().$, bm );
- }
- }
- }
- function replaceFillingChar( html ) {
- return html.replace( /\u200B( )?/g, function( match ) {
- // #10291 if filling char is followed by a space replace it with nbsp.
- return match[ 1 ] ? '\xa0' : '';
- } );
- }
- function createNativeSelectionBookmark( sel ) {
- return [
- { node: sel.anchorNode, offset: sel.anchorOffset },
- { node: sel.focusNode, offset: sel.focusOffset }
- ];
- }
- function moveNativeSelectionToBookmark( document, bm ) {
- var sel = document.getSelection(),
- range = document.createRange();
- range.setStart( bm[ 0 ].node, bm[ 0 ].offset );
- range.collapse( true );
- sel.removeAllRanges();
- sel.addRange( range );
- sel.extend( bm[ 1 ].node, bm[ 1 ].offset );
- }
- // Creates cke_hidden_sel container and puts real selection there.
- function hideSelection( editor ) {
- var style = CKEDITOR.env.ie ? 'display:none' : 'position:fixed;top:0;left:-1000px',
- hiddenEl = CKEDITOR.dom.element.createFromHtml(
- '<div data-cke-hidden-sel="1" data-cke-temp="1" style="' + style + '"> </div>',
- editor.document );
- editor.fire( 'lockSnapshot' );
- editor.editable().append( hiddenEl );
- // Always use real selection to avoid overriding locked one (http://dev.ckeditor.com/ticket/11104#comment:13).
- var sel = editor.getSelection( 1 ),
- range = editor.createRange(),
- // Cancel selectionchange fired by selectRanges - prevent from firing selectionChange.
- listener = sel.root.on( 'selectionchange', function( evt ) {
- evt.cancel();
- }, null, null, 0 );
- range.setStartAt( hiddenEl, CKEDITOR.POSITION_AFTER_START );
- range.setEndAt( hiddenEl, CKEDITOR.POSITION_BEFORE_END );
- sel.selectRanges( [ range ] );
- listener.removeListener();
- editor.fire( 'unlockSnapshot' );
- // Set this value at the end, so reset() executed by selectRanges()
- // will clean up old hidden selection container.
- editor._.hiddenSelectionContainer = hiddenEl;
- }
- function removeHiddenSelectionContainer( editor ) {
- var hiddenEl = editor._.hiddenSelectionContainer;
- if ( hiddenEl ) {
- var isDirty = editor.checkDirty();
- editor.fire( 'lockSnapshot' );
- hiddenEl.remove();
- editor.fire( 'unlockSnapshot' );
- !isDirty && editor.resetDirty();
- }
- delete editor._.hiddenSelectionContainer;
- }
- // Object containing keystroke handlers for fake selection.
- var fakeSelectionDefaultKeystrokeHandlers = ( function() {
- function leave( right ) {
- return function( evt ) {
- var range = evt.editor.createRange();
- // Move selection only if there's a editable place for it.
- // It no, then do nothing (keystroke will be blocked, widget selection kept).
- if ( range.moveToClosestEditablePosition( evt.selected, right ) )
- evt.editor.getSelection().selectRanges( [ range ] );
- // Prevent default.
- return false;
- };
- }
- function del( right ) {
- return function( evt ) {
- var editor = evt.editor,
- range = editor.createRange(),
- found;
- // If haven't found place for caret on the default side,
- // try to find it on the other side.
- if ( !( found = range.moveToClosestEditablePosition( evt.selected, right ) ) )
- found = range.moveToClosestEditablePosition( evt.selected, !right );
- if ( found )
- editor.getSelection().selectRanges( [ range ] );
- // Save the state before removing selected element.
- editor.fire( 'saveSnapshot' );
- evt.selected.remove();
- // Haven't found any editable space before removing element,
- // try to place the caret anywhere (most likely, in empty editable).
- if ( !found ) {
- range.moveToElementEditablePosition( editor.editable() );
- editor.getSelection().selectRanges( [ range ] );
- }
- editor.fire( 'saveSnapshot' );
- // Prevent default.
- return false;
- };
- }
- var leaveLeft = leave(),
- leaveRight = leave( 1 );
- return {
- 37: leaveLeft, // LEFT
- 38: leaveLeft, // UP
- 39: leaveRight, // RIGHT
- 40: leaveRight, // DOWN
- 8: del(), // BACKSPACE
- 46: del( 1 ) // DELETE
- };
- } )();
- // Handle left, right, delete and backspace keystrokes next to non-editable elements
- // by faking selection on them.
- function getOnKeyDownListener( editor ) {
- var keystrokes = { 37: 1, 39: 1, 8: 1, 46: 1 };
- return function( evt ) {
- var keystroke = evt.data.getKeystroke();
- // Handle only left/right/del/bspace keys.
- if ( !keystrokes[ keystroke ] )
- return;
- var sel = editor.getSelection(),
- ranges = sel.getRanges(),
- range = ranges[ 0 ];
- // Handle only single range and it has to be collapsed.
- if ( ranges.length != 1 || !range.collapsed )
- return;
- var next = range[ keystroke < 38 ? 'getPreviousEditableNode' : 'getNextEditableNode' ]();
- if ( next && next.type == CKEDITOR.NODE_ELEMENT && next.getAttribute( 'contenteditable' ) == 'false' ) {
- editor.getSelection().fake( next );
- evt.data.preventDefault();
- evt.cancel();
- }
- };
- }
- // If fake selection should be applied this function will return instance of
- // CKEDITOR.dom.element which should gain fake selection.
- function getNonEditableFakeSelectionReceiver( ranges ) {
- var enclosedNode, shrinkedNode, clone, range;
- if ( ranges.length == 1 && !( range = ranges[ 0 ] ).collapsed &&
- ( enclosedNode = range.getEnclosedNode() ) && enclosedNode.type == CKEDITOR.NODE_ELEMENT ) {
- // So far we can't say that enclosed element is non-editable. Before checking,
- // we'll shrink range (clone). Shrinking will stop on non-editable range, or
- // innermost element (#11114).
- clone = range.clone();
- clone.shrink( CKEDITOR.SHRINK_ELEMENT, true );
- // If shrinked range still encloses an element, check this one (shrink stops only on non-editable elements).
- if ( ( shrinkedNode = clone.getEnclosedNode() ) && shrinkedNode.type == CKEDITOR.NODE_ELEMENT )
- enclosedNode = shrinkedNode;
- if ( enclosedNode.getAttribute( 'contenteditable' ) == 'false' )
- return enclosedNode;
- }
- }
- // Fix ranges which may end after hidden selection container.
- // Note: this function may only be used if hidden selection container
- // is not in DOM any more.
- function fixRangesAfterHiddenSelectionContainer( ranges, root ) {
- var range;
- for ( var i = 0; i < ranges.length; ++i ) {
- range = ranges[ i ];
- if ( range.endContainer.equals( root ) ) {
- // We can use getChildCount() because hidden selection container is not in DOM.
- range.endOffset = Math.min( range.endOffset, root.getChildCount() );
- }
- }
- }
- // Extract only editable part or ranges.
- // Note: this function modifies ranges list!
- // @param {CKEDITOR.dom.rangeList} ranges
- function extractEditableRanges( 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;
- // Range may start inside a non-editable element,
- // replace the range start after it.
- if ( range.startContainer.isReadOnly() ) {
- var current = range.startContainer,
- isElement;
- while ( current ) {
- isElement = current.type == CKEDITOR.NODE_ELEMENT;
- if ( ( isElement && current.is( 'body' ) ) || !current.isReadOnly() )
- break;
- if ( isElement && current.getAttribute( 'contentEditable' ) == 'false' )
- range.setStartAfter( current );
- current = current.getParent();
- }
- }
- var startContainer = range.startContainer,
- endContainer = range.endContainer,
- startOffset = range.startOffset,
- endOffset = range.endOffset,
- walkerRange = range.clone();
- // 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 ranges;
- }
- // Setup all editor instances for the necessary selection hooks.
- CKEDITOR.on( 'instanceCreated', function( ev ) {
- var editor = ev.editor;
- editor.on( 'contentDom', function() {
- var doc = editor.document,
- outerDoc = CKEDITOR.document,
- editable = editor.editable(),
- body = doc.getBody(),
- html = doc.getDocumentElement();
- var isInline = editable.isInline();
- var restoreSel,
- lastSel;
- // Give the editable an initial selection on first focus,
- // put selection at a consistent position at the start
- // of the contents. (#9507)
- if ( CKEDITOR.env.gecko ) {
- editable.attachListener( editable, 'focus', function( evt ) {
- evt.removeListener();
- if ( restoreSel !== 0 ) {
- var nativ = editor.getSelection().getNative();
- // Do it only if the native selection is at an unwanted
- // place (at the very start of the editable). #10119
- if ( nativ && nativ.isCollapsed && nativ.anchorNode == editable.$ ) {
- var rng = editor.createRange();
- rng.moveToElementEditStart( editable );
- rng.select();
- }
- }
- }, null, null, -2 );
- }
- // Plays the magic here to restore/save dom selection on editable focus/blur.
- editable.attachListener( editable, CKEDITOR.env.webkit ? 'DOMFocusIn' : 'focus', function() {
- // On Webkit we use DOMFocusIn which is fired more often than focus - e.g. when moving from main editable
- // to nested editable (or the opposite). Unlock selection all, but restore only when it was locked
- // for the same active element, what will e.g. mean restoring after displaying dialog.
- if ( restoreSel && CKEDITOR.env.webkit )
- restoreSel = editor._.previousActive && editor._.previousActive.equals( doc.getActive() );
- editor.unlockSelection( restoreSel );
- restoreSel = 0;
- }, null, null, -1 );
- // Disable selection restoring when clicking in.
- editable.attachListener( editable, 'mousedown', function() {
- restoreSel = 0;
- } );
- // Save a cloned version of current selection.
- function saveSel() {
- lastSel = new CKEDITOR.dom.selection( editor.getSelection() );
- lastSel.lock();
- }
- // Browsers could loose the selection once the editable lost focus,
- // in such case we need to reproduce it by saving a locked selection
- // and restoring it upon focus gain.
- if ( CKEDITOR.env.ie || isInline ) {
- // For old IEs, we can retrieve the last correct DOM selection upon the "beforedeactivate" event.
- // For the rest, a more frequent check is required for each selection change made.
- if ( isMSSelection )
- editable.attachListener( editable, 'beforedeactivate', saveSel, null, null, -1 );
- else
- editable.attachListener( editor, 'selectionCheck', saveSel, null, null, -1 );
- // Lock the selection and mark it to be restored.
- // On Webkit we use DOMFocusOut which is fired more often than blur. I.e. it will also be
- // fired when nested editable is blurred.
- editable.attachListener( editable, CKEDITOR.env.webkit ? 'DOMFocusOut' : 'blur', function() {
- editor.lockSelection( lastSel );
- restoreSel = 1;
- }, null, null, -1 );
- // Disable selection restoring when clicking in.
- editable.attachListener( editable, 'mousedown', function() {
- restoreSel = 0;
- } );
- }
- // The following selection-related fixes only apply to classic (`iframe`-based) editable.
- if ( CKEDITOR.env.ie && !isInline ) {
- var scroll;
- editable.attachListener( editable, '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.getSelection();
- if ( !sel || sel.getType() == CKEDITOR.SELECTION_NONE )
- scroll = editor.window.getScrollPosition();
- }
- } );
- editable.attachListener( editable, 'mouseup', function( evt ) {
- // 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;
- } );
- // When content doc is in standards mode, IE doesn't focus the editor when
- // clicking at the region below body (on html element) content, we emulate
- // the normal behavior on old IEs. (#1659, #7932)
- if ( doc.$.compatMode != 'BackCompat' ) {
- if ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) {
- html.on( 'mousedown', function( evt ) {
- evt = evt.data;
- // Expand the text range along with mouse move.
- function onHover( evt ) {
- evt = evt.data.$;
- if ( textRng ) {
- // Read the current cursor.
- var rngEnd = body.$.createTextRange();
- moveRangeToPoint( rngEnd, evt.clientX, evt.clientY );
- // Handle drag directions.
- textRng.setEndPoint(
- startRng.compareEndPoints( 'StartToStart', rngEnd ) < 0 ?
- 'EndToEnd' : 'StartToStart', rngEnd );
- // Update selection with new range.
- textRng.select();
- }
- }
- function removeListeners() {
- outerDoc.removeListener( 'mouseup', onSelectEnd );
- html.removeListener( 'mouseup', onSelectEnd );
- }
- function onSelectEnd() {
- html.removeListener( 'mousemove', onHover );
- removeListeners();
- // Make it in effect on mouse up. (#9022)
- textRng.select();
- }
- // We're sure that the click happens at the region
- // below body, but not on scrollbar.
- if ( evt.getTarget().is( 'html' ) &&
- evt.$.y < html.$.clientHeight &&
- evt.$.x < html.$.clientWidth ) {
- // Start to build the text range.
- var textRng = body.$.createTextRange();
- moveRangeToPoint( textRng, evt.$.clientX, evt.$.clientY );
- // Records the dragging start of the above text range.
- var startRng = textRng.duplicate();
- html.on( 'mousemove', onHover );
- outerDoc.on( 'mouseup', onSelectEnd );
- html.on( 'mouseup', onSelectEnd );
- }
- } );
- }
- // It's much simpler for IE8+, we just need to reselect the reported range.
- // This hack does not work on IE>=11 because there's no old selection&range APIs.
- if ( CKEDITOR.env.version > 7 && CKEDITOR.env.version < 11 ) {
- html.on( 'mousedown', function( evt ) {
- if ( evt.data.getTarget().is( 'html' ) ) {
- // Limit the text selection mouse move inside of editable. (#9715)
- outerDoc.on( 'mouseup', onSelectEnd );
- html.on( 'mouseup', onSelectEnd );
- }
- } );
- }
- }
- }
- // We check the selection change:
- // 1. Upon "selectionchange" event from the editable element. (which might be faked event fired by our code)
- // 2. After the accomplish of keyboard and mouse events.
- editable.attachListener( editable, 'selectionchange', checkSelectionChange, editor );
- editable.attachListener( editable, 'keyup', checkSelectionChangeTimeout, editor );
- // Always fire the selection change on focus gain.
- // On Webkit do this on DOMFocusIn, because the selection is unlocked on it too and
- // we need synchronization between those listeners to not lost cached editor._.previousActive property
- // (which is updated on selectionCheck).
- editable.attachListener( editable, CKEDITOR.env.webkit ? 'DOMFocusIn' : 'focus', function() {
- editor.forceNextSelectionCheck();
- editor.selectionChange( 1 );
- } );
- // #9699: On Webkit&Gecko in inline editor we have to check selection when it was changed
- // by dragging and releasing mouse button outside editable. Dragging (mousedown)
- // has to be initialized in editable, but for mouseup we listen on document element.
- if ( isInline && ( CKEDITOR.env.webkit || CKEDITOR.env.gecko ) ) {
- var mouseDown;
- editable.attachListener( editable, 'mousedown', function() {
- mouseDown = 1;
- } );
- editable.attachListener( doc.getDocumentElement(), 'mouseup', function() {
- if ( mouseDown )
- checkSelectionChangeTimeout.call( editor );
- mouseDown = 0;
- } );
- }
- // In all other cases listen on simple mouseup over editable, as we did before #9699.
- //
- // Use document instead of editable in non-IEs for observing mouseup
- // since editable won't fire the event if selection process started within iframe and ended out
- // of the editor (#9851).
- else {
- editable.attachListener( CKEDITOR.env.ie ? editable : doc.getDocumentElement(), 'mouseup', checkSelectionChangeTimeout, editor );
- }
- if ( CKEDITOR.env.webkit ) {
- // Before keystroke is handled by editor, check to remove the filling char.
- editable.attachListener( doc, 'keydown', function( evt ) {
- var key = evt.data.getKey();
- // Remove the filling char before some keys get
- // executed, so they'll not get blocked by it.
- switch ( key ) {
- case 13: // ENTER
- case 33: // PAGEUP
- case 34: // PAGEDOWN
- case 35: // HOME
- case 36: // END
- case 37: // LEFT-ARROW
- case 39: // RIGHT-ARROW
- case 8: // BACKSPACE
- case 45: // INS
- case 46: // DEl
- removeFillingChar( editable );
- }
- }, null, null, -1 );
- }
- // Automatically select non-editable element when navigating into
- // it by left/right or backspace/del keys.
- editable.attachListener( editable, 'keydown', getOnKeyDownListener( editor ), null, null, -1 );
- function moveRangeToPoint( range, x, y ) {
- // Error prune in IE7. (#9034, #9110)
- try {
- range.moveToPoint( x, y );
- } catch ( e ) {}
- }
- function removeListeners() {
- outerDoc.removeListener( 'mouseup', onSelectEnd );
- html.removeListener( 'mouseup', onSelectEnd );
- }
- function onSelectEnd() {
- removeListeners();
- // The event is not fired when clicking on the scrollbars,
- // so we can safely check the following to understand
- // whether the empty space following <body> has been clicked.
- var sel = CKEDITOR.document.$.selection,
- range = sel.createRange();
- // The selection range is reported on host, but actually it should applies to the content doc.
- if ( sel.type != 'None' && range.parentElement().ownerDocument == doc.$ )
- range.select();
- }
- } );
- editor.on( 'setData', function() {
- // Invalidate locked selection when unloading DOM.
- // (#9521, #5217#comment:32 and #11500#comment:11)
- editor.unlockSelection();
- // Webkit's selection will mess up after the data loading.
- if ( CKEDITOR.env.webkit )
- clearSelection();
- } );
- // Catch all the cases which above setData listener couldn't catch.
- // For example: switching to source mode and destroying editor.
- editor.on( 'contentDomUnload', function() {
- editor.unlockSelection();
- } );
- // IE9 might cease to work if there's an object selection inside the iframe (#7639).
- if ( CKEDITOR.env.ie9Compat )
- editor.on( 'beforeDestroy', clearSelection, null, null, 9 );
- // Check selection change on data reload.
- editor.on( 'dataReady', function() {
- // Clean up fake selection after setting data.
- delete editor._.fakeSelection;
- delete editor._.hiddenSelectionContainer;
- editor.selectionChange( 1 );
- } );
- // When loaded data are ready check whether hidden selection container was not loaded.
- editor.on( 'loadSnapshot', function() {
- var isElement = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT ),
- // TODO replace with el.find() which will be introduced in #9764,
- // because it may happen that hidden sel container won't be the last element.
- last = editor.editable().getLast( isElement );
- if ( last && last.hasAttribute( 'data-cke-hidden-sel' ) ) {
- last.remove();
- // Firefox does a very unfortunate thing. When a non-editable element is the only
- // element in the editable, when we remove the hidden selection container, Firefox
- // will insert a bogus <br> at the beginning of the editable...
- // See: https://bugzilla.mozilla.org/show_bug.cgi?id=911201
- //
- // This behavior is never desired because this <br> pushes the content lower, but in
- // this case it is especially dangerous, because it happens when a bookmark is being restored.
- // Since this <br> is inserted at the beginning it changes indexes and thus breaks the bookmark2
- // what results in errors.
- //
- // So... let's revert what Firefox broke.
- if ( CKEDITOR.env.gecko ) {
- var first = editor.editable().getFirst( isElement );
- if ( first && first.is( 'br' ) && first.getAttribute( '_moz_editor_bogus_node' ) ) {
- first.remove();
- }
- }
- }
- }, null, null, 100 );
- editor.on( 'key', function( evt ) {
- if ( editor.mode != 'wysiwyg' )
- return;
- var sel = editor.getSelection();
- if ( !sel.isFake )
- return;
- var handler = fakeSelectionDefaultKeystrokeHandlers[ evt.data.keyCode ];
- if ( handler )
- return handler( { editor: editor, selected: sel.getSelectedElement(), selection: sel, keyEvent: evt } );
- } );
- function clearSelection() {
- var sel = editor.getSelection();
- sel && sel.removeAllRanges();
- }
- } );
- CKEDITOR.on( 'instanceReady', function( evt ) {
- var editor = evt.editor,
- fillingCharBefore,
- selectionBookmark;
- // 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.editable() );
- }, null, null, -1 );
- editor.on( 'beforeSetMode', function() {
- removeFillingChar( editor.editable() );
- }, null, null, -1 );
- editor.on( 'beforeUndoImage', beforeData );
- editor.on( 'afterUndoImage', afterData );
- editor.on( 'beforeGetData', beforeData, null, null, 0 );
- editor.on( 'getData', afterData );
- }
- function beforeData() {
- var editable = editor.editable();
- if ( !editable )
- return;
- var fillingChar = getFillingChar( editable );
- if ( fillingChar ) {
- // If the selection's focus or anchor is located in the filling char's text node,
- // we need to restore the selection in afterData, because it will be lost
- // when setting text. Selection's direction must be preserved.
- // (#7437, #12489, #12491 comment:3)
- var sel = editor.document.$.getSelection();
- if ( sel.type != 'None' && ( sel.anchorNode == fillingChar.$ || sel.focusNode == fillingChar.$ ) )
- selectionBookmark = createNativeSelectionBookmark( sel );
- fillingCharBefore = fillingChar.getText();
- fillingChar.setText( replaceFillingChar( fillingCharBefore ) );
- }
- }
- function afterData() {
- var editable = editor.editable();
- if ( !editable )
- return;
- var fillingChar = getFillingChar( editable );
- if ( fillingChar ) {
- fillingChar.setText( fillingCharBefore );
- if ( selectionBookmark ) {
- moveNativeSelectionToBookmark( editor.document.$, selectionBookmark );
- selectionBookmark = null;
- }
- }
- }
- } );
- /**
- * Check the selection change in editor and potentially fires
- * the {@link CKEDITOR.editor#event-selectionChange} event.
- *
- * @method
- * @member CKEDITOR.editor
- * @param {Boolean} [checkNow=false] Force the check to happen immediately
- * instead of coming with a timeout delay (default).
- */
- CKEDITOR.editor.prototype.selectionChange = function( checkNow ) {
- ( checkNow ? checkSelectionChange : checkSelectionChangeTimeout ).call( this );
- };
- /**
- * Retrieve the editor selection in scope of editable element.
- *
- * **Note:** Since the native browser selection provides only one single
- * selection at a time per document, so if editor's editable element has lost focus,
- * this method will return a null value unless the {@link CKEDITOR.editor#lockSelection}
- * has been called beforehand so the saved selection is retrieved.
- *
- * var selection = CKEDITOR.instances.editor1.getSelection();
- * alert( selection.getType() );
- *
- * @method
- * @member CKEDITOR.editor
- * @param {Boolean} forceRealSelection Return real selection, instead of saved or fake one.
- * @returns {CKEDITOR.dom.selection} A selection object or null if not available for the moment.
- */
- CKEDITOR.editor.prototype.getSelection = function( forceRealSelection ) {
- // Check if there exists a locked or fake selection.
- if ( ( this._.savedSelection || this._.fakeSelection ) && !forceRealSelection )
- return this._.savedSelection || this._.fakeSelection;
- // Editable element might be absent or editor might not be in a wysiwyg mode.
- var editable = this.editable();
- return editable && this.mode == 'wysiwyg' ? new CKEDITOR.dom.selection( editable ) : null;
- };
- /**
- * Locks the selection made in the editor in order to make it possible to
- * manipulate it without browser interference. A locked selection is
- * cached and remains unchanged until it is released with the
- * {@link CKEDITOR.editor#unlockSelection} method.
- *
- * @method
- * @member CKEDITOR.editor
- * @param {CKEDITOR.dom.selection} [sel] Specify the selection to be locked.
- * @returns {Boolean} `true` if selection was locked.
- */
- CKEDITOR.editor.prototype.lockSelection = function( sel ) {
- sel = sel || this.getSelection( 1 );
- if ( sel.getType() != CKEDITOR.SELECTION_NONE ) {
- !sel.isLocked && sel.lock();
- this._.savedSelection = sel;
- return true;
- }
- return false;
- };
- /**
- * Unlocks the selection made in the editor and locked with the
- * {@link CKEDITOR.editor#unlockSelection} method. An unlocked selection
- * is no longer cached and can be changed.
- *
- * @method
- * @member CKEDITOR.editor
- * @param {Boolean} [restore] If set to `true`, the selection is
- * restored back to the selection saved earlier by using the
- * {@link CKEDITOR.dom.selection#lock} method.
- */
- CKEDITOR.editor.prototype.unlockSelection = function( restore ) {
- var sel = this._.savedSelection;
- if ( sel ) {
- sel.unlock( restore );
- delete this._.savedSelection;
- return true;
- }
- return false;
- };
- /**
- * @method
- * @member CKEDITOR.editor
- * @todo
- */
- CKEDITOR.editor.prototype.forceNextSelectionCheck = function() {
- delete this._.selectionPreviousPath;
- };
- /**
- * Gets the current selection in context of the document's body element.
- *
- * var selection = CKEDITOR.instances.editor1.document.getSelection();
- * alert( selection.getType() );
- *
- * @method
- * @member CKEDITOR.dom.document
- * @returns {CKEDITOR.dom.selection} A selection object.
- */
- CKEDITOR.dom.document.prototype.getSelection = function() {
- return new CKEDITOR.dom.selection( this );
- };
- /**
- * Select this range as the only one with {@link CKEDITOR.dom.selection#selectRanges}.
- *
- * @method
- * @returns {CKEDITOR.dom.selection}
- * @member CKEDITOR.dom.range
- */
- CKEDITOR.dom.range.prototype.select = function() {
- var sel = this.root instanceof CKEDITOR.editable ? this.root.editor.getSelection() : new CKEDITOR.dom.selection( this.root );
- sel.selectRanges( [ this ] );
- return sel;
- };
- /**
- * No selection.
- *
- * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE )
- * alert( 'Nothing is selected' );
- *
- * @readonly
- * @property {Number} [=1]
- * @member CKEDITOR
- */
- CKEDITOR.SELECTION_NONE = 1;
- /**
- * A text or a collapsed selection.
- *
- * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT )
- * alert( 'A text is selected' );
- *
- * @readonly
- * @property {Number} [=2]
- * @member CKEDITOR
- */
- CKEDITOR.SELECTION_TEXT = 2;
- /**
- * Element selection.
- *
- * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT )
- * alert( 'An element is selected' );
- *
- * @readonly
- * @property {Number} [=3]
- * @member CKEDITOR
- */
- CKEDITOR.SELECTION_ELEMENT = 3;
- var isMSSelection = typeof window.getSelection != 'function',
- nextRev = 1;
- /**
- * Manipulates the selection within a DOM element. If the current browser selection
- * spans outside of the element, an empty selection object is returned.
- *
- * Despite the fact that selection's constructor allows to create selection instances,
- * usually it's better to get selection from the editor instance:
- *
- * var sel = editor.getSelection();
- *
- * See {@link CKEDITOR.editor#getSelection}.
- *
- * @class
- * @constructor Creates a selection class instance.
- *
- * // Selection scoped in document.
- * var sel = new CKEDITOR.dom.selection( CKEDITOR.document );
- *
- * // Selection scoped in element with 'editable' id.
- * var sel = new CKEDITOR.dom.selection( CKEDITOR.document.getById( 'editable' ) );
- *
- * // Cloning selection.
- * var clone = new CKEDITOR.dom.selection( sel );
- *
- * @param {CKEDITOR.dom.document/CKEDITOR.dom.element/CKEDITOR.dom.selection} target
- * The DOM document/element that the DOM selection is restrained to. Only selection which spans
- * within the target element is considered as valid.
- *
- * If {@link CKEDITOR.dom.selection} is passed, then its clone will be created.
- */
- CKEDITOR.dom.selection = function( target ) {
- // Target is a selection - clone it.
- if ( target instanceof CKEDITOR.dom.selection ) {
- var selection = target;
- target = target.root;
- }
- var isElement = target instanceof CKEDITOR.dom.element,
- root;
- this.rev = selection ? selection.rev : nextRev++;
- this.document = target instanceof CKEDITOR.dom.document ? target : target.getDocument();
- this.root = root = isElement ? target : this.document.getBody();
- this.isLocked = 0;
- this._ = {
- cache: {}
- };
- // Clone selection.
- if ( selection ) {
- CKEDITOR.tools.extend( this._.cache, selection._.cache );
- this.isFake = selection.isFake;
- this.isLocked = selection.isLocked;
- return this;
- }
- // Check whether browser focus is really inside of the editable element.
- var nativeSel = this.getNative(),
- rangeParent,
- range;
- if ( nativeSel ) {
- if ( nativeSel.getRangeAt ) {
- range = nativeSel.rangeCount && nativeSel.getRangeAt( 0 );
- rangeParent = range && new CKEDITOR.dom.node( range.commonAncestorContainer );
- }
- // For old IEs.
- else {
- // Sometimes, mostly when selection is close to the table or hr,
- // IE throws "Unspecified error".
- try {
- range = nativeSel.createRange();
- } catch ( err ) {}
- rangeParent = range && CKEDITOR.dom.element.get( range.item && range.item( 0 ) || range.parentElement() );
- }
- }
- // Selection out of concerned range, empty the selection.
- // TODO check whether this condition cannot be reverted to its old
- // form (commented out) after we closed #10438.
- //if ( !( rangeParent && ( root.equals( rangeParent ) || root.contains( rangeParent ) ) ) ) {
- if ( !(
- rangeParent &&
- ( rangeParent.type == CKEDITOR.NODE_ELEMENT || rangeParent.type == CKEDITOR.NODE_TEXT ) &&
- ( this.root.equals( rangeParent ) || this.root.contains( rangeParent ) )
- ) ) {
- this._.cache.type = CKEDITOR.SELECTION_NONE;
- this._.cache.startElement = null;
- this._.cache.selectedElement = null;
- this._.cache.selectedText = '';
- this._.cache.ranges = new CKEDITOR.dom.rangeList();
- }
- return this;
- };
- 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, thead: 1, tfoot: 1 };
- CKEDITOR.dom.selection.prototype = {
- /**
- * Gets the native selection object from the browser.
- *
- * var selection = editor.getSelection().getNative();
- *
- * @returns {Object} The native browser selection object.
- */
- getNative: function() {
- if ( this._.cache.nativeSel !== undefined )
- return this._.cache.nativeSel;
- return ( this._.cache.nativeSel = isMSSelection ? this.document.$.selection : this.document.getWindow().$.getSelection() );
- },
- /**
- * Gets the type of the current selection. The following values are
- * available:
- *
- * * {@link CKEDITOR#SELECTION_NONE} (1): No selection.
- * * {@link CKEDITOR#SELECTION_TEXT} (2): A text or a collapsed selection is selected.
- * * {@link CKEDITOR#SELECTION_ELEMENT} (3): An element is selected.
- *
- * Example:
- *
- * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT )
- * alert( 'A text is selected' );
- *
- * @method
- * @returns {Number} One of the following constant values: {@link CKEDITOR#SELECTION_NONE},
- * {@link CKEDITOR#SELECTION_TEXT} or {@link CKEDITOR#SELECTION_ELEMENT}.
- */
- getType: isMSSelection ?
- function() {
- var cache = this._.cache;
- if ( cache.type )
- return cache.type;
- var type = CKEDITOR.SELECTION_NONE;
- try {
- var sel = this.getNative(),
- ieType = sel.type;
- if ( ieType == 'Text' )
- type = CKEDITOR.SELECTION_TEXT;
- if ( ieType == 'Control' )
- type = CKEDITOR.SELECTION_ELEMENT;
- // It is possible that we can still get a text range
- // object even when type == 'None' is returned by IE.
- // So we'd better check the object returned by
- // createRange() rather than by looking at the type.
- if ( sel.createRange().parentElement() )
- type = CKEDITOR.SELECTION_TEXT;
- } catch ( e ) {}
- return ( cache.type = type );
- } : function() {
- var cache = this._.cache;
- if ( cache.type )
- return cache.type;
- var type = CKEDITOR.SELECTION_TEXT;
- var sel = this.getNative();
- if ( !( sel && sel.rangeCount ) )
- type = CKEDITOR.SELECTION_NONE;
- else if ( sel.rangeCount == 1 ) {
- // Check if the actual selection is a control (IMG,
- // TABLE, HR, etc...).
- var range = sel.getRangeAt( 0 ),
- startContainer = range.startContainer;
- if ( startContainer == range.endContainer && startContainer.nodeType == 1 &&
- ( range.endOffset - range.startOffset ) == 1 &&
- styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] ) {
- type = CKEDITOR.SELECTION_ELEMENT;
- }
- }
- return ( cache.type = type );
- },
- /**
- * Retrieves the {@link CKEDITOR.dom.range} instances that represent the current selection.
- *
- * Note: Some browsers return multiple ranges even for a continuous selection. Firefox, for example, returns
- * one range for each table cell when one or more table rows are selected.
- *
- * var ranges = selection.getRanges();
- * alert( ranges.length );
- *
- * @method
- * @param {Boolean} [onlyEditables] If set to `true`, this function retrives editable ranges only.
- * @returns {Array} Range instances that represent the current selection.
- */
- getRanges: ( function() {
- var func = isMSSelection ? ( 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 ) {
- // Creates a collapsed range at the requested boundary.
- range = range.duplicate();
- range.collapse( start );
- // Gets the element that encloses the range entirely.
- var parent = range.parentElement();
- // Empty parent element, e.g. <i>^</i>
- 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, container;
- // 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 ) {
- 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
- return { container: parent, offset: getNodeIndex( child ) };
- }
- // 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 ) {
- // Adapt test range to embrace the entire parent contents.
- testRange.moveToElementText( parent );
- 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)
- distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
- 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_TEXT )
- 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 && i > 0 ) {
- sibling = siblings[ --i ];
- if ( sibling.nodeType == CKEDITOR.NODE_TEXT ) {
- container = sibling;
- distance -= sibling.nodeValue.length;
- }
- }
- return { container: container, offset: -distance };
- }
- // Test range was one offset beyond OR behind the anchored text node.
- else {
- // Adapt one side of test range to the actual range
- // for measuring the offset between them.
- testRange.collapse( position > 0 ? true : false );
- testRange.setEndPoint( position > 0 ? 'StartToStart' : 'EndToStart', 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)
- distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
- // Actual range anchor right beside test range at the inner boundary of text node.
- if ( !distance )
- return { container: parent, offset: getNodeIndex( child ) + ( position > 0 ? 0 : 1 ) };
- // Start the measuring until distance overflows, meanwhile count the text nodes.
- while ( distance > 0 ) {
- try {
- sibling = child[ position > 0 ? 'previousSibling' : 'nextSibling' ];
- if ( sibling.nodeType == CKEDITOR.NODE_TEXT ) {
- distance -= sibling.nodeValue.length;
- container = sibling;
- }
- child = sibling;
- }
- // Measurement in IE could be somtimes wrong because of <select> element. (#4611)
- catch ( e ) {
- return { container: parent, offset: getNodeIndex( child ) };
- }
- }
- return { container: container, offset: position > 0 ? -distance : container.nodeValue.length + distance };
- }
- };
- return function() {
- // 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.
- var sel = this.getNative(),
- nativeRange = sel && sel.createRange(),
- type = this.getType(),
- range;
- if ( !sel )
- return [];
- if ( type == CKEDITOR.SELECTION_TEXT ) {
- range = new CKEDITOR.dom.range( this.root );
- var boundaryInfo = getBoundaryInformation( nativeRange, true );
- range.setStart( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
- boundaryInfo = getBoundaryInformation( nativeRange );
- range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
- // 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 = [];
- for ( var i = 0; i < nativeRange.length; i++ ) {
- var element = nativeRange.item( i ),
- parentElement = element.parentNode,
- j = 0;
- range = new CKEDITOR.dom.range( this.root );
- for ( ; j < parentElement.childNodes.length && parentElement.childNodes[ j ] != element; j++ ) {
- }
- range.setStart( new CKEDITOR.dom.node( parentElement ), j );
- range.setEnd( new CKEDITOR.dom.node( parentElement ), j + 1 );
- retval.push( range );
- }
- return retval;
- }
- return [];
- };
- } )() :
- function() {
- // On browsers implementing the W3C range, we simply
- // tranform the native ranges in CKEDITOR.dom.range
- // instances.
- var ranges = [],
- range,
- sel = this.getNative();
- if ( !sel )
- return ranges;
- for ( var i = 0; i < sel.rangeCount; i++ ) {
- var nativeRange = sel.getRangeAt( i );
- range = new CKEDITOR.dom.range( this.root );
- 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 function( onlyEditables ) {
- var cache = this._.cache,
- ranges = cache.ranges;
- if ( !ranges )
- cache.ranges = ranges = new CKEDITOR.dom.rangeList( func.call( this ) );
- if ( !onlyEditables )
- return ranges;
- // Split range into multiple by read-only nodes.
- // Clone ranges array to avoid changing cached ranges (#11493).
- return extractEditableRanges( new CKEDITOR.dom.rangeList( ranges.slice() ) );
- };
- } )(),
- /**
- * Gets the DOM element in which the selection starts.
- *
- * var element = editor.getSelection().getStartElement();
- * alert( element.getName() );
- *
- * @returns {CKEDITOR.dom.element} The element at the beginning of the selection.
- */
- getStartElement: function() {
- var cache = this._.cache;
- if ( cache.startElement !== undefined )
- return cache.startElement;
- var node;
- switch ( this.getType() ) {
- case CKEDITOR.SELECTION_ELEMENT:
- return this.getSelectedElement();
- case CKEDITOR.SELECTION_TEXT:
- var range = this.getRanges()[ 0 ];
- if ( range ) {
- if ( !range.collapsed ) {
- range.optimize();
- // Decrease the range content to exclude particial
- // selected node on the start which doesn't have
- // visual impact. ( #3231 )
- while ( 1 ) {
- var startContainer = range.startContainer,
- startOffset = range.startOffset;
- // Limit the fix only to non-block elements.(#3950)
- if ( startOffset == ( startContainer.getChildCount ? startContainer.getChildCount() : startContainer.getLength() ) && !startContainer.isBlockBoundary() )
- range.setStartAfter( startContainer );
- else
- break;
- }
- node = range.startContainer;
- if ( node.type != CKEDITOR.NODE_ELEMENT )
- return node.getParent();
- node = node.getChild( range.startOffset );
- if ( !node || node.type != CKEDITOR.NODE_ELEMENT )
- node = range.startContainer;
- else {
- var child = node.getFirst();
- while ( child && child.type == CKEDITOR.NODE_ELEMENT ) {
- node = child;
- child = child.getFirst();
- }
- }
- } else {
- node = range.startContainer;
- if ( node.type != CKEDITOR.NODE_ELEMENT )
- node = node.getParent();
- }
- node = node.$;
- }
- }
- return cache.startElement = ( node ? new CKEDITOR.dom.element( node ) : null );
- },
- /**
- * Gets the currently selected element.
- *
- * var element = editor.getSelection().getSelectedElement();
- * alert( element.getName() );
- *
- * @returns {CKEDITOR.dom.element} The selected element. Null if no
- * selection is available or the selection type is not {@link CKEDITOR#SELECTION_ELEMENT}.
- */
- getSelectedElement: function() {
- var cache = this._.cache;
- if ( cache.selectedElement !== undefined )
- return cache.selectedElement;
- var self = this;
- var node = CKEDITOR.tools.tryThese(
- // Is it native IE control type selection?
- function() {
- return self.getNative().createRange().item( 0 );
- },
- // Figure it out by checking if there's a single enclosed
- // node of the range.
- function() {
- var range = self.getRanges()[ 0 ].clone(),
- enclosed, selected;
- // Check first any enclosed element, e.g. <ul>[<li><a href="#">item</a></li>]</ul>
- for ( var i = 2; i && !( ( enclosed = range.getEnclosedNode() ) && ( enclosed.type == CKEDITOR.NODE_ELEMENT ) && styleObjectElements[ enclosed.getName() ] && ( selected = enclosed ) ); i-- ) {
- // Then check any deep wrapped element, e.g. [<b><i><img /></i></b>]
- range.shrink( CKEDITOR.SHRINK_ELEMENT );
- }
- return selected && selected.$;
- }
- );
- return cache.selectedElement = ( node ? new CKEDITOR.dom.element( node ) : null );
- },
- /**
- * Retrieves the text contained within the range. An empty string is returned for non-text selection.
- *
- * var text = editor.getSelection().getSelectedText();
- * alert( text );
- *
- * @since 3.6.1
- * @returns {String} A string of text within the current selection.
- */
- getSelectedText: function() {
- var cache = this._.cache;
- if ( cache.selectedText !== undefined )
- return cache.selectedText;
- var nativeSel = this.getNative(),
- text = isMSSelection ? nativeSel.type == 'Control' ? '' : nativeSel.createRange().text : nativeSel.toString();
- return ( cache.selectedText = text );
- },
- /**
- * Locks the selection made in the editor in order to make it possible to
- * manipulate it without browser interference. A locked selection is
- * cached and remains unchanged until it is released with the {@link #unlock} method.
- *
- * editor.getSelection().lock();
- */
- lock: function() {
- // Call all cacheable function.
- this.getRanges();
- this.getStartElement();
- this.getSelectedElement();
- this.getSelectedText();
- // The native selection is not available when locked.
- this._.cache.nativeSel = null;
- this.isLocked = 1;
- },
- /**
- * @todo
- */
- unlock: function( restore ) {
- if ( !this.isLocked )
- return;
- if ( restore ) {
- var selectedElement = this.getSelectedElement(),
- ranges = !selectedElement && this.getRanges(),
- faked = this.isFake;
- }
- this.isLocked = 0;
- this.reset();
- if ( restore ) {
- // Saved selection may be outdated (e.g. anchored in offline nodes).
- // Avoid getting broken by such.
- var common = selectedElement || ranges[ 0 ] && ranges[ 0 ].getCommonAncestor();
- if ( !( common && common.getAscendant( 'body', 1 ) ) )
- return;
- if ( faked )
- this.fake( selectedElement );
- else if ( selectedElement )
- this.selectElement( selectedElement );
- else
- this.selectRanges( ranges );
- }
- },
- /**
- * Clears the selection cache.
- *
- * editor.getSelection().reset();
- */
- reset: function() {
- this._.cache = {};
- this.isFake = 0;
- var editor = this.root.editor;
- // Invalidate any fake selection available in the editor.
- if ( editor && editor._.fakeSelection ) {
- // Test whether this selection is the one that was
- // faked or its clone.
- if ( this.rev == editor._.fakeSelection.rev ) {
- delete editor._.fakeSelection;
- removeHiddenSelectionContainer( editor );
- }
- // jshint ignore:start
- else { // %REMOVE_LINE%
- window.console && console.log( '[CKEDITOR.dom.selection.reset] Wrong selection instance resets fake selection.' ); // %REMOVE_LINE%
- } // %REMOVE_LINE%
- // jshint ignore:end
- }
- this.rev = nextRev++;
- },
- /**
- * Makes the current selection of type {@link CKEDITOR#SELECTION_ELEMENT} by enclosing the specified element.
- *
- * var element = editor.document.getById( 'sampleElement' );
- * editor.getSelection().selectElement( element );
- *
- * @param {CKEDITOR.dom.element} element The element to enclose in the selection.
- */
- selectElement: function( element ) {
- var range = new CKEDITOR.dom.range( this.root );
- range.setStartBefore( element );
- range.setEndAfter( element );
- this.selectRanges( [ range ] );
- },
- /**
- * Clears the original selection and adds the specified ranges to the document selection.
- *
- * // Move selection to the end of the editable element.
- * var range = editor.createRange();
- * range.moveToPosition( range.root, CKEDITOR.POSITION_BEFORE_END );
- * editor.getSelection().selectRanges( [ ranges ] );
- *
- * @param {Array} ranges An array of {@link CKEDITOR.dom.range} instances
- * representing ranges to be added to the document.
- */
- selectRanges: function( ranges ) {
- var editor = this.root.editor,
- hadHiddenSelectionContainer = editor && editor._.hiddenSelectionContainer;
- this.reset();
- // Check if there's a hiddenSelectionContainer in editable at some index.
- // Some ranges may be anchored after the hiddenSelectionContainer and,
- // once the container is removed while resetting the selection, they
- // may need new endOffset (one element less within the range) (#11021 #11393).
- if ( hadHiddenSelectionContainer )
- fixRangesAfterHiddenSelectionContainer( ranges, this.root );
- if ( !ranges.length )
- return;
- // Refresh the locked selection.
- if ( this.isLocked ) {
- // making a new DOM selection will force the focus on editable in certain situation,
- // we have to save the currently focused element for later recovery.
- var focused = CKEDITOR.document.getActive();
- this.unlock();
- this.selectRanges( ranges );
- this.lock();
- // Return to the previously focused element.
- focused && !focused.equals( this.root ) && focused.focus();
- return;
- }
- // Handle special case - automatic fake selection on non-editable elements.
- var receiver = getNonEditableFakeSelectionReceiver( ranges );
- if ( receiver ) {
- this.fake( receiver );
- return;
- }
- if ( isMSSelection ) {
- var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
- fillerTextRegex = /\ufeff|\u00a0/,
- nonCells = { table: 1, tbody: 1, tr: 1 };
- 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 );
- }
- var range = ranges[ 0 ];
- var collapsed = range.collapsed,
- isStartMarkerAlone, dummySpan, ieRange;
- // Try to make a object selection, be careful with selecting phase element in IE
- // will breaks the selection in non-framed environment.
- var selected = range.getEnclosedNode();
- if ( selected && selected.type == CKEDITOR.NODE_ELEMENT && selected.getName() in styleObjectElements &&
- !( selected.is( 'a' ) && selected.getText() ) ) {
- try {
- ieRange = selected.$.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.
- // <table><tbody><tr>[<td>cell</b></td>... => <table><tbody><tr><td>[cell</td>...
- if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.getName() in nonCells ||
- range.endContainer.type == CKEDITOR.NODE_ELEMENT && range.endContainer.getName() in nonCells ) {
- range.shrink( CKEDITOR.NODE_ELEMENT, true );
- // The range might get collapsed (#7975). Update cached variable.
- collapsed = range.collapsed;
- }
- var bookmark = range.createBookmark();
- // Create marker tags for the start and end boundaries.
- var startNode = bookmark.startNode;
- var endNode;
- if ( !collapsed )
- endNode = bookmark.endNode;
- // Create the main range which will be used for the selection.
- ieRange = range.document.$.body.createTextRange();
- // Position the range at the start boundary.
- ieRange.moveToElementText( startNode.$ );
- ieRange.moveStart( 'character', 1 );
- if ( endNode ) {
- // Create a tool range for the end.
- var ieRangeEnd = range.document.$.body.createTextRange();
- // Position the tool range at the end.
- ieRangeEnd.moveToElementText( endNode.$ );
- // Move the end boundary of the main range to match the tool range.
- ieRange.setEndPoint( 'EndToEnd', ieRangeEnd );
- ieRange.moveEnd( 'character', -1 );
- } else {
- // The isStartMarkerAlone logic comes from V2. It guarantees that the lines
- // will expand and that the cursor will be blinking on the right place.
- // Actually, we are using this flag just to avoid using this hack in all
- // situations, but just on those needed.
- var next = startNode.getNext( notWhitespaces );
- var inPre = startNode.hasAscendant( 'pre' );
- isStartMarkerAlone = ( !( next && next.getText && next.getText().match( fillerTextRegex ) ) && // already a filler there?
- ( inPre || !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) ) );
- // Append a temporary <span></span> before the selection.
- // This is needed to avoid IE destroying selections inside empty
- // inline elements, like <b></b> (#253).
- // It is also needed when placing the selection right after an inline
- // element to avoid the selection moving inside of it.
- dummySpan = range.document.createElement( 'span' );
- dummySpan.setHtml( '' ); // Zero Width No-Break Space (U+FEFF). See #1359.
- dummySpan.insertBefore( startNode );
- if ( isStartMarkerAlone ) {
- // To expand empty blocks or line spaces after <br>, we need
- // instead to have any char, which will be later deleted using the
- // selection.
- // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359)
- range.document.createText( '\ufeff' ).insertBefore( startNode );
- }
- }
- // Remove the markers (reset the position, because of the changes in the DOM tree).
- range.setStartBefore( startNode );
- startNode.remove();
- if ( collapsed ) {
- if ( isStartMarkerAlone ) {
- // Move the selection start to include the temporary \ufeff.
- ieRange.moveStart( 'character', -1 );
- ieRange.select();
- // Remove our temporary stuff.
- range.document.$.selection.clear();
- } else {
- ieRange.select();
- }
- range.moveToPosition( dummySpan, CKEDITOR.POSITION_BEFORE_START );
- dummySpan.remove();
- } else {
- range.setEndBefore( endNode );
- endNode.remove();
- ieRange.select();
- }
- } else {
- var sel = this.getNative();
- // getNative() returns null if iframe is "display:none" in FF. (#6577)
- if ( !sel )
- return;
- this.removeAllRanges();
- 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. <span contenteditable="false">[placeholder]</span>
- // 2. <input contenteditable="false" type="radio"/> (#6621)
- if ( ancestor.isReadOnly() || enclosed && enclosed.isReadOnly() ) {
- right.setStart( left.startContainer, left.startOffset );
- ranges.splice( i--, 1 );
- continue;
- }
- }
- }
- range = ranges[ i ];
- var nativeRange = this.document.$.createRange();
- 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.root );
- range.insertNode( fillingChar );
- next = fillingChar.getNext();
- // If the filling char is followed by a <br>, 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.root );
- 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 );
- }
- }
- this.reset();
- // Fakes the IE DOM event "selectionchange" on editable.
- this.root.fire( 'selectionchange' );
- },
- /**
- * Makes a "fake selection" of an element.
- *
- * A fake selection does not render UI artifacts over the selected
- * element. Additionally, the browser native selection system is not
- * aware of the fake selection. In practice, the native selection is
- * moved to a hidden place where no native selection UI artifacts are
- * displayed to the user.
- *
- * @param {CKEDITOR.dom.element} element The element to be "selected".
- */
- fake: function( element ) {
- var editor = this.root.editor;
- // Cleanup after previous selection - e.g. remove hidden sel container.
- this.reset();
- hideSelection( editor );
- // Set this value after executing hiseSelection, because it may
- // cause reset() which overwrites cache.
- var cache = this._.cache;
- // Caches a range than holds the element.
- var range = new CKEDITOR.dom.range( this.root );
- range.setStartBefore( element );
- range.setEndAfter( element );
- cache.ranges = new CKEDITOR.dom.rangeList( range );
- // Put this element in the cache.
- cache.selectedElement = cache.startElement = element;
- cache.type = CKEDITOR.SELECTION_ELEMENT;
- // Properties that will not be available when isFake.
- cache.selectedText = cache.nativeSel = null;
- this.isFake = 1;
- this.rev = nextRev++;
- // Save this selection, so it can be returned by editor.getSelection().
- editor._.fakeSelection = this;
- // Fire selectionchange, just like a normal selection.
- this.root.fire( 'selectionchange' );
- },
- /**
- * Checks whether selection is placed in hidden element.
- *
- * This method is to be used to verify whether fake selection
- * (see {@link #fake}) is still hidden.
- *
- * **Note:** this method should be executed on real selection - e.g.:
- *
- * editor.getSelection( true ).isHidden();
- *
- * @returns {Boolean}
- */
- isHidden: function() {
- var el = this.getCommonAncestor();
- if ( el && el.type == CKEDITOR.NODE_TEXT )
- el = el.getParent();
- return !!( el && el.data( 'cke-hidden-sel' ) );
- },
- /**
- * Creates a bookmark for each range of this selection (from {@link #getRanges})
- * by calling the {@link CKEDITOR.dom.range#createBookmark} method,
- * with extra care taken to avoid interference among those ranges. The arguments
- * received are the same as with the underlying range method.
- *
- * var bookmarks = editor.getSelection().createBookmarks();
- *
- * @returns {Array} Array of bookmarks for each range.
- */
- createBookmarks: function( serializable ) {
- var bookmark = this.getRanges().createBookmarks( serializable );
- this.isFake && ( bookmark.isFake = 1 );
- return bookmark;
- },
- /**
- * Creates a bookmark for each range of this selection (from {@link #getRanges})
- * by calling the {@link CKEDITOR.dom.range#createBookmark2} method,
- * with extra care taken to avoid interference among those ranges. The arguments
- * received are the same as with the underlying range method.
- *
- * var bookmarks = editor.getSelection().createBookmarks2();
- *
- * @returns {Array} Array of bookmarks for each range.
- */
- createBookmarks2: function( normalized ) {
- var bookmark = this.getRanges().createBookmarks2( normalized );
- this.isFake && ( bookmark.isFake = 1 );
- return bookmark;
- },
- /**
- * Selects the virtual ranges denoted by the bookmarks by calling {@link #selectRanges}.
- *
- * var bookmarks = editor.getSelection().createBookmarks();
- * editor.getSelection().selectBookmarks( bookmarks );
- *
- * @param {Array} bookmarks The bookmarks representing ranges to be selected.
- * @returns {CKEDITOR.dom.selection} This selection object, after the ranges were selected.
- */
- selectBookmarks: function( bookmarks ) {
- var ranges = [],
- node;
- for ( var i = 0; i < bookmarks.length; i++ ) {
- var range = new CKEDITOR.dom.range( this.root );
- range.moveToBookmark( bookmarks[ i ] );
- ranges.push( range );
- }
- // It may happen that the content change during loading, before selection is set so bookmark leads to text node.
- if ( bookmarks.isFake ) {
- node = ranges[ 0 ].getEnclosedNode();
- if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) {
- // %REMOVE_START%
- window.console && console.log( '[CKEDITOR.dom.selection.selectBookmarks] Selection is no longer fake.' ); // jshint ignore:line
- // %REMOVE_END%
- bookmarks.isFake = 0;
- }
- }
- if ( bookmarks.isFake )
- this.fake( node );
- else
- this.selectRanges( ranges );
- return this;
- },
- /**
- * Retrieves the common ancestor node of the first range and the last range.
- *
- * var ancestor = editor.getSelection().getCommonAncestor();
- *
- * @returns {CKEDITOR.dom.element} The common ancestor of the selection or `null` if selection is empty.
- */
- getCommonAncestor: function() {
- var ranges = this.getRanges();
- if ( !ranges.length )
- return null;
- var startNode = ranges[ 0 ].startContainer,
- endNode = ranges[ ranges.length - 1 ].endContainer;
- return startNode.getCommonAncestor( endNode );
- },
- /**
- * Moves the scrollbar to the starting position of the current selection.
- *
- * editor.getSelection().scrollIntoView();
- */
- scrollIntoView: function() {
- // Scrolls the first range into view.
- if ( this.type != CKEDITOR.SELECTION_NONE )
- this.getRanges()[ 0 ].scrollIntoView();
- },
- /**
- * Remove all the selection ranges from the document.
- */
- removeAllRanges: function() {
- // Don't clear selection outside this selection's root (#11500).
- if ( this.getType() == CKEDITOR.SELECTION_NONE )
- return;
- var nativ = this.getNative();
- try {
- nativ && nativ[ isMSSelection ? 'empty' : 'removeAllRanges' ]();
- } catch ( er ) {}
- this.reset();
- }
- };
- } )();
- /**
- * Fired when selection inside editor has been changed. Note that this event
- * is fired only when selection's start element (container of a selecion start)
- * changes, not on every possible selection change. Thanks to that `selectionChange`
- * is fired less frequently, but on every context
- * (the {@link CKEDITOR.editor#elementPath elements path} holding selection's start) change.
- *
- * @event selectionChange
- * @member CKEDITOR.editor
- * @param {CKEDITOR.editor} editor This editor instance.
- * @param data
- * @param {CKEDITOR.dom.selection} data.selection
- * @param {CKEDITOR.dom.elementPath} data.path
- */
- /**
- * Selection's revision. This value is incremented every time new
- * selection is created or existing one is modified.
- *
- * @since 4.3
- * @readonly
- * @property {Number} rev
- */
- /**
- * Document in which selection is anchored.
- *
- * @readonly
- * @property {CKEDITOR.dom.document} document
- */
- /**
- * Selection's root element.
- *
- * @readonly
- * @property {CKEDITOR.dom.element} root
- */
- /**
- * Whether selection is locked (cannot be modified).
- *
- * See {@link #lock} and {@link #unlock} methods.
- *
- * @readonly
- * @property {Boolean} isLocked
- */
- /**
- * Whether selection is a fake selection.
- *
- * See {@link #fake} method.
- *
- * @readonly
- * @property {Boolean} isFake
- */
|