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

Enabled block autoformat in non-empty blocks #7576

Merged
merged 15 commits into from
Jul 22, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
19 changes: 10 additions & 9 deletions packages/ckeditor5-autoformat/src/autoformat.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ export default class Autoformat extends Plugin {
const commands = this.editor.commands;

if ( commands.get( 'bulletedList' ) ) {
blockAutoformatEditing( this.editor, this, /^[*-]\s$/, 'bulletedList' );
blockAutoformatEditing( this.editor, this, /^[*-]\s/, 'bulletedList' );
}

if ( commands.get( 'numberedList' ) ) {
blockAutoformatEditing( this.editor, this, /^1[.|)]\s$/, 'numberedList' );
blockAutoformatEditing( this.editor, this, /^1[.|)]\s/, 'numberedList' );
}
}

Expand Down Expand Up @@ -123,16 +123,17 @@ export default class Autoformat extends Plugin {
if ( command ) {
command.modelElements
.filter( name => name.match( /^heading[1-6]$/ ) )
.forEach( commandValue => {
const level = commandValue[ 7 ];
const pattern = new RegExp( `^(#{${ level }})\\s$` );
.forEach( modelName => {
const level = modelName[ 7 ];
const pattern = new RegExp( `^(#{${ level }})\\s` );

blockAutoformatEditing( this.editor, this, pattern, () => {
if ( !command.isEnabled ) {
// Should only be active if command is enabled and heading style associated with pattern is inactive.
if ( !command.isEnabled || command.value === modelName ) {
return false;
}

this.editor.execute( 'heading', { value: commandValue } );
this.editor.execute( 'heading', { value: modelName } );
} );
} );
}
Expand All @@ -148,7 +149,7 @@ export default class Autoformat extends Plugin {
*/
_addBlockQuoteAutoformats() {
if ( this.editor.commands.get( 'blockQuote' ) ) {
blockAutoformatEditing( this.editor, this, /^>\s$/, 'blockQuote' );
blockAutoformatEditing( this.editor, this, /^>\s/, 'blockQuote' );
}
}

Expand All @@ -162,7 +163,7 @@ export default class Autoformat extends Plugin {
*/
_addCodeBlockAutoformats() {
if ( this.editor.commands.get( 'codeBlock' ) ) {
blockAutoformatEditing( this.editor, this, /^```$/, 'codeBlock' );
blockAutoformatEditing( this.editor, this, /^```/, 'codeBlock' );
}
}
}
Expand Down
25 changes: 22 additions & 3 deletions packages/ckeditor5-autoformat/src/blockautoformatediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import LiveRange from '@ckeditor/ckeditor5-engine/src/model/liverange';
import first from '@ckeditor/ckeditor5-utils/src/first';
import Position from '@ckeditor/ckeditor5-engine/src/model/position';

/**
* The block autoformatting engine. It allows to format various block patterns. For example,
Expand Down Expand Up @@ -82,18 +85,34 @@ export default function blockAutoformatEditing( editor, plugin, pattern, callbac

const blockToFormat = entry.position.parent;

// Block formatting should trigger only if the entire content of a paragraph is a single text node... (see ckeditor5#5671).
if ( !blockToFormat.is( 'paragraph' ) || blockToFormat.childCount !== 1 ) {
// Block formatting should be disabled in codeBlocks (#5800).
if ( blockToFormat.is( 'codeBlock' ) ) {
return;
}

const match = pattern.exec( blockToFormat.getChild( 0 ).data );
// In case a command is bound, do not re-execute it over an existing block style which would result with a style removal.
// Instead just drop processing so that autoformat trigger text is not lost. E.g. writing "# " in a level 1 heading.
if ( command && command.value === true ) {
return;
}

const firstNode = blockToFormat.getChild( 0 );
const match = pattern.exec( firstNode.data );

// ...and this text node's data match the pattern.
if ( !match ) {
return;
}

const range = first( editor.model.document.selection.getRanges() );

// We're only handling a collapsed selection that is right after the matched text (#5671).
const expectedPosition = ( new Position( range.root, firstNode.getPath() ) ).getShiftedBy( match[ 0 ].length );

if ( !( range.isCollapsed && range.end.isEqual( expectedPosition ) ) ) {
return;
}

// Use enqueueChange to create new batch to separate typing batch from the auto-format changes.
editor.model.enqueueChange( writer => {
// Matched range.
Expand Down
79 changes: 71 additions & 8 deletions packages/ckeditor5-autoformat/tests/autoformat.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '<listItem listIndent="0" listType="bulleted">[]</listItem>' );
} );

it( 'should replace a non-empty paragraph using the parenthesis format', () => {
setData( model, '<paragraph>*[]sample text</paragraph>' );
model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

expect( getData( model ) ).to.equal( '<listItem listIndent="0" listType="bulleted">[]sample text</listItem>' );
} );

it( 'should not replace minus character when inside bulleted list item', () => {
setData( model, '<listItem listIndent="0" listType="bulleted">-[]</listItem>' );
model.change( writer => {
Expand Down Expand Up @@ -115,6 +124,15 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '<listItem listIndent="0" listType="numbered">[]</listItem>' );
} );

it( 'should replace a non-empty paragraph using the parenthesis format', () => {
setData( model, '<paragraph>1)[]sample text</paragraph>' );
model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

expect( getData( model ) ).to.equal( '<listItem listIndent="0" listType="numbered">[]sample text</listItem>' );
} );

it( 'should not replace digit character when there is no . or ) in the format', () => {
setData( model, '<paragraph>1[]</paragraph>' );
model.change( writer => {
Expand Down Expand Up @@ -150,6 +168,24 @@ describe( 'Autoformat', () => {

expect( getData( model ) ).to.equal( '<paragraph>Foo<softBreak></softBreak>1. []</paragraph>' );
} );

it( 'should should be converted from a header', () => {
setData( model, '<heading1>1.[]</heading1>' );
model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

expect( getData( model ) ).to.equal( '<listItem listIndent="0" listType="numbered">[]</listItem>' );
} );

it( 'should should be converted from a bulleted list', () => {
setData( model, '<listItem listIndent="0" listType="bulleted">1.[]</listItem>' );
model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

expect( getData( model ) ).to.equal( '<listItem listIndent="0" listType="numbered">[]</listItem>' );
} );
} );

describe( 'Heading', () => {
Expand Down Expand Up @@ -243,6 +279,15 @@ describe( 'Autoformat', () => {

expect( getData( model ) ).to.equal( '<paragraph>Foo<softBreak></softBreak># []</paragraph>' );
} );

it( 'should convert a header that already contains a text', () => {
setData( model, '<heading1>###[]foo</heading1>' );
model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

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

describe( 'Block quote', () => {
Expand All @@ -255,13 +300,22 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '<blockQuote><paragraph>[]</paragraph></blockQuote>' );
} );

it( 'should not replace greater-than character when inside heading', () => {
it( 'should replace greater-than character in a non-empty paragraph', () => {
setData( model, '<paragraph>>[]foo</paragraph>' );
model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

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

it( 'should wrap the heading if greater-than character was used', () => {
setData( model, '<heading1>>[]</heading1>' );
model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

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

it( 'should not replace greater-than character when inside numbered list', () => {
Expand Down Expand Up @@ -302,22 +356,31 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '<codeBlock language="plaintext">[]</codeBlock>' );
} );

it( 'should not replace triple grave accents when already in a code block', () => {
setData( model, '<codeBlock language="plaintext">``[]</codeBlock>' );
it( 'should replace triple grave accents in a heading', () => {
setData( model, '<heading1>``[]</heading1>' );
model.change( writer => {
writer.insertText( '`', doc.selection.getFirstPosition() );
} );

expect( getData( model ) ).to.equal( '<codeBlock language="plaintext">```[]</codeBlock>' );
expect( getData( model ) ).to.equal( '<codeBlock language="plaintext">[]</codeBlock>' );
} );

it( 'should not replace triple grave accents when inside heading', () => {
setData( model, '<heading1>``[]</heading1>' );
it( 'should replace triple grave accents in a non-empty paragraph', () => {
setData( model, '<paragraph>``[]let foo = 1;</paragraph>' );
model.change( writer => {
writer.insertText( '`', doc.selection.getFirstPosition() );
} );

expect( getData( model ) ).to.equal( '<heading1>```[]</heading1>' );
expect( getData( model ) ).to.equal( '<codeBlock language="plaintext">[]let foo = 1;</codeBlock>' );
} );

it( 'should not replace triple grave accents when already in a code block', () => {
setData( model, '<codeBlock language="plaintext">``[]</codeBlock>' );
model.change( writer => {
writer.insertText( '`', doc.selection.getFirstPosition() );
} );

expect( getData( model ) ).to.equal( '<codeBlock language="plaintext">```[]</codeBlock>' );
} );

it( 'should not replace triple grave accents when inside numbered list', () => {
Expand Down
12 changes: 12 additions & 0 deletions packages/ckeditor5-autoformat/tests/blockautoformatediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,18 @@ describe( 'blockAutoformatEditing', () => {
sinon.assert.notCalled( spy );
} );

it( 'should not call callback when typing in the middle of block text', () => {
const spy = testUtils.sinon.spy();
blockAutoformatEditing( editor, plugin, /^[*]\s/, spy );

setData( model, '<paragraph>* foo[]bar</paragraph>' );
model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

sinon.assert.notCalled( spy );
} );

it( 'should not call callback when after inline element (typing after softBreak in a "matching" paragraph)', () => {
// Configure the schema.
model.schema.register( 'softBreak', {
Expand Down
6 changes: 1 addition & 5 deletions packages/ckeditor5-autoformat/tests/manual/autoformat.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

1. Type `>` and press the space in an empty paragraph to replace it with a block quote.

1. Type a number from the range **1-3** followed by a `.` and press space to replace an empty paragraph with a numbered list item.

1. Type a number from the range **1-3** followed by a `)` and press space to replace an empty paragraph with a numbered list item.
1. Type `1` followed by a `.` or `)` and press space to replace an empty paragraph with a numbered list item.

1. Type `*foobar*`/`_foobar_` to italicize `foobar`. `*`/`_` should be removed.

Expand All @@ -22,6 +20,4 @@

1. For every autoformat pattern: Undo until you'll see just the pattern (e.g. `- `). Typing should be then possible without triggering the autoformatting again.

1. Typing a different pattern in an already converted block **must not** trigger the autoformatting. For example, typing `- ` in a heading should not convert a heading to a list.

1. Type inline formatting (bold, italic, code, strikethrough) after a soft break (<kbd>Shift</kbd>+<kbd>Enter</kbd>).