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

#6693: Keyboard support for the widget type around feature #7387

Merged
merged 77 commits into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
eac90d2
Moved the isArrowKeyCode() helper to keyboard utils.
oleq May 15, 2020
0c757a1
Extracted more arrow keys–related utils to ckeditor5-utils.
oleq May 15, 2020
04ce633
(WIP) PoC of the keyboard support.
oleq May 15, 2020
6f42141
Merge branch 'master' into i/6693
oleq May 21, 2020
6a14814
TMP
oleq May 21, 2020
526beef
Merge branch 'master' into i/6693
oleq May 21, 2020
543d8a3
TMP
oleq May 21, 2020
c1ea5ef
Merge branch 'master' into i/6693
oleq May 26, 2020
e8c5e73
The model selectionPostFixer() should not alter the selection if a "f…
oleq May 27, 2020
b22aa44
Used a thin caret-like look for type around lines.
oleq May 27, 2020
824d731
Show the buttons and lines for all block widgets regarless of their l…
oleq May 27, 2020
767544b
Enabled typing support when in the "fake caret" mode.
oleq May 27, 2020
5dc4962
Code refactoring.
oleq May 27, 2020
6f179b0
Prevent default action on enter only if inserted a new paragraph.
oleq May 27, 2020
d5733aa
Animate the fake horizontal care from the full visibility onwards for…
oleq May 27, 2020
dfbf694
Remove the fake horizontal caret as soon as the editor is blurred. Th…
oleq May 27, 2020
a81a9cb
The keydown and enter listeners should not collide.
oleq May 27, 2020
a641a03
Added an outline to the type around fake caret for better visibility …
oleq May 28, 2020
96609c4
Tests: Added table and table cell properties to the widget type aroun…
oleq May 28, 2020
34a2b61
Integrated the fake horizontal caret with the widget selection handle.
oleq May 28, 2020
d3b3107
Made some of Widget plugin methods @protected instead of @private bec…
oleq Jun 2, 2020
bc3a51a
Added support for keyboard arrows handling when selection the widget …
oleq Jun 2, 2020
b315c4d
Merge branch 'master' into i/6693
oleq Jun 2, 2020
e9d5a0d
Tests: Added the selection-post-fixer test to make sure it does not s…
oleq Jun 2, 2020
7e98bee
Tests: Added tests for new keyboard helpers.
oleq Jun 2, 2020
da795f9
Code refactoring in styles. Use a single fake caret per-widget.
oleq Jun 3, 2020
33e9b37
Tests: Added tests fro the isSafeKeystroke() helper.
oleq Jun 3, 2020
8a3d137
Added the insert paragraph on (Shift+)Enter feature that used to be a…
oleq Jun 3, 2020
45ca5ba
Tests: Added tests for arrow keys and the "fake" type around caret.
oleq Jun 3, 2020
b7afa06
Tests: Added tests for various aspects of inserting a paragraph aroun…
oleq Jun 4, 2020
95cab0f
The InsertParagraphCommand should split position ancestors to find a …
oleq Jun 4, 2020
e091c9a
Tests: Moved some enter key integration tests from the Widget plugin …
oleq Jun 4, 2020
8472e70
Tests: Updated widget integration tests after obsolete CSS classes re…
oleq Jun 4, 2020
9ef4285
Tests: Updated Widget tests to consider WidgetTypeAround keyboard sup…
oleq Jun 4, 2020
036dcd5
Tests: Removed obsolete CSS classes related to the WidgetTypeAround p…
oleq Jun 4, 2020
d333024
Tests: Aligned table tests to the WidgetTypeAround feature.
oleq Jun 4, 2020
b342749
Docs: Added docs to the WidgetTypeAround plugin.
oleq Jun 4, 2020
c11a59f
Merge remote-tracking branch 'origin/master' into i/6693
oleq Jun 5, 2020
dddc9ce
Hide the widget selection handle when the "fake caret" is either befo…
oleq Jun 5, 2020
6be9c2b
Code refactoring.
oleq Jun 5, 2020
bc9a93e
Implemented a WidgetTypeAround integration with the delete/backspace.
oleq Jun 5, 2020
d537827
Merge remote-tracking branch 'origin/master' into i/6693
oleq Jun 5, 2020
c1db77b
Merge branch 'master' into i/6693
oleq Jun 9, 2020
a6da635
Update packages/ckeditor5-typing/tests/utils/injectunsafekeystrokesha…
oleq Jun 9, 2020
f80bef2
Update packages/ckeditor5-typing/tests/utils/injectunsafekeystrokesha…
oleq Jun 9, 2020
4691d85
Restored the widget :hover outline when the type around "fake caret" …
oleq Jun 9, 2020
cf9cca7
The widget selection handle should slowly fade out when the type arou…
oleq Jun 9, 2020
18f68f0
Renamed isSafeKeystroke() to isNonTypingKeystroke(). Added docs for t…
oleq Jun 9, 2020
5e527b9
Tests: Code refactoring in isNonTypingKeystroke() tests.
oleq Jun 9, 2020
b0e273e
Tests: Code refactoring in isNonTypingKeystroke() tests.
oleq Jun 9, 2020
0cb585f
Docs: Extended docs of various keyboard helpers.
oleq Jun 9, 2020
c77db0a
Added a missing dependency in package.json.
oleq Jun 9, 2020
415f0bf
Code refactoring: Got rid of the Paragraph dependency in the WidgetTy…
oleq Jun 9, 2020
abda6ea
Docs: Added missing docs in the WidgetTypeAround#_insertParagraphAcco…
oleq Jun 9, 2020
0888cc3
Code refactoring: Increased the priority of the keydown listeners in …
oleq Jun 9, 2020
022cada
Used a cached reference to a view widget in the WidgetTypeAround plug…
oleq Jun 9, 2020
c18a487
Update packages/ckeditor5-widget/src/widgettypearound/widgettypearoun…
oleq Jun 9, 2020
ffc5b13
Docs.
oleq Jun 9, 2020
a3de8bb
Code refactoring: Don't look for nearest selection range if not lavin…
oleq Jun 9, 2020
1936a53
Update packages/ckeditor5-widget/src/widgettypearound/widgettypearoun…
oleq Jun 9, 2020
b4aa473
Code refactoring: Split WidgetTypeAround#_handleArrowKeyPress into tw…
oleq Jun 9, 2020
56c857f
Code refactoring.
oleq Jun 9, 2020
f265208
Improved delete and backspace handling around blocks widgets when emp…
oleq Jun 9, 2020
ab41440
Tests: Used proper key codes for Shift, Ctrl and Alt instead of inter…
oleq Jun 9, 2020
771898e
Tests: Added a test to check if all 4 arrow keys activate the WidgetT…
oleq Jun 10, 2020
289dac8
Tests: Added tests for WidgetTypeAround using an arrow with a Shift m…
oleq Jun 10, 2020
09b57f8
Integrated WidgetTypeAround feature with the widget resize UI.
oleq Jun 10, 2020
3a531dd
Code refactoring: Made WidgetTypeAround#_insertParagraph accept a mod…
oleq Jun 10, 2020
241bfc1
Code refactoring in the WidgetTypeAround plugin.
oleq Jun 10, 2020
d77595e
Code refactoring in the WidgetTypeAround plugin.
oleq Jun 10, 2020
682402f
Code refactoring in the WidgetTypeAround feature.
oleq Jun 10, 2020
db1c036
Code refactoring: Use a cached model widget element to remove the typ…
oleq Jun 10, 2020
a253882
Improved some visual aspects of the widget when the fake caret is vis…
oleq Jun 10, 2020
9717d21
Internal: Added a missing ckeditor5-image dependency to package.json.
oleq Jun 10, 2020
73c2272
Code refactoring: Split the Widget keydown listener into two separate…
oleq Jun 10, 2020
d5a86cf
Refactoring in the logic responsible for deleting content around widg…
niegowski Jun 10, 2020
893a577
Merge branch 'master' into i/6693
oleq Jun 10, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,14 @@ function selectionPostFixer( writer, model ) {
// Those ranges might overlap but will be corrected later.
const correctedRange = tryFixingRange( modelRange, schema );

if ( correctedRange ) {
// "Selection fixing" algorithms sometimes get lost. In consequence, it may happen
// that a new range is returned but, in fact, it has the same positions as the original
// range anyway. If this range is not discarded, a new selection will be set and that,
// for instance, would destroy the selection attributes. Let's make sure that the post-fixer
// actually worked first before setting a new selection.
//
// https://github.com/ckeditor/ckeditor5/issues/6693
if ( correctedRange && !correctedRange.isEqual( modelRange ) ) {
ranges.push( correctedRange );
wasFixed = true;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,35 @@ describe( 'Selection post-fixer', () => {
'</table>]'
);
} );

it( 'should not reset the selection if the final range is the same as the initial one', () => {
setModelData( model,
'<table>' +
'<tableRow>' +
'<tableCell>[<image></image>]</tableCell>' +
'</tableRow>' +
'</table>'
);

// Setting a selection attribute will trigger the post-fixer. However, because this
// action does not affect ranges, the post-fixer should not set a new selection and,
// in consequence, should not clear the selection attribute (like it normally would when
// a new selection is set).
// https://github.com/ckeditor/ckeditor5/issues/6693
model.change( writer => {
writer.setSelectionAttribute( 'foo', 'bar' );
} );

assertEqualMarkup( getModelData( model ),
'<table>' +
'<tableRow>' +
'<tableCell>[<image></image>]</tableCell>' +
'</tableRow>' +
'</table>'
);

expect( model.document.selection.hasAttribute( 'foo' ) ).to.be.true;
} );
} );

