diff --git a/packages/ckeditor5-code-block/src/codeblockcommand.js b/packages/ckeditor5-code-block/src/codeblockcommand.js
index 19dd0da9297..8f77867e723 100644
--- a/packages/ckeditor5-code-block/src/codeblockcommand.js
+++ b/packages/ckeditor5-code-block/src/codeblockcommand.js
@@ -10,7 +10,7 @@
import { Command } from 'ckeditor5/src/core';
import { first } from 'ckeditor5/src/utils';
-import { getNormalizedAndLocalizedLanguageDefinitions } from './utils';
+import { getNormalizedAndLocalizedLanguageDefinitions, canBeCodeBlock } from './utils';
/**
* The code block command plugin.
@@ -179,14 +179,6 @@ export default class CodeBlockCommand extends Command {
}
}
-function canBeCodeBlock( schema, element ) {
- if ( element.is( 'rootElement' ) || schema.isLimit( element ) ) {
- return false;
- }
-
- return schema.checkChild( element.parent, 'codeBlock' );
-}
-
// Picks the language for the new code block. If any language is passed as an option,
// it will be returned. Else, if option usePreviousLanguageChoice is true and some
// code block was already created (lastLanguage is not null) then previously used
diff --git a/packages/ckeditor5-code-block/src/indentcodeblockcommand.js b/packages/ckeditor5-code-block/src/indentcodeblockcommand.js
index 1d8afb234c7..b264d8f4a81 100644
--- a/packages/ckeditor5-code-block/src/indentcodeblockcommand.js
+++ b/packages/ckeditor5-code-block/src/indentcodeblockcommand.js
@@ -76,7 +76,13 @@ export default class IndentCodeBlockCommand extends Command {
//
//
for ( const position of positions ) {
- writer.insertText( this._indentSequence, position );
+ const indentSequenceTextElement = writer.createText( this._indentSequence );
+
+ // Previously insertion was done by writer.insertText(). It was changed to insertContent() to enable
+ // integration of code block with track changes. It's the easiest way of integration because insertContent()
+ // is already integrated with track changes, but if it ever cause any troubles it can be reverted, however
+ // some additional work will be required in track changes integration of code block.
+ model.insertContent( indentSequenceTextElement, position );
}
} );
}
diff --git a/packages/ckeditor5-code-block/src/outdentcodeblockcommand.js b/packages/ckeditor5-code-block/src/outdentcodeblockcommand.js
index 3d1211c7a17..83401f4ebb3 100644
--- a/packages/ckeditor5-code-block/src/outdentcodeblockcommand.js
+++ b/packages/ckeditor5-code-block/src/outdentcodeblockcommand.js
@@ -51,7 +51,7 @@ export default class OutdentCodeBlockCommand extends Command {
const editor = this.editor;
const model = editor.model;
- model.change( writer => {
+ model.change( () => {
const positions = getIndentOutdentPositions( model );
// Outdent all positions, for instance assuming the indent sequence is 4x space (" "):
@@ -76,10 +76,14 @@ export default class OutdentCodeBlockCommand extends Command {
// bazqux
//
for ( const position of positions ) {
- const range = getLastOutdentableSequenceRange( this.editor.model, position, this._indentSequence );
+ const range = getLastOutdentableSequenceRange( model, position, this._indentSequence );
if ( range ) {
- writer.remove( range );
+ // Previously deletion was done by writer.remove(). It was changed to deleteContent() to enable
+ // integration of code block with track changes. It's the easiest way of integration because deleteContent()
+ // is already integrated with track changes, but if it ever cause any troubles it can be reverted, however
+ // some additional work will be required in track changes integration of code block.
+ model.deleteContent( model.createSelection( range ) );
}
}
} );
diff --git a/packages/ckeditor5-code-block/src/utils.js b/packages/ckeditor5-code-block/src/utils.js
index 3b0c794573a..586cd4f17b8 100644
--- a/packages/ckeditor5-code-block/src/utils.js
+++ b/packages/ckeditor5-code-block/src/utils.js
@@ -219,3 +219,18 @@ export function isModelSelectionInCodeBlock( selection ) {
return firstBlock && firstBlock.is( 'element', 'codeBlock' );
}
+
+/**
+ * Checks if an {@link module:engine/model/element~Element Element} can become a code block.
+ *
+ * @param {module:engine/model/schema~Schema} schema Model's schema.
+ * @param {module:engine/model/element~Element} element The element to be checked.
+ * @returns {Boolean} Check result.
+ */
+export function canBeCodeBlock( schema, element ) {
+ if ( element.is( 'rootElement' ) || schema.isLimit( element ) ) {
+ return false;
+ }
+
+ return schema.checkChild( element.parent, 'codeBlock' );
+}
diff --git a/packages/ckeditor5-code-block/tests/indentcodeblockcommand.js b/packages/ckeditor5-code-block/tests/indentcodeblockcommand.js
index 84ca726ea2d..6f8cbbbea92 100644
--- a/packages/ckeditor5-code-block/tests/indentcodeblockcommand.js
+++ b/packages/ckeditor5-code-block/tests/indentcodeblockcommand.js
@@ -13,10 +13,13 @@ import BlockQuoteEditing from '@ckeditor/ckeditor5-block-quote/src/blockquoteedi
import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
describe( 'IndentCodeBlockCommand', () => {
let editor, model, indentCommand;
+ testUtils.createSinonSandbox();
+
beforeEach( () => {
return ModelTestEditor
.create( {
@@ -143,6 +146,20 @@ describe( 'IndentCodeBlockCommand', () => {
' f[oo b]ar' );
} );
+ // Need to ensure that insertContent() will not be reverted to model.change() to not break integration
+ // with Track Changes.
+ it( 'should insert indent with insertContent()', () => {
+ const insertContentSpy = sinon.spy( model, 'insertContent' );
+ const modelChangeSpy = sinon.spy( model, 'change' );
+
+ setModelData( model, '[]Foo' );
+
+ indentCommand.execute();
+
+ expect( insertContentSpy.calledOnce ).to.be.true;
+ expect( modelChangeSpy.calledAfter( insertContentSpy ) ).to.be.true;
+ } );
+
describe( 'config.codeBlock.indentSequence', () => {
it( 'should be used when indenting', () => {
return ModelTestEditor
diff --git a/packages/ckeditor5-code-block/tests/outdentcodeblockcommand.js b/packages/ckeditor5-code-block/tests/outdentcodeblockcommand.js
index 099dabe003f..84528bfeba1 100644
--- a/packages/ckeditor5-code-block/tests/outdentcodeblockcommand.js
+++ b/packages/ckeditor5-code-block/tests/outdentcodeblockcommand.js
@@ -13,10 +13,13 @@ import BlockQuoteEditing from '@ckeditor/ckeditor5-block-quote/src/blockquoteedi
import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
describe( 'OutdentCodeBlockCommand', () => {
let editor, model, outdentCommand;
+ testUtils.createSinonSandbox();
+
beforeEach( () => {
return ModelTestEditor
.create( {
@@ -278,6 +281,24 @@ describe( 'OutdentCodeBlockCommand', () => {
expect( getModelData( model ) ).to.equal( 'f[ooba]r' );
} );
+ // Need to ensure that deleteContent() will not be reverted to model.change() to not break integration
+ // with Track Changes.
+ it( 'should outdent with deleteContent()', () => {
+ const deleteContentSpy = sinon.spy( model, 'deleteContent' );
+ const modelChangeSpy = sinon.spy( model, 'change' );
+
+ setModelData( model, '[]Foo' );
+
+ model.change( writer => {
+ writer.insertText( ' ', model.document.getRoot().getChild( 0 ), 0 );
+ } );
+
+ outdentCommand.execute();
+
+ expect( deleteContentSpy.calledOnce ).to.be.true;
+ expect( modelChangeSpy.calledAfter( deleteContentSpy ) ).to.be.true;
+ } );
+
describe( 'config.codeBlock.indentSequence', () => {
it( 'should be respected', () => {
return ModelTestEditor
diff --git a/packages/ckeditor5-code-block/tests/utils.js b/packages/ckeditor5-code-block/tests/utils.js
new file mode 100644
index 00000000000..4ea9e22636b
--- /dev/null
+++ b/packages/ckeditor5-code-block/tests/utils.js
@@ -0,0 +1,90 @@
+/**
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import Model from '@ckeditor/ckeditor5-engine/src/model/model';
+
+import { canBeCodeBlock } from '../src/utils';
+
+describe( 'CodeBlock - utils', () => {
+ describe( 'canBecomeCodeBlock()', () => {
+ it( 'should not allow a root element to become a code block', () => {
+ const model = new Model();
+ const root = model.document.createRoot();
+
+ const testResult = canBeCodeBlock( model.schema, root );
+
+ expect( root.is( 'rootElement' ) ).to.be.true;
+ expect( testResult ).to.be.false;
+ } );
+
+ it( 'should not allow a limit element to become a code block', () => {
+ const model = new Model();
+ const root = model.document.createRoot();
+
+ model.schema.register( 'limitElement', { isObject: true } );
+
+ model.change( writer => {
+ const limitElement = writer.createElement( 'limitElement' );
+
+ writer.insert( limitElement, root, 0 );
+ } );
+
+ const limitElement = root.getChild( 0 );
+
+ const testResult = canBeCodeBlock( model.schema, limitElement );
+
+ expect( model.schema.isLimit( limitElement ) ).to.be.true;
+ expect( testResult ).to.be.false;
+ } );
+
+ it( 'should not allow an element to become a code block if it isnt allowed in parent', () => {
+ const model = new Model();
+ const root = model.document.createRoot();
+
+ model.schema.register( 'container', { allowIn: '$root' } );
+ model.schema.register( 'fooElement', { allowIn: 'container' } );
+ model.schema.register( 'codeBlock', { } );
+
+ let fooElement;
+ let containerElement;
+
+ model.change( writer => {
+ containerElement = writer.createElement( 'container' );
+ fooElement = writer.createElement( 'codeBlock' );
+
+ writer.insert( containerElement, root, 0 );
+ writer.insert( fooElement, containerElement, 0 );
+ } );
+
+ const testResult = canBeCodeBlock( model.schema, fooElement );
+
+ expect( testResult ).to.be.false;
+ } );
+
+ it( 'should allow an element to become a code block if it is allowed in parent', () => {
+ const model = new Model();
+ const root = model.document.createRoot();
+
+ model.schema.register( 'container', { allowIn: '$root' } );
+ model.schema.register( 'fooElement', { allowIn: 'container' } );
+ model.schema.register( 'codeBlock', { allowIn: 'container' } );
+
+ let fooElement;
+ let containerElement;
+
+ model.change( writer => {
+ containerElement = writer.createElement( 'container' );
+ fooElement = writer.createElement( 'codeBlock' );
+
+ writer.insert( containerElement, root, 0 );
+ writer.insert( fooElement, containerElement, 0 );
+ } );
+
+ const testResult = canBeCodeBlock( model.schema, fooElement );
+
+ expect( testResult ).to.be.true;
+ } );
+ } );
+} );