Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #65 from ckeditor/t/64
Browse files Browse the repository at this point in the history
Feature: `BlockAutoformatEditing` will not format if the command is disabled. `InlineAutoformatEditing` will not format if the callback returned `false`. Closes #64.
  • Loading branch information
Piotr Jasiun authored Feb 4, 2019
2 parents 200f4b0 + ef5beee commit cc7f454
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 25 deletions.
44 changes: 37 additions & 7 deletions src/autoformat.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,29 @@ export default class Autoformat extends Plugin {

if ( commands.get( 'bold' ) ) {
/* eslint-disable no-new */
new InlineAutoformatEditing( this.editor, /(\*\*)([^*]+)(\*\*)$/g, 'bold' );
new InlineAutoformatEditing( this.editor, /(__)([^_]+)(__)$/g, 'bold' );
const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' );

new InlineAutoformatEditing( this.editor, /(\*\*)([^*]+)(\*\*)$/g, boldCallback );
new InlineAutoformatEditing( this.editor, /(__)([^_]+)(__)$/g, boldCallback );
/* eslint-enable no-new */
}

if ( commands.get( 'italic' ) ) {
/* eslint-disable no-new */
const italicCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'italic' );

// The italic autoformatter cannot be triggered by the bold markers, so we need to check the
// text before the pattern (e.g. `(?:^|[^\*])`).

/* eslint-disable no-new */
new InlineAutoformatEditing( this.editor, /(?:^|[^*])(\*)([^*_]+)(\*)$/g, 'italic' );
new InlineAutoformatEditing( this.editor, /(?:^|[^_])(_)([^_]+)(_)$/g, 'italic' );
new InlineAutoformatEditing( this.editor, /(?:^|[^*])(\*)([^*_]+)(\*)$/g, italicCallback );
new InlineAutoformatEditing( this.editor, /(?:^|[^_])(_)([^_]+)(_)$/g, italicCallback );
/* eslint-enable no-new */
}

if ( commands.get( 'code' ) ) {
/* eslint-disable no-new */
new InlineAutoformatEditing( this.editor, /(`)([^`]+)(`)$/g, 'code' );
const codeCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'code' );

new InlineAutoformatEditing( this.editor, /(`)([^`]+)(`)$/g, codeCallback );
/* eslint-enable no-new */
}
}
Expand Down Expand Up @@ -144,3 +149,28 @@ export default class Autoformat extends Plugin {
}
}
}

