Skip to content

Commit

Permalink
Merge pull request #10499 from ckeditor/ck/10413-backspace-reverting-…
Browse files Browse the repository at this point in the history
…text-transformation

Feature (typing, image, link, media-embed): Allows using backspace to undo automatic transformations. Closes #10413.
  • Loading branch information
niegowski authored Sep 10, 2021
2 parents bac7d7d + e07b04d commit 66073dd
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 13 deletions.
5 changes: 4 additions & 1 deletion packages/ckeditor5-image/src/autoimage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Plugin } from 'ckeditor5/src/core';
import { Clipboard } from 'ckeditor5/src/clipboard';
import { LivePosition, LiveRange } from 'ckeditor5/src/engine';
import { Undo } from 'ckeditor5/src/undo';
import { Delete } from 'ckeditor5/src/typing';
import { global } from 'ckeditor5/src/utils';

import ImageUtils from './imageutils';
Expand All @@ -32,7 +33,7 @@ export default class AutoImage extends Plugin {
* @inheritDoc
*/
static get requires() {
return [ Clipboard, ImageUtils, Undo ];
return [ Clipboard, ImageUtils, Undo, Delete ];
}

/**
Expand Down Expand Up @@ -173,6 +174,8 @@ export default class AutoImage extends Plugin {
this._positionToInsert.detach();
this._positionToInsert = null;
} );

editor.plugins.get( 'Delete' ).requestUndoOnBackspace();
}, 100 );
}
}
25 changes: 25 additions & 0 deletions packages/ckeditor5-image/tests/autoimage.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Table from '@ckeditor/ckeditor5-table/src/table';
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
import Undo from '@ckeditor/ckeditor5-undo/src/undo';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';

import Image from '../src/image';
Expand Down Expand Up @@ -102,6 +103,30 @@ describe( 'AutoImage - integration', () => {
);
} );

it( 'can undo auto-embeding by pressing backspace', () => {
const viewDocument = editor.editing.view.document;
const deleteEvent = new DomEventData(
viewDocument,
{ preventDefault: sinon.spy() },
{ direction: 'backward', unit: 'codePoint', sequence: 1 }
);

setData( editor.model, '<paragraph>[]</paragraph>' );
pasteHtml( editor, 'http://example.com/image.png' );

expect( getData( editor.model ) ).to.equal(
'<paragraph>http://example.com/image.png[]</paragraph>'
);

clock.tick( 100 );

viewDocument.fire( 'delete', deleteEvent );

expect( getData( editor.model ) ).to.equal(
'<paragraph>http://example.com/image.png[]</paragraph>'
);
} );

describe( 'supported URL', () => {
const supportedURLs = [
'example.com/image.png',
Expand Down
14 changes: 13 additions & 1 deletion packages/ckeditor5-link/src/autolink.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { Plugin } from 'ckeditor5/src/core';
import { TextWatcher, getLastTextLine } from 'ckeditor5/src/typing';
import { Delete, TextWatcher, getLastTextLine } from 'ckeditor5/src/typing';

import { addLinkProtocolIfApplicable } from './utils';

Expand Down Expand Up @@ -69,6 +69,13 @@ const URL_GROUP_IN_MATCH = 2;
* @extends module:core/plugin~Plugin
*/
export default class AutoLink extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ Delete ];
}

/**
* @inheritDoc
*/
Expand Down Expand Up @@ -226,6 +233,7 @@ export default class AutoLink extends Plugin {
*/
_applyAutoLink( link, range ) {
const model = this.editor.model;
const deletePlugin = this.editor.plugins.get( 'Delete' );

if ( !this.isEnabled || !isLinkAllowedOnRange( range, model ) ) {
return;
Expand All @@ -236,6 +244,10 @@ export default class AutoLink extends Plugin {
const defaultProtocol = this.editor.config.get( 'link.defaultProtocol' );
const parsedUrl = addLinkProtocolIfApplicable( link, defaultProtocol );
writer.setAttribute( 'linkHref', parsedUrl, range );

model.enqueueChange( () => {
deletePlugin.requestUndoOnBackspace();
} );
} );
}
}
Expand Down
18 changes: 18 additions & 0 deletions packages/ckeditor5-link/tests/autolink.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Input from '@ckeditor/ckeditor5-typing/src/input';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';

import LinkEditing from '../src/linkediting';
Expand Down Expand Up @@ -388,6 +389,23 @@ describe( 'AutoLink', () => {
'<paragraph>[]</paragraph>'
);
} );

it( 'should undo auto-linking by pressing backspace', () => {
const viewDocument = editor.editing.view.document;
const deleteEvent = new DomEventData(
viewDocument,
{ preventDefault: sinon.spy() },
{ direction: 'backward', unit: 'codePoint', sequence: 1 }
);

simulateTyping( ' ' );

viewDocument.fire( 'delete', deleteEvent );

expect( getData( model ) ).to.equal(
'<paragraph>https://www.cksource.com []</paragraph>'
);
} );
} );

