Selaa lähdekoodia

Docs: Update documentation of selection post fixer.

Maciej Gołaszewski 7 vuotta sitten
vanhempi
commit
824cfc1e99
1 muutettua tiedostoa jossa 108 lisäystä ja 67 poistoa
  1. 108 67
      packages/ckeditor5-engine/src/controller/editingcontroller.js

+ 108 - 67
packages/ckeditor5-engine/src/controller/editingcontroller.js

@@ -13,11 +13,7 @@ import Mapper from '../conversion/mapper';
 import DowncastDispatcher from '../conversion/downcastdispatcher';
 import { insertText, remove } from '../conversion/downcast-converters';
 import { convertSelectionChange } from '../conversion/upcast-selection-converters';
-import {
-	convertRangeSelection,
-	convertCollapsedSelection,
-	clearAttributes
-} from '../conversion/downcast-selection-converters';
+import { clearAttributes, convertCollapsedSelection, convertRangeSelection } from '../conversion/downcast-selection-converters';
 
 import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
@@ -100,33 +96,7 @@ export default class EditingController {
 		this.downcastDispatcher.on( 'selection', convertCollapsedSelection(), { priority: 'low' } );
 
 		// Add selection post fixer.
-		doc.registerPostFixer( selectionPostFixer );
-
-		function selectionPostFixer( writer ) {
-			const ranges = [];
-
-			let needsUpdate = false;
-
-			for ( const modelRange of selection.getRanges() ) {
-				const correctedRange = correctRange( modelRange, model.schema );
-
-				if ( correctedRange ) {
-					ranges.push( correctedRange );
-					needsUpdate = true;
-				} else {
-					ranges.push( modelRange );
-				}
-			}
-
-			// If any of ranges were corrected update the selection.
-			if ( needsUpdate ) {
-				// The above algorithm might create ranges that intersects each other when selection contains more then one range.
-				// This is case happens mostly on Firefox which creates multiple ranges for selected table.
-				const safeRange = combineRanges( ranges );
-
-				writer.setSelection( safeRange, { backward: selection.isBackward } );
-			}
-		}
+		doc.registerPostFixer( writer => selectionPostFixer( writer, model ) );
 
 		// Binds {@link module:engine/view/document~Document#roots view roots collection} to
 		// {@link module:engine/model/document~Document#roots model roots collection} so creating
@@ -159,36 +129,103 @@ export default class EditingController {
 
 mix( EditingController, ObservableMixin );
 
-function correctRange( range, schema ) {
-	if ( range.isCollapsed ) {
-		// check only if position is allowed:
-		const originalPosition = range.start;
+/**
+ * The selection post fixer which check if nodes with `isLimit` property in schema are properly selected.
+ *
+ * @param {module:engine/model/writer~Writer} writer
+ * @param {module:engine/model/model~Model} model
+ */
+function selectionPostFixer( writer, model ) {
+	const selection = model.document.selection;
+	const schema = model.schema;
 
-		const nearestSelectionRange = schema.getNearestSelectionRange( originalPosition );
+	const ranges = [];
 
-		// This get empty if editor data is empty (some tests)
-		if ( !nearestSelectionRange ) {
-			return null;
-		}
+	let wasFixed = false;
 
-		const fixedPosition = nearestSelectionRange.start;
+	for ( const modelRange of selection.getRanges() ) {
+		// Go through all ranges in selection and try fixing each of them.
+		// Those ranges might overlap but will be corrected later.
+		const correctedRange = tryFixRangeWithIsLimitBlocks( modelRange, schema );
 
-		if ( !originalPosition.isEqual( fixedPosition ) ) {
-			return fixSelectionOnLimitBlock( schema, fixedPosition );
+		if ( correctedRange ) {
+			ranges.push( correctedRange );
+			wasFixed = true;
+		} else {
+			ranges.push( modelRange );
 		}
+	}
+
+	// If any of ranges were corrected update the selection.
+	if ( wasFixed ) {
+		// The above algorithm might create ranges that intersects each other when selection contains more then one range.
+		// This is case happens mostly on Firefox which creates multiple ranges for selected table.
+		const safeRange = combineRangesOnLimitNodes( ranges );
+
+		writer.setSelection( safeRange, { backward: selection.isBackward } );
+	}
+}
+
+// Tries to correct a range if it contains blocks defined as `isLimit` in schema.
+//
+// @param {module:engine/model/range~Range} range
+// @param {module:engine/model/schema~Schema} schema
+// @returns {module:engine/model/range~Range|null} Returns fixed range or null if range is valid.
+function tryFixRangeWithIsLimitBlocks( range, schema ) {
+	if ( range.isCollapsed ) {
+		return tryFixCollapsedRange( range, schema );
+	}
+
+	return tryFixExpandedRange( range, schema );
+}
 
+// Tries to fix collapsed ranges - ie. when collapsed selection is in limit node that contains other limit nodes.
+//
+// @param {module:engine/model/range~Range} range Collapsed range to fix.
+// @param {module:engine/model/schema~Schema} schema
+// @returns {module:engine/model/range~Range|null} Returns fixed range or null if range is valid.
+function tryFixCollapsedRange( range, schema ) {
+	const originalPosition = range.start;
+
+	const nearestSelectionRange = schema.getNearestSelectionRange( originalPosition );
+
+	// This might be null ie when editor data is empty.
+	// In such cases there is no need to fix the selection range.
+	if ( !nearestSelectionRange ) {
 		return null;
 	}
 
-	if ( range.isFlat || range.isCollapsed ) {
+	const fixedPosition = nearestSelectionRange.start;
+
+	// Fixed position is the same as original - no need to return corrected range.
+	if ( originalPosition.isEqual( fixedPosition ) ) {
+		return null;
+	}
+
+	// Check single node selection (happens in tables).
+	if ( fixedPosition.nodeAfter && schema.isLimit( fixedPosition.nodeAfter ) ) {
+		return new Range( fixedPosition, Position.createAfter( fixedPosition.nodeAfter ) );
+	}
+
+	return new Range( fixedPosition );
+}
+
+// Tries to fix a expanded range that overlaps limit nodes.
+//
+// @param {module:engine/model/range~Range} range Expanded range to fix.
+// @param {module:engine/model/schema~Schema} schema
+// @returns {module:engine/model/range~Range|null} Returns fixed range or null if range is valid.
+function tryFixExpandedRange( range, schema ) {
+	// No need to check flat ranges as they will not cross node boundary.
+	if ( range.isFlat ) {
 		return null;
 	}
 
 	const start = range.start;
 	const end = range.end;
 
-	const updatedStart = ensurePositionInIsLimitBlock( start, schema, 'start' );
-	const updatedEnd = ensurePositionInIsLimitBlock( end, schema, 'end' );
+	const updatedStart = expandSelectionOnIsLimitNode( start, schema, 'start' );
+	const updatedEnd = expandSelectionOnIsLimitNode( end, schema, 'end' );
 
 	if ( !start.isEqual( updatedStart ) || !end.isEqual( updatedEnd ) ) {
 		return new Range( updatedStart, updatedEnd );
@@ -197,33 +234,37 @@ function correctRange( range, schema ) {
 	return null;
 }
 
-function ensurePositionInIsLimitBlock( position, schema, where ) {
-	let parent = position.parent;
-	let node = parent;
-
+// Expands selection so it contains whole limit node.
+//
+// @param {module:engine/model/position~Position} position
+// @param {module:engine/model/schema~Schema} schema
+// @param {String} expandToDirection Direction of expansion - either 'start' or 'end' of the range.
+// @returns {module:engine/model/position~Position}
+function expandSelectionOnIsLimitNode( position, schema, expandToDirection ) {
+	let node = position.parent;
+	let parent = node;
+
+	// Find outer most isLimit block as such blocks might be nested (ie. in tables).
 	while ( schema.isLimit( parent ) && parent.parent ) {
 		node = parent;
 		parent = parent.parent;
 	}
 
 	if ( node === parent ) {
+		// If there is not is limit block the return original position.
 		return position;
 	}
 
-	return where === 'start' ? Position.createBefore( node ) : Position.createAfter( node );
-}
-
-function fixSelectionOnLimitBlock( schema, fixedPosition ) {
-	// Check single node selection (happens in tables).
-	if ( fixedPosition.nodeAfter && schema.isLimit( fixedPosition.nodeAfter ) ) {
-		return new Range( fixedPosition, Position.createAfter( fixedPosition.nodeAfter ) );
-	}
-
-	return new Range( fixedPosition );
+	// Depending on direction of expanding selection return position before or after found node.
+	return expandToDirection === 'start' ? Position.createBefore( node ) : Position.createAfter( node );
 }
 
-function combineRanges( ranges ) {
-	const combined = [];
+// Returns minimal set of continuous ranges.
+//
+// @param {Array.<module:engine/model/range~Range>} ranges
+// @returns {Array.<module:engine/model/range~Range>}
+function combineRangesOnLimitNodes( ranges ) {
+	const combinedRanges = [];
 
 	let previousRange;
 
@@ -232,7 +273,7 @@ function combineRanges( ranges ) {
 
 		if ( !previousRange ) {
 			previousRange = range;
-			combined.push( previousRange );
+			combinedRanges.push( previousRange );
 			continue;
 		}
 
@@ -246,7 +287,7 @@ function combineRanges( ranges ) {
 			const newEnd = range.end.isAfter( previousRange.end ) ? range.end : previousRange.end;
 			const newRange = new Range( newStart, newEnd );
 
-			combined.splice( combined.indexOf( previousRange ), 1, newRange );
+			combinedRanges.splice( combinedRanges.indexOf( previousRange ), 1, newRange );
 
 			previousRange = newRange;
 
@@ -254,8 +295,8 @@ function combineRanges( ranges ) {
 		}
 
 		previousRange = range;
-		combined.push( range );
+		combinedRanges.push( range );
 	}
 
-	return combined;
+	return combinedRanges;
 }