Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BubblingEmitterMixin #9023

Merged
merged 52 commits into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
53402db
PoC of events bubbling over model document tree.
niegowski Dec 11, 2020
ce2926e
Added support for bubbling arrow keys events.
niegowski Dec 11, 2020
8165c93
Added tests for features affected by bubbling events.
niegowski Dec 13, 2020
79b8efb
Removed unnecessary priority manipulation.
niegowski Dec 13, 2020
69db918
Updated JsDocs.
niegowski Dec 14, 2020
ee7aab5
Fixed delete handler for widget neighborhood.
niegowski Dec 21, 2020
7d9aa16
Merge conflicts.
niegowski Feb 7, 2021
a275562
Merge conflicts.
niegowski Feb 7, 2021
a386058
EmitterMixin should have a separate methods for listener and emitter.
niegowski Feb 7, 2021
fa95bea
Introducing BubblingObserver.
niegowski Feb 7, 2021
5a950db
Introducing ArrowKeysObserver (a BubblingObserver).
niegowski Feb 8, 2021
58616f7
Refactored custom context matcher.
niegowski Feb 8, 2021
251f06a
Merge branch 'master' into i/8640
niegowski Feb 8, 2021
bd2b4cd
Refactored the way of intercepting registration of listeners.
niegowski Feb 8, 2021
caf718b
Cleaning tests.
niegowski Feb 8, 2021
4d4ac0e
Cleaning tests.
niegowski Feb 8, 2021
41a4505
Properly recover decorated methods on destroying.
niegowski Feb 8, 2021
c7ed873
Bubbling should start from the deepest node for non collapsed selection.
niegowski Feb 9, 2021
6454502
Simplified API by reducing redundant name for the custom context.
niegowski Feb 10, 2021
18558b1
Simplified internals of custom contexts.
niegowski Feb 11, 2021
6d9622f
Added docs for BubblingObserver.
niegowski Feb 11, 2021
e77f953
Added docs for BubblingObserver.
niegowski Feb 11, 2021
8be3a2b
Added option to register one handler for multiple contexts.
niegowski Feb 11, 2021
5474e7d
Added missing tests.
niegowski Feb 11, 2021
ea98da1
Added missing tests.
niegowski Feb 11, 2021
a8ab3c3
Added missing tests.
niegowski Feb 12, 2021
32364d8
ArrowKeysObserver extracted as a separate observer.
niegowski Feb 12, 2021
5401c35
Refactored event binding interception. Updated tests.
niegowski Feb 12, 2021
78cc563
Merge branch 'master' into i/8640
niegowski Feb 12, 2021
a6f864d
Added option to the EmitterMixin to attach to the emitter that does n…
niegowski Feb 12, 2021
a27fe5c
BubblingObserver refactored as an BubblingEmitterMixin.
niegowski Feb 15, 2021
28ba988
BubblingEmitterMixin refactor.
niegowski Feb 15, 2021
9fe86fb
Added tests.
niegowski Feb 15, 2021
cef93fb
Added docs.
niegowski Feb 15, 2021
318dd20
Added missing test.
niegowski Feb 16, 2021
c556194
Simplified mixing of BubblingEmitterMixin.
niegowski Feb 16, 2021
8c9497d
Updated JsDoc.
niegowski Feb 16, 2021
965ea5c
Updated comment.
niegowski Feb 16, 2021
03d2362
Introducing BubblingEventInfo.
niegowski Feb 21, 2021
cca2af6
Merge branch 'master' into i/8640
Reinmar Feb 22, 2021
75d754d
Updated JSDoc.
niegowski Feb 22, 2021
296674a
Updated test name.
niegowski Feb 22, 2021
b4e48f0
The enter default listeners back to low priority.
niegowski Feb 22, 2021
ecd9392
The delete default listeners moved to low priority.
niegowski Feb 22, 2021
09ffe91
Added details about mocked objects in tests.
niegowski Feb 22, 2021
b4942cf
Updated code comment.
niegowski Feb 22, 2021
af200e6
Updated editing engine guide to include ArrowKeysObserver.
niegowski Feb 22, 2021
f2dcc63
Merge branch 'master' into i/8640-bubbling-events
niegowski Feb 23, 2021
27e84cd
Merge branch 'master' into i/8640-bubbling-events
niegowski Mar 1, 2021
78c0d63
Merge branch 'master' into i/8640-bubbling-events
niegowski Mar 3, 2021
bef0dd2
Merge branch 'master' into i/8640-bubbling-events
niegowski Mar 4, 2021
9e65219
Merge branch 'master' into i/8640
Reinmar Mar 8, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/framework/guides/architecture/editing-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ By default, the view adds the following observers:
* {@link module:engine/view/observer/keyobserver~KeyObserver}
* {@link module:engine/view/observer/fakeselectionobserver~FakeSelectionObserver}
* {@link module:engine/view/observer/compositionobserver~CompositionObserver}
* {@link module:engine/view/observer/arrowkeysobserver~ArrowKeysObserver}

