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

Commit cc7f454

Browse files
author
Piotr Jasiun
authoredFeb 4, 2019
Merge pull request #65 from ckeditor/t/64
Feature: `BlockAutoformatEditing` will not format if the command is disabled. `InlineAutoformatEditing` will not format if the callback returned `false`. Closes #64.
2 parents 200f4b0 + ef5beee commit cc7f454

7 files changed

+141
-25
lines changed
 

‎src/autoformat.js

+37-7
Original file line numberDiff line numberDiff line change
@@ -78,24 +78,29 @@ export default class Autoformat extends Plugin {
7878

7979
if ( commands.get( 'bold' ) ) {
8080
/* eslint-disable no-new */
81-
new InlineAutoformatEditing( this.editor, /(\*\*)([^*]+)(\*\*)$/g, 'bold' );
82-
new InlineAutoformatEditing( this.editor, /(__)([^_]+)(__)$/g, 'bold' );
81+
const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' );
82+
83+
new InlineAutoformatEditing( this.editor, /(\*\*)([^*]+)(\*\*)$/g, boldCallback );
84+
new InlineAutoformatEditing( this.editor, /(__)([^_]+)(__)$/g, boldCallback );
8385
/* eslint-enable no-new */
8486
}
8587

8688
if ( commands.get( 'italic' ) ) {
89+
/* eslint-disable no-new */
90+
const italicCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'italic' );
91+
8792
// The italic autoformatter cannot be triggered by the bold markers, so we need to check the
8893
// text before the pattern (e.g. `(?:^|[^\*])`).
89-
90-
/* eslint-disable no-new */
91-
new InlineAutoformatEditing( this.editor, /(?:^|[^*])(\*)([^*_]+)(\*)$/g, 'italic' );
92-
new InlineAutoformatEditing( this.editor, /(?:^|[^_])(_)([^_]+)(_)$/g, 'italic' );
94+
new InlineAutoformatEditing( this.editor, /(?:^|[^*])(\*)([^*_]+)(\*)$/g, italicCallback );
95+
new InlineAutoformatEditing( this.editor, /(?:^|[^_])(_)([^_]+)(_)$/g, italicCallback );
9396
/* eslint-enable no-new */
9497
}
9598

9699
if ( commands.get( 'code' ) ) {
97100
/* eslint-disable no-new */
98-
new InlineAutoformatEditing( this.editor, /(`)([^`]+)(`)$/g, 'code' );
101+
const codeCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'code' );
102+
103+
new InlineAutoformatEditing( this.editor, /(`)([^`]+)(`)$/g, codeCallback );
99104
/* eslint-enable no-new */
100105
}
101106
}
@@ -144,3 +149,28 @@ export default class Autoformat extends Plugin {
144149
}
145150
}
146151
}
152+
153+
// Helper function for getting `InlineAutoformatEditing` callbacks that checks if command is enabled.
154+
//
155+
// @param {module:core/editor/editor~Editor} editor
156+
// @param {String} attributeKey
157+
// @returns {Function}
158+
function getCallbackFunctionForInlineAutoformat( editor, attributeKey ) {
159+
return ( writer, rangesToFormat ) => {
160+
const command = editor.commands.get( attributeKey );
161+
162+
if ( !command.isEnabled ) {
163+
return false;
164+
}
165+
166+
const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
167+
168+
for ( const range of validRanges ) {
169+
writer.setAttribute( attributeKey, true, range );
170+
}
171+
172+
// After applying attribute to the text, remove given attribute from the selection.
173+
// This way user is able to type a text without attribute used by auto formatter.
174+
writer.removeSelectionAttribute( attributeKey );
175+
};
176+
}