describe( 'non-collapsed selection - image scenarios', () => {
Expand Down
6 changes: 0 additions & 6 deletions packages/ckeditor5-image/tests/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ describe( 'Image', () => {
expect( getViewData( view ) ).to.equal(
'[<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'ck-widget_selected image" contenteditable="false"' +
'>' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
Expand All @@ -82,7 +81,6 @@ describe( 'Image', () => {
expect( getViewData( view ) ).to.equal(
'[<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'ck-widget_selected image" contenteditable="false"' +
'>' +
'<img alt="" src="/assets/sample.png"></img>' +
Expand All @@ -103,15 +101,13 @@ describe( 'Image', () => {
expect( getViewData( view ) ).to.equal(
'[<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'ck-widget_selected image" contenteditable="false"' +
'>' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
'</figure>]' +
'<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'image" contenteditable="false"' +
'>' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
Expand All @@ -127,15 +123,13 @@ describe( 'Image', () => {
expect( getViewData( view ) ).to.equal(
'<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'image" contenteditable="false"' +
'>' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
'</figure>' +
'[<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'ck-widget_selected image" contenteditable="false"' +
'>' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
Expand Down
23 changes: 18 additions & 5 deletions packages/ckeditor5-paragraph/src/insertparagraphcommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import Command from '@ckeditor/ckeditor5-core/src/command';
* position: editor.model.createPositionBefore( element )
* } );
*
* If a paragraph is disallowed in the context of the specific position, the command
* will attempt to split position ancestors to find a place where it is possible
* to insert a paragraph.
*
* **Note**: This command moves the selection to the inserted paragraph.
*
* @extends module:core/command~Command
Expand All @@ -33,15 +37,24 @@ export default class InsertParagraphCommand extends Command {
*/
execute( options ) {
const model = this.editor.model;

if ( !model.schema.checkChild( options.position, 'paragraph' ) ) {
return;
}
let position = options.position;

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );

model.insertContent( paragraph, options.position );
if ( !model.schema.checkChild( position.parent, paragraph ) ) {
const allowedParent = model.schema.findAllowedParent( position, paragraph );

// It could be there's no ancestor limit that would allow paragraph.
// In theory, "paragraph" could be disallowed even in the "$root".
if ( !allowedParent ) {
return;
}

position = writer.split( position, allowedParent ).position;
}

model.insertContent( paragraph, position );

writer.setSelection( paragraph, 'in' );
} );
Expand Down
34 changes: 28 additions & 6 deletions packages/ckeditor5-paragraph/tests/insertparagraphcommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ describe( 'InsertParagraphCommand', () => {
editor.commands.add( 'insertParagraph', command );
schema.register( 'paragraph', { inheritAllFrom: '$block' } );
schema.register( 'heading1', { inheritAllFrom: '$block', allowIn: 'headersOnly' } );
schema.register( 'headersOnly', { inheritAllFrom: '$block' } );
schema.register( 'allowP', { inheritAllFrom: '$block' } );
schema.register( 'disallowP', { inheritAllFrom: '$block', allowIn: [ 'allowP' ] } );
model.schema.extend( 'paragraph', { allowIn: [ 'allowP' ] } );
} );
} );

Expand All @@ -42,18 +44,38 @@ describe( 'InsertParagraphCommand', () => {
expect( getData( model ) ).to.equal( '<paragraph>[]</paragraph><heading1>foo</heading1>' );
} );

it( 'should do nothing if the paragraph is not allowed at the provided position', () => {
setData( model, '<headersOnly><heading1>foo[]</heading1></headersOnly>' );
it( 'should split ancestors down to a limit where a paragraph is allowed', () => {
setData( model, '<allowP><disallowP>foo</disallowP></allowP>' );

command.execute( {
position: model.createPositionBefore( root.getChild( 0 ).getChild( 0 ) )
// fo[]o
position: model.createPositionAt( root.getChild( 0 ).getChild( 0 ), 2 )
} );

expect( getData( model ) ).to.equal(
'<allowP>' +
'<disallowP>fo</disallowP>' +
'<paragraph>[]</paragraph>' +
'<disallowP>o</disallowP>' +
'</allowP>'
);
} );

it( 'should do nothing if the paragraph is not allowed at the provided position', () => {
// Create a situation where "paragraph" is disallowed even in the "root".
schema.addChildCheck( ( context, childDefinition ) => {
if ( context.endsWith( '$root' ) && childDefinition.name == 'paragraph' ) {
return false;
}
} );

setData( model, '<heading1>foo[]</heading1>' );

command.execute( {
position: model.createPositionAfter( root.getChild( 0 ).getChild( 0 ) )
position: model.createPositionBefore( root.getChild( 0 ) )
} );

expect( getData( model ) ).to.equal( '<headersOnly><heading1>foo[]</heading1></headersOnly>' );
expect( getData( model ) ).to.equal( '<heading1>foo[]</heading1>' );
} );

describe( 'interation with existing paragraphs in the content', () => {
Expand Down
43 changes: 6 additions & 37 deletions packages/ckeditor5-table/src/tablekeyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import TableWalker from './tablewalker';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
import {
isArrowKeyCode,
getLocalizedArrowKeyCodeDirection
} from '@ckeditor/ckeditor5-utils/src/keyboard';
import { getSelectedTableCells, getTableCellsContainingSelection } from './utils/selection';
import { findAncestor } from './utils/common';

Expand Down Expand Up @@ -163,13 +166,14 @@ export default class TableKeyboard extends Plugin {
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
*/
_onKeydown( eventInfo, domEventData ) {
const editor = this.editor;
const keyCode = domEventData.keyCode;

if ( !isArrowKeyCode( keyCode ) ) {
return;
}

const direction = getDirectionFromKeyCode( keyCode, this.editor.locale.contentLanguageDirection );
const direction = getLocalizedArrowKeyCodeDirection( keyCode, editor.locale.contentLanguageDirection );
const wasHandled = this._handleArrowKeys( direction, domEventData.shiftKey );

if ( wasHandled ) {
Expand Down Expand Up @@ -515,38 +519,3 @@ export default class TableKeyboard extends Plugin {
}
}

// Returns `true` if the provided key code represents one of the arrow keys.
//
// @private
// @param {Number} keyCode
// @returns {Boolean}
function isArrowKeyCode( keyCode ) {
return keyCode == keyCodes.arrowright ||
keyCode == keyCodes.arrowleft ||
keyCode == keyCodes.arrowup ||
keyCode == keyCodes.arrowdown;
}

// Returns the direction name from `keyCode`.
//
// @private
// @param {Number} keyCode
// @param {String} contentLanguageDirection The content language direction.
// @returns {'left'|'up'|'right'|'down'} Arrow direction.
function getDirectionFromKeyCode( keyCode, contentLanguageDirection ) {
const isLtrContent = contentLanguageDirection === 'ltr';

switch ( keyCode ) {
case keyCodes.arrowleft:
return isLtrContent ? 'left' : 'right';

case keyCodes.arrowright:
return isLtrContent ? 'right' : 'left';

case keyCodes.arrowup:
return 'up';

case keyCodes.arrowdown:
return 'down';
}
}
10 changes: 7 additions & 3 deletions packages/ckeditor5-table/tests/converters/upcasttable.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
import { getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import ImageEditing from '@ckeditor/ckeditor5-image/src/image/imageediting';
Expand All @@ -17,8 +17,8 @@ describe( 'upcastTable()', () => {
let editor, model;

beforeEach( () => {
return VirtualTestEditor
.create( {
return ClassicTestEditor
.create( '', {
plugins: [ TableEditing, Paragraph, ImageEditing, Widget ]
} )
.then( newEditor => {
Expand All @@ -31,6 +31,10 @@ describe( 'upcastTable()', () => {
} );
} );

afterEach( () => {
editor.destroy();
} );

function expectModel( data ) {
assertEqualMarkup( getModelData( model, { withoutSelection: true } ), data );
}
Expand Down
26 changes: 19 additions & 7 deletions packages/ckeditor5-table/tests/table-integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
import ListEditing from '@ckeditor/ckeditor5-list/src/listediting';
import BlockQuoteEditing from '@ckeditor/ckeditor5-block-quote/src/blockquoteediting';
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
import {
getData as getModelData,
setData as setModelData
Expand All @@ -27,14 +27,18 @@ describe( 'Table feature – integration', () => {
let editor, clipboard;

beforeEach( () => {
return VirtualTestEditor
.create( { plugins: [ Paragraph, TableEditing, ListEditing, BlockQuoteEditing, Widget, Clipboard ] } )
return ClassicTestEditor
.create( '', { plugins: [ Paragraph, TableEditing, ListEditing, BlockQuoteEditing, Widget, Clipboard ] } )
.then( newEditor => {
editor = newEditor;
clipboard = editor.plugins.get( 'Clipboard' );
} );
} );

afterEach( () => {
editor.destroy();
} );

it( 'pastes td as p when pasting into the table', () => {
setModelData( editor.model, modelTable( [ [ 'foo[]' ] ] ) );

Expand Down Expand Up @@ -86,15 +90,19 @@ describe( 'Table feature – integration', () => {
let editor, doc, root;

beforeEach( () => {
return VirtualTestEditor
.create( { plugins: [ Paragraph, TableEditing, Widget, UndoEditing ] } )
return ClassicTestEditor
.create( '', { plugins: [ Paragraph, TableEditing, Widget, UndoEditing ] } )
.then( newEditor => {
editor = newEditor;
doc = editor.model.document;
root = doc.getRoot();
} );
} );

afterEach( () => {
editor.destroy();
} );

it( 'fixing empty roots should be transparent to undo', () => {
expect( editor.getData( { trim: 'none' } ) ).to.equal( '<p>&nbsp;</p>' );
expect( editor.commands.get( 'undo' ).isEnabled ).to.be.false;
Expand Down Expand Up @@ -155,13 +163,17 @@ describe( 'Table feature – integration', () => {
let editor;

beforeEach( () => {
return VirtualTestEditor
.create( { plugins: [ Paragraph, TableEditing, ListEditing, BlockQuoteEditing, Widget, Typing ] } )
return ClassicTestEditor
.create( '', { plugins: [ Paragraph, TableEditing, ListEditing, BlockQuoteEditing, Widget, Typing ] } )
.then( newEditor => {
editor = newEditor;
} );
} );

afterEach( () => {
editor.destroy();
} );

it( 'merges elements without throwing errors', () => {
setModelData( editor.model, modelTable( [
[ '<blockQuote><paragraph>Foo</paragraph></blockQuote><paragraph>[]Bar</paragraph>' ]
Expand Down
3 changes: 3 additions & 0 deletions packages/ckeditor5-table/tests/tablekeyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -2769,6 +2769,9 @@ describe( 'TableKeyboard', () => {
[ '20', '21', '22' ]
] ) );

// Note: Two keydowns are necessary because the first one is handled by the WidgetTypeAround plugin
// to activate the "fake caret".
editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );

assertEqualMarkup( getModelData( model ), modelTable( [
Expand Down
Loading