This commit is contained in:
2026-05-15 18:33:51 +02:00
parent 3601be572f
commit c980004309
8442 changed files with 783630 additions and 1 deletions

View File

@@ -0,0 +1,163 @@
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @fileOverview Defines methods used for selection optimization.
*/
( function() {
var preventListener = true,
preventOptimization = false;
/**
* Sets editor listeners up to optimize the selection.
*
* **Note**: This method is called automatically during the editor initialization and should not be called manually.
*
* @since 4.13.0
* @static
* @see CKEDITOR.dom.selection.optimizeInElementEnds
* @param {CKEDITOR.editor} editor
* @member CKEDITOR.dom.selection
*/
CKEDITOR.dom.selection.setupEditorOptimization = function( editor ) {
editor.on( 'selectionCheck', function( evt ) {
if ( evt.data && !preventOptimization ) {
evt.data.optimizeInElementEnds();
}
preventOptimization = false;
} );
editor.on( 'contentDom', function() {
var editable = editor.editable();
if ( !editable ) {
return;
}
editable.attachListener( editable, 'keydown', function( evt ) {
this._.shiftPressed = evt.data.$.shiftKey;
}, this );
editable.attachListener( editable, 'keyup', function( evt ) {
this._.shiftPressed = evt.data.$.shiftKey;
}, this );
} );
};
/**
* Performs an optimization on the current selection if necessary.
*
* The general idea is to shrink the range to text when:
*
* * The range starts at the end of an element.
* * The range ends at the start of an element.
* * One of the range ends is anchored in a text node and another in an element.
*
* For example:
*
* ```html
* <p>{foo</p>
* <p>]bar</p>
* ```
*
* is optimized too:
*
* ```html
* <p>{foo}</p>
* <p>bar</p>
* ```
*
* @since 4.13.0
* @member CKEDITOR.dom.selection
*/
CKEDITOR.dom.selection.prototype.optimizeInElementEnds = function() {
var range = this.getRanges()[ 0 ],
editor = this.root.editor;
if ( !shouldOptimize( range, this ) ) {
return;
}
var oldRange = range.clone();
range.shrink( CKEDITOR.SHRINK_TEXT, false, { skipBogus: !CKEDITOR.env.webkit } );
preventListener = false;
preventRecurrency( editor, range, oldRange );
range.select();
preventListener = true;
};
function isText( node ) {
return node.type === CKEDITOR.NODE_TEXT;
}
// Returns `true` if any condition is met:
// * The range starts at the end of an element.
// * The range ends at the start of an element.
// * One end of the range is in text and another one is not.
//
// Always returns `false` when:
// * The Shift key is pressed.
// * The selection is fake.
// * The range is collapsed.
// * The range start and end container is the same element.
function shouldOptimize( range, selection ) {
if ( selection.root.editor._.shiftPressed ) {
return false;
}
if ( selection.isFake || range.isCollapsed || range.startContainer.equals( range.endContainer ) ) {
return false;
}
// The endContainer might be a text inside li element (in IE8).
var isInList = range.endContainer.is ?
range.endContainer.is( 'li' ) :
range.endContainer.getParent().is && range.endContainer.getParent().is( 'li' );
// Prevent optimization in lists (#4931).
if ( isInList ) {
return false;
}
if ( range.endOffset === 0 ) {
return true;
}
var startsInText = isText( range.startContainer ),
endsInText = isText( range.endContainer ),
limit = startsInText ? range.startContainer.getLength() : range.startContainer.getChildCount();
return range.startOffset === limit || startsInText ^ endsInText;
}
// Prevent infinite recurrency when the browser does not allow the expected selection.
// There are two cases to handle:
// - When the browser modified the range in a way that it is the same as before the optimization.
// The second event is canceled, we do not need to fire listeners two times with the exact same selection.
// - When the browser does not modify the range.
// The event is not canceled, as the selection changed, however, the next optimization is prevented.
function preventRecurrency( editor, targetRange, initialRange ) {
editor.once( 'selectionCheck', function( evt ) {
if ( preventListener ) {
return;
}
var newRange = evt.data.getRanges()[ 0 ];
if ( initialRange.equals( newRange ) ) {
evt.cancel();
} else if ( targetRange.equals( newRange ) ) {
preventOptimization = true;
}
}, null, null, -1 );
}
} )();