diff --git a/docs/framework/guides/deep-dive/schema.md b/docs/framework/guides/deep-dive/schema.md
index f3e06c6e0..8222e2391 100644
--- a/docs/framework/guides/deep-dive/schema.md
+++ b/docs/framework/guides/deep-dive/schema.md
@@ -57,7 +57,8 @@ schema.register( '$block', {
isBlock: true
} );
schema.register( '$text', {
- allowIn: '$block'
+ allowIn: '$block',
+ isInline: true
} );
```
diff --git a/src/model/model.js b/src/model/model.js
index 3c970ab29..500de84ae 100644
--- a/src/model/model.js
+++ b/src/model/model.js
@@ -94,7 +94,8 @@ export default class Model {
isBlock: true
} );
this.schema.register( '$text', {
- allowIn: '$block'
+ allowIn: '$block',
+ isInline: true
} );
this.schema.register( '$clipboardHolder', {
allowContentOf: '$root',
diff --git a/src/model/schema.js b/src/model/schema.js
index a4a4f8b40..3745c143d 100644
--- a/src/model/schema.js
+++ b/src/model/schema.js
@@ -230,7 +230,7 @@ export default class Schema {
/**
* Returns `true` if the given item is defined to be
- * a object element by {@link module:engine/model/schema~SchemaItemDefinition}'s `isObject` property.
+ * an object element by {@link module:engine/model/schema~SchemaItemDefinition}'s `isObject` property.
*
* schema.isObject( 'paragraph' ); // -> false
* schema.isObject( 'image' ); // -> true
@@ -246,6 +246,24 @@ export default class Schema {
return !!( def && def.isObject );
}
+ /**
+ * Returns `true` if the given item is defined to be
+ * a object element by {@link module:engine/model/schema~SchemaItemDefinition}'s `isInline` property.
+ *
+ * schema.isInline( 'paragraph' ); // -> false
+ * schema.isInline( 'softBreak' ); // -> true
+ *
+ * const text = writer.createText('foo' );
+ * schema.isObject( text ); // -> true
+ *
+ * @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
+ */
+ isInline( item ) {
+ const def = this.getDefinition( item );
+
+ return !!( def && def.isInline );
+ }
+
/**
* Checks whether the given node (`child`) can be a child of the given context.
*
@@ -899,7 +917,7 @@ mix( Schema, ObservableMixin );
* * `allowAttributesOf` – A string or an array of strings. Inherits attributes from other items.
* * `inheritTypesFrom` – A string or an array of strings. Inherits `is*` properties of other items.
* * `inheritAllFrom` – A string. A shorthand for `allowContentOf`, `allowWhere`, `allowAttributesOf`, `inheritTypesFrom`.
- * * Additionally, you can define the following `is*` properties: `isBlock`, `isLimit`, `isObject`. Read about them below.
+ * * Additionally, you can define the following `is*` properties: `isBlock`, `isLimit`, `isObject`, `isInline`. Read about them below.
*
* # The is* properties
*
@@ -915,6 +933,8 @@ mix( Schema, ObservableMixin );
* a limit element are limited to its content. **Note:** All objects (`isObject`) are treated as limit elements, too.
* * `isObject` – Whether an item is "self-contained" and should be treated as a whole. Examples of object elements:
* `image`, `table`, `video`, etc. **Note:** An object is also a limit, so
+ * * `isInline` – Whether an item is "text-like" and should be treated as an inline text. Examples of inline elements:
+ * `text`, `softBreak` (`
`), etc.
* {@link module:engine/model/schema~Schema#isLimit `isLimit()`}
* returns `true` for object elements automatically.
*
@@ -931,7 +951,8 @@ mix( Schema, ObservableMixin );
* isBlock: true
* } );
* this.schema.register( '$text', {
- * allowIn: '$block'
+ * allowIn: '$block',
+ * isInline: true
* } );
*
* They reflect typical editor content that is contained within one root, consists of several blocks
diff --git a/tests/model/schema.js b/tests/model/schema.js
index 2b7b53eb3..63febea29 100644
--- a/tests/model/schema.js
+++ b/tests/model/schema.js
@@ -376,6 +376,41 @@ describe( 'Schema', () => {
} );
} );
+ describe( 'isInline()', () => {
+ it( 'returns true if an item was registered as inline', () => {
+ schema.register( 'foo', {
+ isInline: true
+ } );
+
+ expect( schema.isInline( 'foo' ) ).to.be.true;
+ } );
+
+ it( 'returns false if an item was registered as a limit (because not all limits are objects)', () => {
+ schema.register( 'foo', {
+ isLimit: true
+ } );
+
+ expect( schema.isInline( 'foo' ) ).to.be.false;
+ } );
+
+ it( 'returns false if an item was not registered as an object', () => {
+ schema.register( 'foo' );
+
+ expect( schema.isInline( 'foo' ) ).to.be.false;
+ } );
+
+ it( 'returns false if an item was not registered at all', () => {
+ expect( schema.isInline( 'foo' ) ).to.be.false;
+ } );
+
+ it( 'uses getDefinition()\'s item to definition normalization', () => {
+ const stub = sinon.stub( schema, 'getDefinition' ).returns( { isInline: true } );
+
+ expect( schema.isInline( 'foo' ) ).to.be.true;
+ expect( stub.calledOnce ).to.be.true;
+ } );
+ } );
+
describe( 'checkChild()', () => {
beforeEach( () => {
schema.register( '$root' );
@@ -2468,7 +2503,8 @@ describe( 'Schema', () => {
},
() => {
schema.extend( '$text', {
- allowAttributes: [ 'bold', 'italic' ]
+ allowAttributes: [ 'bold', 'italic' ],
+ isInline: true
} );
// Disallow bold in heading1.
@@ -2494,7 +2530,8 @@ describe( 'Schema', () => {
isBlock: true
} );
schema.register( '$text', {
- allowIn: '$block'
+ allowIn: '$block',
+ isInline: true
} );
for ( const definition of definitions ) {
@@ -2738,40 +2775,53 @@ describe( 'Schema', () => {
expect( schema.checkAttribute( r1i, 'alignment' ) ).to.be.false;
} );
+ it( '$text is inline', () => {
+ expect( schema.isLimit( '$text' ) ).to.be.false;
+ expect( schema.isBlock( '$text' ) ).to.be.false;
+ expect( schema.isObject( '$text' ) ).to.be.false;
+ expect( schema.isInline( '$text' ) ).to.be.true;
+ } );
+
it( '$root is limit', () => {
expect( schema.isLimit( '$root' ) ).to.be.true;
expect( schema.isBlock( '$root' ) ).to.be.false;
expect( schema.isObject( '$root' ) ).to.be.false;
+ expect( schema.isInline( '$root' ) ).to.be.false;
} );
it( 'paragraph is block', () => {
expect( schema.isLimit( 'paragraph' ) ).to.be.false;
expect( schema.isBlock( 'paragraph' ) ).to.be.true;
expect( schema.isObject( 'paragraph' ) ).to.be.false;
+ expect( schema.isInline( '$root' ) ).to.be.false;
} );
it( 'heading1 is block', () => {
expect( schema.isLimit( 'heading1' ) ).to.be.false;
expect( schema.isBlock( 'heading1' ) ).to.be.true;
expect( schema.isObject( 'heading1' ) ).to.be.false;
+ expect( schema.isInline( '$root' ) ).to.be.false;
} );
it( 'listItem is block', () => {
expect( schema.isLimit( 'listItem' ) ).to.be.false;
expect( schema.isBlock( 'listItem' ) ).to.be.true;
expect( schema.isObject( 'listItem' ) ).to.be.false;
+ expect( schema.isInline( '$root' ) ).to.be.false;
} );
it( 'image is block object', () => {
expect( schema.isLimit( 'image' ) ).to.be.true;
expect( schema.isBlock( 'image' ) ).to.be.true;
expect( schema.isObject( 'image' ) ).to.be.true;
+ expect( schema.isInline( '$root' ) ).to.be.false;
} );
it( 'caption is limit', () => {
expect( schema.isLimit( 'caption' ) ).to.be.true;
expect( schema.isBlock( 'caption' ) ).to.be.false;
expect( schema.isObject( 'caption' ) ).to.be.false;
+ expect( schema.isInline( '$root' ) ).to.be.false;
} );
} );
diff --git a/tests/model/utils/selection-post-fixer.js b/tests/model/utils/selection-post-fixer.js
index 328978ade..9ce86a5a2 100644
--- a/tests/model/utils/selection-post-fixer.js
+++ b/tests/model/utils/selection-post-fixer.js
@@ -945,6 +945,47 @@ describe( 'Selection post-fixer', () => {
} );
} );
+ describe( 'non-collapsed selection - inline widget scenarios', () => {
+ beforeEach( () => {
+ model.schema.register( 'placeholder', {
+ allowWhere: '$text',
+ isInline: true
+ } );
+ } );
+
+ it( 'should fix selection that ends in inline element', () => {
+ setModelData( model, 'aaa[]bbb' );
+
+ expect( getModelData( model ) ).to.equal( 'aaa[]bbb' );
+ } );
+
+ it( 'should fix selection that starts in inline element', () => {
+ setModelData( model, 'aaa[]bbb' );
+
+ expect( getModelData( model ) ).to.equal( 'aaa[]bbb' );
+ } );
+
+ it( 'should fix selection that ends in inline element that is also an object', () => {
+ model.schema.extend( 'placeholder', {
+ isObject: true
+ } );
+
+ setModelData( model, 'aaa[]bbb' );
+
+ expect( getModelData( model ) ).to.equal( 'aaa[]bbb' );
+ } );
+
+ it( 'should fix selection that starts in inline element that is also an object', () => {
+ model.schema.extend( 'placeholder', {
+ isObject: true
+ } );
+
+ setModelData( model, 'aaa[]bbb' );
+
+ expect( getModelData( model ) ).to.equal( 'aaa[]bbb' );
+ } );
+ } );
+
describe( 'collapsed selection', () => {
beforeEach( () => {
setModelData( model,