// Helper function for getting `InlineAutoformatEditing` callbacks that checks if command is enabled.
//
// @param {module:core/editor/editor~Editor} editor
// @param {String} attributeKey
// @returns {Function}
function getCallbackFunctionForInlineAutoformat( editor, attributeKey ) {
return ( writer, rangesToFormat ) => {
const command = editor.commands.get( attributeKey );

if ( !command.isEnabled ) {
return false;
}

const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );

for ( const range of validRanges ) {
writer.setAttribute( attributeKey, true, range );
}

// After applying attribute to the text, remove given attribute from the selection.
// This way user is able to type a text without attribute used by auto formatter.
writer.removeSelectionAttribute( attributeKey );
};
}
11 changes: 8 additions & 3 deletions src/blockautoformatediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default class BlockAutoformatEditing {
*
* Examples of usage:
*
* To convert a paragraph to heading 1 when `- ` is typed, using just the commmand name:
* To convert a paragraph to heading 1 when `- ` is typed, using just the command name:
*
* new BlockAutoformatEditing( editor, /^\- $/, 'heading1' );
*
Expand All @@ -49,19 +49,24 @@ export default class BlockAutoformatEditing {
*/
constructor( editor, pattern, callbackOrCommand ) {
let callback;
let command = null;

if ( typeof callbackOrCommand == 'function' ) {
callback = callbackOrCommand;
} else {
// We assume that the actual command name was provided.
const command = callbackOrCommand;
command = editor.commands.get( callbackOrCommand );

callback = () => {
editor.execute( command );
editor.execute( callbackOrCommand );
};
}

editor.model.document.on( 'change', ( evt, batch ) => {
if ( command && !command.isEnabled ) {
return;
}

if ( batch.type == 'transparent' ) {
return;
}
Expand Down
27 changes: 20 additions & 7 deletions src/inlineautoformatediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,23 @@ export default class InlineAutoformatEditing {
* }
*
* @param {Function|String} attributeOrCallback The name of attribute to apply on matching text or a callback for manual
* formatting.
* formatting. If callback is passed it should return `false` if changes should not be applied (e.g. if a command is disabled).
*
* // Use attribute name:
* new InlineAutoformatEditing( editor, /(\*\*)([^\*]+?)(\*\*)$/g, 'bold' );
*
* // Use formatting callback:
* new InlineAutoformatEditing( editor, /(\*\*)([^\*]+?)(\*\*)$/g, ( writer, validRanges ) => {
* new InlineAutoformatEditing( editor, /(\*\*)([^\*]+?)(\*\*)$/g, ( writer, rangesToFormat ) => {
* const command = editor.commands.get( 'bold' );
*
* if ( !command.isEnabled ) {
* return false;
* }
*
* const validRanges = editor.model.schema.getValidRanges( rangesToFormat, 'bold' );
*
* for ( let range of validRanges ) {
* writer.setAttribute( command, true, range );
* writer.setAttribute( 'bold', true, range );
* }
* } );
*/
Expand Down Expand Up @@ -128,7 +136,9 @@ export default class InlineAutoformatEditing {
} );

// A format callback run on matched text.
formatCallback = formatCallback || ( ( writer, validRanges ) => {
formatCallback = formatCallback || ( ( writer, rangesToFormat ) => {
const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );

for ( const range of validRanges ) {
writer.setAttribute( attributeKey, true, range );
}
Expand Down Expand Up @@ -170,10 +180,13 @@ export default class InlineAutoformatEditing {

// Use enqueueChange to create new batch to separate typing batch from the auto-format changes.
editor.model.enqueueChange( writer => {
const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );

// Apply format.
formatCallback( writer, validRanges );
const hasChanged = formatCallback( writer, rangesToFormat );

// Strict check on `false` to have backward compatibility (when callbacks were returning `undefined`).
if ( hasChanged === false ) {
return;
}

// Remove delimiters - use reversed order to not mix the offsets while removing.
for ( const range of rangesToRemove.reverse() ) {
Expand Down
16 changes: 16 additions & 0 deletions tests/autoformat.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,22 @@ describe( 'Autoformat', () => {

expect( getData( model ) ).to.equal( '<paragraph>foo <$text bold="true">bar</$text>[] baz</paragraph>' );
} );

it( 'should not format if the command is not enabled', () => {
model.schema.addAttributeCheck( ( context, attributeName ) => {
if ( attributeName == 'bold' ) {
return false;
}
} );

setData( model, '<paragraph>**foobar*[]</paragraph>' );

model.change( writer => {
writer.insertText( '*', doc.selection.getFirstPosition() );
} );

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

describe( 'without commands', () => {
Expand Down
40 changes: 35 additions & 5 deletions tests/blockautoformatediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* For licensing, see LICENSE.md.
*/

import Autoformat from '../src/autoformat';
import BlockAutoformatEditing from '../src/blockautoformatediting';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
Expand All @@ -19,7 +20,7 @@ describe( 'BlockAutoformatEditing', () => {
beforeEach( () => {
return VirtualTestEditor
.create( {
plugins: [ Enter, Paragraph ]
plugins: [ Enter, Paragraph, Autoformat ]
} )
.then( newEditor => {
editor = newEditor;
Expand All @@ -28,13 +29,17 @@ describe( 'BlockAutoformatEditing', () => {
} );
} );

describe( 'Command name', () => {
describe( 'command name', () => {
it( 'should run a command when the pattern is matched', () => {
const spy = testUtils.sinon.spy();
editor.commands.add( 'testCommand', new TestCommand( editor, spy ) );
const testCommand = new TestCommand( editor, spy );

editor.commands.add( 'testCommand', testCommand );

new BlockAutoformatEditing( editor, /^[*]\s$/, 'testCommand' ); // eslint-disable-line no-new

setData( model, '<paragraph>*[]</paragraph>' );

model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );
Expand All @@ -44,20 +49,45 @@ describe( 'BlockAutoformatEditing', () => {

it( 'should remove found pattern', () => {
const spy = testUtils.sinon.spy();
editor.commands.add( 'testCommand', new TestCommand( editor, spy ) );
const testCommand = new TestCommand( editor, spy );

editor.commands.add( 'testCommand', testCommand );

new BlockAutoformatEditing( editor, /^[*]\s$/, 'testCommand' ); // eslint-disable-line no-new

setData( model, '<paragraph>*[]</paragraph>' );

model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

sinon.assert.calledOnce( spy );
expect( getData( model ) ).to.equal( '<paragraph>[]</paragraph>' );
} );

it( 'should not autoformat if command is disabled', () => {
const spy = testUtils.sinon.spy();
const testCommand = new TestCommand( editor, spy );

testCommand.refresh = function() {
this.isEnabled = false;
};

editor.commands.add( 'testCommand', testCommand );

new BlockAutoformatEditing( editor, /^[*]\s$/, 'testCommand' ); // eslint-disable-line no-new

setData( model, '<paragraph>*[]</paragraph>' );

model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

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

describe( 'Callback', () => {
describe( 'callback', () => {
it( 'should run callback when the pattern is matched', () => {
const spy = testUtils.sinon.spy();
new BlockAutoformatEditing( editor, /^[*]\s$/, spy ); // eslint-disable-line no-new
Expand Down
26 changes: 24 additions & 2 deletions tests/inlineautoformatediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* For licensing, see LICENSE.md.
*/

import Autoformat from '../src/autoformat';
import InlineAutoformatEditing from '../src/inlineautoformatediting';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
Expand All @@ -18,7 +19,7 @@ describe( 'InlineAutoformatEditing', () => {
beforeEach( () => {
return VirtualTestEditor
.create( {
plugins: [ Enter, Paragraph ]
plugins: [ Enter, Paragraph, Autoformat ]
} )
.then( newEditor => {
editor = newEditor;
Expand Down Expand Up @@ -64,7 +65,7 @@ describe( 'InlineAutoformatEditing', () => {
} );
} );

describe( 'Callback', () => {
describe( 'callback', () => {
it( 'should stop when there are no format ranges returned from testCallback', () => {
const formatSpy = testUtils.sinon.spy();
const testStub = testUtils.sinon.stub().returns( {
Expand Down Expand Up @@ -115,6 +116,27 @@ describe( 'InlineAutoformatEditing', () => {

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

it( 'should not autoformat if callback returned false', () => {
setData( model, '<paragraph>Foobar[]</paragraph>' );

const p = model.document.getRoot().getChild( 0 );

const testCallback = () => ( {
format: [ model.createRange( model.createPositionAt( p, 0 ), model.createPositionAt( p, 3 ) ) ],
remove: [ model.createRange( model.createPositionAt( p, 0 ), model.createPositionAt( p, 3 ) ) ]
} );

const formatCallback = () => false;

new InlineAutoformatEditing( editor, testCallback, formatCallback ); // eslint-disable-line no-new

model.change( writer => {
writer.insertText( ' ', doc.selection.getFirstPosition() );
} );

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

it( 'should ignore transparent batches', () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/undointegration.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtest
import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';

describe( 'Autoformat', () => {
describe( 'Autoformat undo integration', () => {
let editor, model, doc;

testUtils.createSinonSandbox();
Expand Down

0 comments on commit cc7f454

Please sign in to comment.