Additionally, some features add their own observers. For instance, the {@link module:clipboard/clipboard~Clipboard clipboard feature} adds {@link module:clipboard/clipboardobserver~ClipboardObserver}.

Expand Down
18 changes: 11 additions & 7 deletions packages/ckeditor5-block-quote/src/blockquoteediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
*/

import { Plugin } from 'ckeditor5/src/core';
import { priorities } from 'ckeditor5/src/utils';
import { Enter } from 'ckeditor5/src/enter';
import { Delete } from 'ckeditor5/src/typing';

import BlockQuoteCommand from './blockquotecommand';

Expand All @@ -27,6 +28,13 @@ export default class BlockQuoteEditing extends Plugin {
return 'BlockQuoteEditing';
}

/**
* @inheritDoc
*/
static get requires() {
return [ Enter, Delete ];
}

/**
* @inheritDoc
*/
Expand Down Expand Up @@ -110,8 +118,6 @@ export default class BlockQuoteEditing extends Plugin {

// Overwrite default Enter key behavior.
// If Enter key is pressed with selection collapsed in empty block inside a quote, break the quote.
//
// Priority normal - 10 to override default handler but not list's feature listener.
this.listenTo( viewDocument, 'enter', ( evt, data ) => {
if ( !selection.isCollapsed || !blockQuoteCommand.value ) {
return;
Expand All @@ -126,12 +132,10 @@ export default class BlockQuoteEditing extends Plugin {
data.preventDefault();
evt.stop();
}
}, { priority: priorities.normal - 10 } );
}, { context: 'blockquote' } );

// Overwrite default Backspace key behavior.
// If Backspace key is pressed with selection collapsed in first empty block inside a quote, break the quote.
//
// Priority high + 5 to override widget's feature listener but not list's feature listener.
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
if ( data.direction != 'backward' || !selection.isCollapsed || !blockQuoteCommand.value ) {
return;
Expand All @@ -146,6 +150,6 @@ export default class BlockQuoteEditing extends Plugin {
data.preventDefault();
evt.stop();
}
}, { priority: priorities.high + 5 } );
}, { context: 'blockquote' } );
}
}
56 changes: 56 additions & 0 deletions packages/ckeditor5-block-quote/tests/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,62 @@ describe( 'BlockQuote integration', () => {
'<paragraph>y</paragraph>'
);
} );

it( 'does nothing if selection is in an empty block but not in a block quote', () => {
const data = fakeEventData();
const execSpy = sinon.spy( editor, 'execute' );

setModelData( model, '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>x</paragraph>' );

viewDocument.fire( 'delete', data );

// Only enter command should be executed.
expect( data.preventDefault.called ).to.be.true;
expect( execSpy.calledOnce ).to.be.true;
expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'delete' );
} );

it( 'does nothing if selection is in a non-empty block (at the end) in a block quote', () => {
const data = fakeEventData();
const execSpy = sinon.spy( editor, 'execute' );

setModelData( model, '<blockQuote><paragraph>xx[]</paragraph></blockQuote>' );

viewDocument.fire( 'delete', data );

// Only enter command should be executed.
expect( data.preventDefault.called ).to.be.true;
expect( execSpy.calledOnce ).to.be.true;
expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'delete' );
} );

it( 'does nothing if selection is in a non-empty block (at the beginning) in a block quote', () => {
const data = fakeEventData();
const execSpy = sinon.spy( editor, 'execute' );

setModelData( model, '<blockQuote><paragraph>[]xx</paragraph></blockQuote>' );

viewDocument.fire( 'delete', data );

// Only enter command should be executed.
expect( data.preventDefault.called ).to.be.true;
expect( execSpy.calledOnce ).to.be.true;
expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'delete' );
} );

it( 'does nothing if selection is not collapsed', () => {
const data = fakeEventData();
const execSpy = sinon.spy( editor, 'execute' );

setModelData( model, '<blockQuote><paragraph>[</paragraph><paragraph>]</paragraph></blockQuote>' );

viewDocument.fire( 'delete', data );

// Only enter command should be executed.
expect( data.preventDefault.called ).to.be.true;
expect( execSpy.calledOnce ).to.be.true;
expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'delete' );
} );
} );

