|
|
@@ -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.
|
|
|
//
|