Преглед на файлове

Android backspace handling improvements.

Krzysztof Krztoń преди 7 години
родител
ревизия
18ec2fa0a4
променени са 1 файла, в които са добавени 46 реда и са изтрити 28 реда
  1. 46 28
      packages/ckeditor5-typing/src/utils/injectandroidbackspacemutationshandling.js

+ 46 - 28
packages/ckeditor5-typing/src/utils/injectandroidbackspacemutationshandling.js

@@ -20,22 +20,30 @@ import { containerChildrenMutated } from '../utils';
 export default function injectAndroidBackspaceMutationsHandling( editor ) {
 	const model = editor.model;
 	const view = editor.editing.view;
+	const selectionChangeToleranceMs = 200;
 
 	let previousSelection = null;
 	let currentSelection = new Selection( model.document.selection );
+	let latestSelectionChangeMs = Date.now();
 
-	view.document.on( 'selectionChange', handleSelectionChange );
+	model.document.selection.on( 'change', handleSelectionChange );
 
 	view.document.on( 'mutations', handleMutations, { priority: 'highest' } );
 
 	// Saves current and previous selection when it changes. Saved selections are used
 	// to remove correct piece of content when `Backspace` mutations are detected.
-	function handleSelectionChange() {
-		previousSelection = currentSelection;
-		currentSelection = new Selection( model.document.selection );
+	//
+	// @param {Object} evt
+	function handleSelectionChange( evt ) {
+		const newSelection = new Selection( evt.source );
+		if ( !currentSelection.isEqual( newSelection ) ) {
+			previousSelection = currentSelection;
+			currentSelection = newSelection;
+			latestSelectionChangeMs = Date.now();
+		}
 	}
 
-	// Handles specific DOM mutations.
+	// Handles DOM mutations and checks if they should be processed as block elements removal mutations.
 	//
 	// @param {Object} evt
 	// @param {Array.<module:engine/view/observer/mutationobserver~MutatedText|
@@ -54,7 +62,9 @@ export default function injectAndroidBackspaceMutationsHandling( editor ) {
 	// and delete observer will not be triggered. In such cases we need to handle
 	// container removal mutations manually.
 	function handleContainerRemovalMutations() {
-		if ( shouldUsePreviousSelection( currentSelection, previousSelection ) ) {
+		if ( shouldUsePreviousSelection() ) {
+			// If previous selection is used, update model selection in order
+			// to use `delete` command and to make `undo` work correctly.
 			model.enqueueChange( writer => {
 				writer.setSelection( previousSelection );
 			} );
@@ -62,28 +72,36 @@ export default function injectAndroidBackspaceMutationsHandling( editor ) {
 
 		editor.execute( 'delete' );
 	}
-}
 
-// Whether previously saved selection should be used to remove content instead of the current one.
-// Previous selection will be used if it exists is non-collapsed and has the same last position as the current selection.
-//
-// On Android devices when pressing backspace on non-collapsed selection, selection like:
-//
-//		`<h1>[Foo</h1><p>Bar]</p>`
-//
-// is changed to:
-//
-//		`<h1>Foo</h1><p>Bar[]</p>`
-//
-// even before `keypress` event, so in such cases we have to rely on previous selection to correctly process selected content.
-//
-// @private
-// @param {module:engine/model/selection~Selection} currentSelection
-// @param {module:engine/model/selection~Selection} previousSelection
-// @returns {Boolean}
-function shouldUsePreviousSelection( currentSelection, previousSelection ) {
-	return previousSelection && !previousSelection.isCollapsed &&
-		currentSelection.getLastPosition().isEqual( previousSelection.getLastPosition() );
+	// Whether previously saved selection should be used to remove content instead of the current one.
+	//
+	// On Android devices when pressing backspace on non-collapsed selection, selection like:
+	//
+	//		`<h1>[Foo</h1><p>Bar]</p>`
+	//
+	// is changed to:
+	//
+	//		`<h1>Foo</h1><p>Bar[]</p>`
+	//
+	// even before `keypress` event, so in such cases we have to rely on previous selection to correctly process selected content.
+	//
+	// Previous selection will be used if:
+	//
+	//		* current selection is collapsed (see example above),
+	//		* previous selection exists, is non-collapsed and has same ending (last position) as the current one,
+	//		* change of the selection happened not earlier than X milliseconds ago (see `selectionChangeToleranceMs`).
+	//
+	// The last check is needed, because user can manually collapse the selection on its current end and then press `Backspace`.
+	// In such situations timing determines if selection change was caused by the user or browser native behaviour.
+	// However, this happens only if selection is collapsed by the user on the beginning of the paragraph (so mutations
+	// still will show container removal).
+	//
+	// @returns {Boolean}
+	function shouldUsePreviousSelection() {
+		return Date.now() - latestSelectionChangeMs < selectionChangeToleranceMs &&
+			previousSelection && !previousSelection.isCollapsed && currentSelection.isCollapsed &&
+			currentSelection.getLastPosition().isEqual( previousSelection.getLastPosition() );
+	}
 }
 
 // Checks whether mutations array contains mutation generated by container/containers removal.
@@ -100,7 +118,7 @@ function shouldUsePreviousSelection( currentSelection, previousSelection ) {
 //			{ newChildren: [ 'Heading 1Paragraph' ], oldChildren: [ 'Heading 1' ], node: H1, type: 'children' }
 //		]
 //
-// The 1st and 3rd mutations show just changes in a text (1st - text in `p` element was removed, 3rd - text in `h2` has changed)
+// The 1st and 3rd mutations show just changes in a text (1st - text in `p` element was removed, 3rd - text in `h2` was changed)
 // and the 2nd one shows that one `ContainerElement` was removed. We have to recognize if mutations like 2nd one are present.
 // Based on that heuristic mutations are treated as the one removing container element.
 //