// Historically, due to problems with schema, images were not quotable.
Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-code-block/src/codeblockediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export default class CodeBlockEditing extends Plugin {

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

Expand Down
22 changes: 22 additions & 0 deletions packages/ckeditor5-code-block/tests/codeblockediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,28 @@ describe( 'CodeBlockEditing', () => {
sinon.assert.notCalled( shiftEnterCommand.execute );
} );

it( 'should execute enter command when pressing enter in an element nested inside a codeBlock', () => {
model.schema.register( 'codeBlockSub', { allowIn: 'codeBlock', isInline: true } );
model.schema.extend( '$text', { allowIn: 'codeBlockSub' } );
editor.conversion.elementToElement( { model: 'codeBlockSub', view: 'codeBlockSub' } );

const enterCommand = editor.commands.get( 'enter' );
const shiftEnterCommand = editor.commands.get( 'shiftEnter' );

sinon.spy( enterCommand, 'execute' );
sinon.spy( shiftEnterCommand, 'execute' );

setModelData( model, '<codeBlock>foo<codeBlockSub>b[]a</codeBlockSub>r</codeBlock>' );

viewDoc.fire( 'enter', getEvent() );

expect( getModelData( model ) ).to.equal(
'<codeBlock>foo<codeBlockSub>b</codeBlockSub><codeBlockSub>[]a</codeBlockSub>r</codeBlock>'
);
sinon.assert.calledOnce( enterCommand.execute );
sinon.assert.notCalled( shiftEnterCommand.execute );
} );

describe( 'indentation retention', () => {
it( 'should work when indentation is with spaces', () => {
setModelData( model, '<codeBlock language="css">foo[]</codeBlock>' );
Expand Down
3 changes: 3 additions & 0 deletions packages/ckeditor5-engine/src/view/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import DocumentSelection from './documentselection';
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import mix from '@ckeditor/ckeditor5-utils/src/mix';
import BubblingEmitterMixin from './observer/bubblingemittermixin';
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';

// @if CK_DEBUG_ENGINE // const { logDocument } = require( '../dev-utils/utils' );
Expand All @@ -18,6 +19,7 @@ import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
* Document class creates an abstract layer over the content editable area, contains a tree of view elements and
* {@link module:engine/view/documentselection~DocumentSelection view selection} associated with this document.
*
* @mixes module:engine/view/observer/bubblingemittermixin~BubblingEmitterMixin
* @mixes module:utils/observablemixin~ObservableMixin
*/
export default class Document {
Expand Down Expand Up @@ -203,6 +205,7 @@ export default class Document {
// @if CK_DEBUG_ENGINE // }
}

mix( Document, BubblingEmitterMixin );
mix( Document, ObservableMixin );

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-engine/src/view/filler.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export function getDataWithoutFiller( domText ) {
* @param {module:engine/view/view~View} view View controller instance we should inject quirks handling on.
*/
export function injectQuirksHandling( view ) {
view.document.on( 'keydown', jumpOverInlineFiller );
view.document.on( 'arrowKey', jumpOverInlineFiller, { priority: 'low' } );
}

// Move cursor from the end of the inline filler to the beginning of it when, so the filler does not break navigation.
Expand Down
58 changes: 58 additions & 0 deletions packages/ckeditor5-engine/src/view/observer/arrowkeysobserver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module engine/view/observer/arrowkeysobserver
*/

import Observer from './observer';
import BubblingEventInfo from './bubblingeventinfo';

import { isArrowKeyCode } from '@ckeditor/ckeditor5-utils';

/**
* Arrow keys observer introduces the {@link module:engine/view/document~Document#event:arrowKey `Document#arrowKey`} event.
*
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
*
* @extends module:engine/view/observer/observer~Observer
*/
export default class ArrowKeysObserver extends Observer {
/**
* @inheritDoc
*/
constructor( view ) {
super( view );

this.document.on( 'keydown', ( event, data ) => {
if ( this.isEnabled && isArrowKeyCode( data.keyCode ) ) {
const eventInfo = new BubblingEventInfo( this.document, 'arrowKey', this.document.selection.getFirstRange() );

this.document.fire( eventInfo, data );

if ( eventInfo.stop.called ) {
event.stop();
}
}
} );
}

/**
* @inheritDoc
*/
observe() {}
}

/**
* Event fired when the user presses an arrow keys.
*
* Introduced by {@link module:engine/view/observer/arrowkeysobserver~ArrowKeysObserver}.
*
* Note that because {@link module:engine/view/observer/arrowkeysobserver~ArrowKeysObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @event module:engine/view/document~Document#event:arrowKey
* @param {module:engine/view/observer/domeventdata~DomEventData} data
*/
Loading