‎src/blockautoformatediting.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default class BlockAutoformatEditing {
2626
*
2727
* Examples of usage:
2828
*
29-
* To convert a paragraph to heading 1 when `- ` is typed, using just the commmand name:
29+
* To convert a paragraph to heading 1 when `- ` is typed, using just the command name:
3030
*
3131
* new BlockAutoformatEditing( editor, /^\- $/, 'heading1' );
3232
*
@@ -49,19 +49,24 @@ export default class BlockAutoformatEditing {
4949
*/
5050
constructor( editor, pattern, callbackOrCommand ) {
5151
let callback;
52+
let command = null;
5253

5354
if ( typeof callbackOrCommand == 'function' ) {
5455
callback = callbackOrCommand;
5556
} else {
5657
// We assume that the actual command name was provided.
57-
const command = callbackOrCommand;
58+
command = editor.commands.get( callbackOrCommand );
5859

5960
callback = () => {
60-
editor.execute( command );
61+
editor.execute( callbackOrCommand );
6162
};
6263
}
6364

6465
editor.model.document.on( 'change', ( evt, batch ) => {
66+
if ( command && !command.isEnabled ) {
67+
return;
68+
}
69+
6570
if ( batch.type == 'transparent' ) {
6671
return;
6772
}

‎src/inlineautoformatediting.js

+20-7
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,23 @@ export default class InlineAutoformatEditing {
5252
* }
5353
*
5454
* @param {Function|String} attributeOrCallback The name of attribute to apply on matching text or a callback for manual
55-
* formatting.
55+
* formatting. If callback is passed it should return `false` if changes should not be applied (e.g. if a command is disabled).
5656
*
5757
* // Use attribute name:
5858
* new InlineAutoformatEditing( editor, /(\*\*)([^\*]+?)(\*\*)$/g, 'bold' );
5959
*
6060
* // Use formatting callback:
61-
* new InlineAutoformatEditing( editor, /(\*\*)([^\*]+?)(\*\*)$/g, ( writer, validRanges ) => {
61+
* new InlineAutoformatEditing( editor, /(\*\*)([^\*]+?)(\*\*)$/g, ( writer, rangesToFormat ) => {
62+
* const command = editor.commands.get( 'bold' );
63+
*
64+
* if ( !command.isEnabled ) {
65+
* return false;
66+
* }
67+
*
68+
* const validRanges = editor.model.schema.getValidRanges( rangesToFormat, 'bold' );
69+
*
6270
* for ( let range of validRanges ) {
63-
* writer.setAttribute( command, true, range );
71+
* writer.setAttribute( 'bold', true, range );
6472
* }
6573
* } );
6674
*/
@@ -128,7 +136,9 @@ export default class InlineAutoformatEditing {
128136
} );
129137

130138
// A format callback run on matched text.
131-
formatCallback = formatCallback || ( ( writer, validRanges ) => {
139+
formatCallback = formatCallback || ( ( writer, rangesToFormat ) => {
140+
const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
141+
132142
for ( const range of validRanges ) {
133143
writer.setAttribute( attributeKey, true, range );
134144
}
@@ -170,10 +180,13 @@ export default class InlineAutoformatEditing {
170180

171181
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes.
172182
editor.model.enqueueChange( writer => {
173-
const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
174-
175183
// Apply format.
176-
formatCallback( writer, validRanges );
184+
const hasChanged = formatCallback( writer, rangesToFormat );
185+
186+
// Strict check on `false` to have backward compatibility (when callbacks were returning `undefined`).
187+
if ( hasChanged === false ) {
188+
return;
189+
}
177190

178191
// Remove delimiters - use reversed order to not mix the offsets while removing.
179192
for ( const range of rangesToRemove.reverse() ) {

‎tests/autoformat.js

+16
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,22 @@ describe( 'Autoformat', () => {
272272

273273
expect( getData( model ) ).to.equal( '<paragraph>foo <$text bold="true">bar</$text>[] baz</paragraph>' );
274274
} );
275+
276+
it( 'should not format if the command is not enabled', () => {
277+
model.schema.addAttributeCheck( ( context, attributeName ) => {
278+
if ( attributeName == 'bold' ) {
279+
return false;
280+
}
281+
} );
282+
283+
setData( model, '<paragraph>**foobar*[]</paragraph>' );
284+
285+
model.change( writer => {
286+
writer.insertText( '*', doc.selection.getFirstPosition() );
287+
} );
288+
289+
expect( getData( model ) ).to.equal( '<paragraph>**foobar**[]</paragraph>' );
290+
} );
275291
} );
276292

277293
describe( 'without commands', () => {

‎tests/blockautoformatediting.js

+35-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* For licensing, see LICENSE.md.
44
*/
55

6+
import Autoformat from '../src/autoformat';
67
import BlockAutoformatEditing from '../src/blockautoformatediting';
78
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
89
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
@@ -19,7 +20,7 @@ describe( 'BlockAutoformatEditing', () => {
1920
beforeEach( () => {
2021
return VirtualTestEditor
2122
.create( {
22-
plugins: [ Enter, Paragraph ]
23+
plugins: [ Enter, Paragraph, Autoformat ]
2324
} )
2425
.then( newEditor => {
2526
editor = newEditor;
@@ -28,13 +29,17 @@ describe( 'BlockAutoformatEditing', () => {
2829
} );
2930
} );
3031

31-
describe( 'Command name', () => {
32+
describe( 'command name', () => {
3233
it( 'should run a command when the pattern is matched', () => {
3334
const spy = testUtils.sinon.spy();
34-
editor.commands.add( 'testCommand', new TestCommand( editor, spy ) );
35+
const testCommand = new TestCommand( editor, spy );
36+
37+
editor.commands.add( 'testCommand', testCommand );
38+
3539
new BlockAutoformatEditing( editor, /^[*]\s$/, 'testCommand' ); // eslint-disable-line no-new
3640

3741
setData( model, '<paragraph>*[]</paragraph>' );
42+
3843
model.change( writer => {
3944
writer.insertText( ' ', doc.selection.getFirstPosition() );
4045
} );
@@ -44,20 +49,45 @@ describe( 'BlockAutoformatEditing', () => {
4449

4550
it( 'should remove found pattern', () => {
4651
const spy = testUtils.sinon.spy();
47-
editor.commands.add( 'testCommand', new TestCommand( editor, spy ) );
52+
const testCommand = new TestCommand( editor, spy );
53+
54+
editor.commands.add( 'testCommand', testCommand );
55+
4856
new BlockAutoformatEditing( editor, /^[*]\s$/, 'testCommand' ); // eslint-disable-line no-new
4957

5058
setData( model, '<paragraph>*[]</paragraph>' );
59+
5160
model.change( writer => {
5261
writer.insertText( ' ', doc.selection.getFirstPosition() );
5362
} );
5463

5564
sinon.assert.calledOnce( spy );
5665
expect( getData( model ) ).to.equal( '<paragraph>[]</paragraph>' );
5766
} );
67+
68+
it( 'should not autoformat if command is disabled', () => {
69+
const spy = testUtils.sinon.spy();
70+
const testCommand = new TestCommand( editor, spy );
71+
72+
testCommand.refresh = function() {
73+
this.isEnabled = false;
74+
};
75+
76+
editor.commands.add( 'testCommand', testCommand );
77+
78+
new BlockAutoformatEditing( editor, /^[*]\s$/, 'testCommand' ); // eslint-disable-line no-new
79+
80+
setData( model, '<paragraph>*[]</paragraph>' );
81+
82+
model.change( writer => {
83+
writer.insertText( ' ', doc.selection.getFirstPosition() );
84+
} );
85+
86+
sinon.assert.notCalled( spy );
87+
} );
5888
} );
5989

60-
describe( 'Callback', () => {
90+
describe( 'callback', () => {
6191
it( 'should run callback when the pattern is matched', () => {
6292
const spy = testUtils.sinon.spy();
6393
new BlockAutoformatEditing( editor, /^[*]\s$/, spy ); // eslint-disable-line no-new

‎tests/inlineautoformatediting.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* For licensing, see LICENSE.md.
44
*/
55

6+
import Autoformat from '../src/autoformat';
67
import InlineAutoformatEditing from '../src/inlineautoformatediting';
78
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
89
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
@@ -18,7 +19,7 @@ describe( 'InlineAutoformatEditing', () => {
1819
beforeEach( () => {
1920
return VirtualTestEditor
2021
.create( {
21-
plugins: [ Enter, Paragraph ]
22+
plugins: [ Enter, Paragraph, Autoformat ]
2223
} )
2324
.then( newEditor => {
2425
editor = newEditor;
@@ -64,7 +65,7 @@ describe( 'InlineAutoformatEditing', () => {
6465
} );
6566
} );
6667

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

116117
sinon.assert.notCalled( formatSpy );
117118
} );
119+
120+
it( 'should not autoformat if callback returned false', () => {
121+
setData( model, '<paragraph>Foobar[]</paragraph>' );
122+
123+
const p = model.document.getRoot().getChild( 0 );
124+
125+
const testCallback = () => ( {
126+
format: [ model.createRange( model.createPositionAt( p, 0 ), model.createPositionAt( p, 3 ) ) ],
127+
remove: [ model.createRange( model.createPositionAt( p, 0 ), model.createPositionAt( p, 3 ) ) ]
128+
} );
129+
130+
const formatCallback = () => false;
131+
132+
new InlineAutoformatEditing( editor, testCallback, formatCallback ); // eslint-disable-line no-new
133+
134+
model.change( writer => {
135+
writer.insertText( ' ', doc.selection.getFirstPosition() );
136+
} );
137+
138+
expect( getData( model ) ).to.equal( '<paragraph>Foobar []</paragraph>' );
139+
} );
118140
} );
119141

120142
it( 'should ignore transparent batches', () => {

‎tests/undointegration.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtest
2020
import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
2121
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
2222

23-
describe( 'Autoformat', () => {
23+
describe( 'Autoformat undo integration', () => {
2424
let editor, model, doc;
2525

2626
testUtils.createSinonSandbox();

0 commit comments

Comments
 (0)
This repository has been archived.