describe( 'Code blocks integration', () => {
Expand Down
5 changes: 4 additions & 1 deletion packages/ckeditor5-media-embed/src/automediaembed.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { Plugin } from 'ckeditor5/src/core';
import { LiveRange, LivePosition } from 'ckeditor5/src/engine';
import { Clipboard } from 'ckeditor5/src/clipboard';
import { Delete } from 'ckeditor5/src/typing';
import { Undo } from 'ckeditor5/src/undo';
import { global } from 'ckeditor5/src/utils';

Expand All @@ -29,7 +30,7 @@ export default class AutoMediaEmbed extends Plugin {
* @inheritDoc
*/
static get requires() {
return [ Clipboard, Undo ];
return [ Clipboard, Delete, Undo ];
}

/**
Expand Down Expand Up @@ -174,6 +175,8 @@ export default class AutoMediaEmbed extends Plugin {
this._positionToInsert.detach();
this._positionToInsert = null;
} );

editor.plugins.get( 'Delete' ).requestUndoOnBackspace();
}, 100 );
}
}
25 changes: 25 additions & 0 deletions packages/ckeditor5-media-embed/tests/automediaembed.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Image from '@ckeditor/ckeditor5-image/src/image';
import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
import Table from '@ckeditor/ckeditor5-table/src/table';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';

describe( 'AutoMediaEmbed - integration', () => {
Expand Down Expand Up @@ -96,6 +97,30 @@ describe( 'AutoMediaEmbed - integration', () => {
);
} );

it( 'can undo auto-embeding by pressing backspace', () => {
const viewDocument = editor.editing.view.document;
const deleteEvent = new DomEventData(
viewDocument,
{ preventDefault: sinon.spy() },
{ direction: 'backward', unit: 'codePoint', sequence: 1 }
);

setData( editor.model, '<paragraph>[]</paragraph>' );
pasteHtml( editor, 'https://www.youtube.com/watch?v=H08tGjXNHO4' );

expect( getData( editor.model ) ).to.equal(
'<paragraph>https://www.youtube.com/watch?v=H08tGjXNHO4[]</paragraph>'
);

clock.tick( 100 );

viewDocument.fire( 'delete', deleteEvent );

expect( getData( editor.model ) ).to.equal(
'<paragraph>https://www.youtube.com/watch?v=H08tGjXNHO4[]</paragraph>'
);
} );

it( 'works for a full URL (https + "www" sub-domain)', () => {
setData( editor.model, '<paragraph>[]</paragraph>' );
pasteHtml( editor, 'https://www.youtube.com/watch?v=H08tGjXNHO4' );
Expand Down
38 changes: 38 additions & 0 deletions packages/ckeditor5-typing/src/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ import env from '@ckeditor/ckeditor5-utils/src/env';
* @extends module:core/plugin~Plugin
*/
export default class Delete extends Plugin {
/**
* Whether pressing backspace should trigger undo action
*
* @private
* @member {Boolean} #_undoOnBackspace
*/

/**
* @inheritDoc
*/
Expand All @@ -29,9 +36,12 @@ export default class Delete extends Plugin {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
const modelDocument = editor.model.document;

view.addObserver( DeleteObserver );

this._undoOnBackspace = false;

const deleteForwardCommand = new DeleteCommand( editor, 'forward' );

// Register `deleteForward` command and add `forwardDelete` command as an alias for backward compatibility.
Expand Down Expand Up @@ -97,5 +107,33 @@ export default class Delete extends Plugin {
}
} );
}

if ( this.editor.plugins.has( 'UndoEditing' ) ) {
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
if ( this._undoOnBackspace && data.direction == 'backward' && data.sequence == 1 && data.unit == 'codePoint' ) {
this._undoOnBackspace = false;

editor.execute( 'undo' );

data.preventDefault();
evt.stop();
}
}, { context: '$capture' } );

this.listenTo( modelDocument, 'change', () => {
this._undoOnBackspace = false;
} );
}
}

/**
* If the next user action after calling this method is pressing backspace, it would undo the last change.
*
* Requires {@link module:undo/undoediting~UndoEditing} plugin. If not loaded, does nothing.
*/
requestUndoOnBackspace() {
if ( this.editor.plugins.has( 'UndoEditing' ) ) {
this._undoOnBackspace = true;
}
}
}
2 changes: 1 addition & 1 deletion packages/ckeditor5-typing/src/deleteobserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default class DeleteObserver extends Observer {
* @event module:engine/view/document~Document#event:delete
* @param {module:engine/view/observer/domeventdata~DomEventData} data
* @param {'forward'|'delete'} data.direction The direction in which the deletion should happen.
* @param {'character'|'word'} data.unit The "amount" of content that should be deleted.
* @param {'character'|'codePoint'|'word'} data.unit The "amount" of content that should be deleted.
* @param {Number} data.sequence A number describing which subsequent delete event it is without the key being released.
* If it's 2 or more it means that the key was pressed and hold.
* @param {module:engine/view/selection~Selection} [data.selectionToRemove] View selection which content should be removed. If not set,
Expand Down
16 changes: 14 additions & 2 deletions packages/ckeditor5-typing/src/texttransformation.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ const DEFAULT_TRANSFORMATIONS = [
* @extends module:core/plugin~Plugin
*/
export default class TextTransformation extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ 'Delete', 'Input' ];
}

/**
* @inheritDoc
*/
Expand Down Expand Up @@ -117,7 +124,8 @@ export default class TextTransformation extends Plugin {
_enableTransformationWatchers() {
const editor = this.editor;
const model = editor.model;
const input = editor.plugins.get( 'Input' );
const inputPlugin = editor.plugins.get( 'Input' );
const deletePlugin = editor.plugins.get( 'Delete' );
const normalizedTransformations = normalizeTransformations( editor.config.get( 'typing.transformations' ) );

const testCallback = text => {
Expand All @@ -132,7 +140,7 @@ export default class TextTransformation extends Plugin {
};

const watcherCallback = ( evt, data ) => {
if ( !input.isInput( data.batch ) ) {
if ( !inputPlugin.isInput( data.batch ) ) {
return;
}

Expand Down Expand Up @@ -164,6 +172,10 @@ export default class TextTransformation extends Plugin {

changeIndex += replaceWith.length;
}

model.enqueueChange( () => {
deletePlugin.requestUndoOnBackspace();
} );
} );
};

Expand Down
Loading

0 comments on commit 66073dd

Please sign in to comment.