Browse Source

Merge branch 'master' into i/7579

panr 5 years ago
parent
commit
22c2c644c8
100 changed files with 1486 additions and 315 deletions
  1. 4 2
      docs/framework/guides/architecture/editing-engine.md
  2. 3 3
      docs/framework/guides/architecture/ui-library.md
  3. 1 1
      docs/framework/guides/deep-dive/localization.md
  4. 1 1
      docs/framework/guides/development-tools.md
  5. 1 1
      packages/ckeditor5-autoformat/src/blockautoformatediting.js
  6. 1 1
      packages/ckeditor5-autoformat/src/inlineautoformatediting.js
  7. 1 1
      packages/ckeditor5-autosave/src/autosave.js
  8. 7 4
      packages/ckeditor5-block-quote/src/blockquoteediting.js
  9. 2 2
      packages/ckeditor5-clipboard/src/utils/viewtoplaintext.js
  10. 3 3
      packages/ckeditor5-code-block/src/codeblockcommand.js
  11. 8 8
      packages/ckeditor5-code-block/src/codeblockediting.js
  12. 1 1
      packages/ckeditor5-code-block/src/converters.js
  13. 2 2
      packages/ckeditor5-code-block/src/outdentcodeblockcommand.js
  14. 2 2
      packages/ckeditor5-code-block/src/utils.js
  15. 2 0
      packages/ckeditor5-core/src/editor/editor.js
  16. 1 1
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-figure-attributes.js
  17. 1 1
      packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-preserving-custom-content.md
  18. 2 1
      packages/ckeditor5-engine/src/controller/datacontroller.js
  19. 2 1
      packages/ckeditor5-engine/src/controller/editingcontroller.js
  20. 3 3
      packages/ckeditor5-engine/src/conversion/conversion.js
  21. 6 0
      packages/ckeditor5-engine/src/conversion/downcastdispatcher.js
  22. 284 7
      packages/ckeditor5-engine/src/conversion/downcasthelpers.js
  23. 4 4
      packages/ckeditor5-engine/src/conversion/mapper.js
  24. 1 1
      packages/ckeditor5-engine/src/conversion/upcastdispatcher.js
  25. 194 3
      packages/ckeditor5-engine/src/conversion/upcasthelpers.js
  26. 5 5
      packages/ckeditor5-engine/src/conversion/viewconsumable.js
  27. 1 1
      packages/ckeditor5-engine/src/dev-utils/model.js
  28. 2 2
      packages/ckeditor5-engine/src/dev-utils/view.js
  29. 1 1
      packages/ckeditor5-engine/src/model/differ.js
  30. 1 1
      packages/ckeditor5-engine/src/model/documentfragment.js
  31. 4 6
      packages/ckeditor5-engine/src/model/element.js
  32. 10 0
      packages/ckeditor5-engine/src/model/markercollection.js
  33. 1 1
      packages/ckeditor5-engine/src/model/model.js
  34. 2 2
      packages/ckeditor5-engine/src/model/node.js
  35. 2 2
      packages/ckeditor5-engine/src/model/operation/utils.js
  36. 2 5
      packages/ckeditor5-engine/src/model/position.js
  37. 1 5
      packages/ckeditor5-engine/src/model/rootelement.js
  38. 2 2
      packages/ckeditor5-engine/src/model/schema.js
  39. 10 6
      packages/ckeditor5-engine/src/model/text.js
  40. 10 5
      packages/ckeditor5-engine/src/model/textproxy.js
  41. 1 1
      packages/ckeditor5-engine/src/model/treewalker.js
  42. 1 1
      packages/ckeditor5-engine/src/model/utils/getselectedcontent.js
  43. 1 1
      packages/ckeditor5-engine/src/model/utils/modifyselection.js
  44. 3 5
      packages/ckeditor5-engine/src/view/attributeelement.js
  45. 3 5
      packages/ckeditor5-engine/src/view/containerelement.js
  46. 1 1
      packages/ckeditor5-engine/src/view/documentfragment.js
  47. 5 5
      packages/ckeditor5-engine/src/view/domconverter.js
  48. 10 10
      packages/ckeditor5-engine/src/view/downcastwriter.js
  49. 3 5
      packages/ckeditor5-engine/src/view/editableelement.js
  50. 5 7
      packages/ckeditor5-engine/src/view/element.js
  51. 3 5
      packages/ckeditor5-engine/src/view/emptyelement.js
  52. 2 0
      packages/ckeditor5-engine/src/view/filler.js
  53. 2 3
      packages/ckeditor5-engine/src/view/node.js
  54. 1 1
      packages/ckeditor5-engine/src/view/observer/mutationobserver.js
  55. 6 6
      packages/ckeditor5-engine/src/view/position.js
  56. 7 7
      packages/ckeditor5-engine/src/view/range.js
  57. 2 2
      packages/ckeditor5-engine/src/view/renderer.js
  58. 3 5
      packages/ckeditor5-engine/src/view/rooteditableelement.js
  59. 15 13
      packages/ckeditor5-engine/src/view/text.js
  60. 10 5
      packages/ckeditor5-engine/src/view/textproxy.js
  61. 3 5
      packages/ckeditor5-engine/src/view/uielement.js
  62. 2 2
      packages/ckeditor5-engine/tests/controller/datacontroller.js
  63. 3 3
      packages/ckeditor5-engine/tests/conversion/conversion.js
  64. 482 1
      packages/ckeditor5-engine/tests/conversion/downcasthelpers.js
  65. 196 11
      packages/ckeditor5-engine/tests/conversion/upcasthelpers.js
  66. 2 2
      packages/ckeditor5-engine/tests/model/documentfragment.js
  67. 2 2
      packages/ckeditor5-engine/tests/model/documentselection.js
  68. 7 6
      packages/ckeditor5-engine/tests/model/element.js
  69. 1 1
      packages/ckeditor5-engine/tests/model/liveposition.js
  70. 1 1
      packages/ckeditor5-engine/tests/model/liverange.js
  71. 7 1
      packages/ckeditor5-engine/tests/model/markercollection.js
  72. 2 2
      packages/ckeditor5-engine/tests/model/node.js
  73. 1 1
      packages/ckeditor5-engine/tests/model/position.js
  74. 1 1
      packages/ckeditor5-engine/tests/model/range.js
  75. 6 4
      packages/ckeditor5-engine/tests/model/rootelement.js
  76. 4 4
      packages/ckeditor5-engine/tests/model/selection.js
  77. 3 1
      packages/ckeditor5-engine/tests/model/text.js
  78. 4 2
      packages/ckeditor5-engine/tests/model/textproxy.js
  79. 5 5
      packages/ckeditor5-engine/tests/view/attributeelement.js
  80. 5 5
      packages/ckeditor5-engine/tests/view/containerelement.js
  81. 2 2
      packages/ckeditor5-engine/tests/view/documentfragment.js
  82. 3 3
      packages/ckeditor5-engine/tests/view/documentselection.js
  83. 2 2
      packages/ckeditor5-engine/tests/view/downcastwriter/writer.js
  84. 4 5
      packages/ckeditor5-engine/tests/view/editableelement.js
  85. 6 6
      packages/ckeditor5-engine/tests/view/element.js
  86. 6 6
      packages/ckeditor5-engine/tests/view/emptyelement.js
  87. 3 3
      packages/ckeditor5-engine/tests/view/node.js
  88. 5 5
      packages/ckeditor5-engine/tests/view/observer/mutationobserver.js
  89. 3 3
      packages/ckeditor5-engine/tests/view/position.js
  90. 3 3
      packages/ckeditor5-engine/tests/view/range.js
  91. 8 6
      packages/ckeditor5-engine/tests/view/rooteditableelement.js
  92. 2 2
      packages/ckeditor5-engine/tests/view/selection.js
  93. 5 3
      packages/ckeditor5-engine/tests/view/text.js
  94. 6 4
      packages/ckeditor5-engine/tests/view/textproxy.js
  95. 5 5
      packages/ckeditor5-engine/tests/view/uielement.js
  96. 1 1
      packages/ckeditor5-font/src/ui/colortableview.js
  97. 1 1
      packages/ckeditor5-heading/src/headingcommand.js
  98. 2 2
      packages/ckeditor5-heading/src/headingediting.js
  99. 6 6
      packages/ckeditor5-heading/src/title.js
  100. 1 1
      packages/ckeditor5-horizontal-line/src/horizontallinecommand.js

+ 4 - 2
docs/framework/guides/architecture/editing-engine.md

@@ -159,9 +159,11 @@ Markers are a special type of ranges.
 * They can only be created and changed through the {@link module:engine/model/writer~Writer model writer}.
 * They can be synchronized over the network with other collaborating clients.
 * They are automatically updated when the document's structure is changed.
-* They can be converted to attributes or elements in the [view](#view).
+* They can be converted to the editing view, to show them in the editor (as {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToHighlight highlights} or {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToElement elements}).
+* They can be {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToData converted to the data view}, to store them with the document data.
+* They can be {@link module:engine/conversion/upcasthelpers~UpcastHelpers#dataToMarker loaded with the document data}.
 
-This makes them ideal for storing and maintaining additional data in the model — such as comments, selections of other users, etc.
+Markers are ideal for storing and maintaining additional data related to portions of the document — such as comments or selections of other users.
 
 ### Schema
 

+ 3 - 3
docs/framework/guides/architecture/ui-library.md

@@ -310,7 +310,7 @@ The dropdown panel exposes its {@link module:ui/dropdown/dropdownpanelview~Dropd
 
 The framework provides a set of helpers to make the dropdown creation process easier, although it is still possible to compose a custom dropdown from scratch using the base classes.
 
-The {@link module:ui/dropdown/utils#createDropdown} helper creates a {@link module:ui/dropdown/dropdownview~DropdownView} with either a {@link module:ui/button/buttonview~ButtonView} or a {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView}.
+The {@link module:ui/dropdown/utils~createDropdown} helper creates a {@link module:ui/dropdown/dropdownview~DropdownView} with either a {@link module:ui/button/buttonview~ButtonView} or a {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView}.
 
 ```js
 import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
@@ -376,7 +376,7 @@ dropdownView.buttonView.set( {
 
 #### Adding a list to a dropdown
 
-The {@link module:ui/list/listview~ListView} can be added to a dropdown using the {@link module:ui/dropdown/utils#addListToDropdown} helper.
+The {@link module:ui/list/listview~ListView} can be added to a dropdown using the {@link module:ui/dropdown/utils~addListToDropdown} helper.
 
 ```js
 import Model from '@ckeditor/ckeditor5-ui/src/model';
@@ -411,7 +411,7 @@ addListToDropdown( dropdownView, items );
 
 #### Adding a toolbar to a dropdown
 
-A {@link module:ui/toolbar/toolbarview~ToolbarView} can be added to a dropdown using  the {@link module:ui/dropdown/utils#addToolbarToDropdown} helper.
+A {@link module:ui/toolbar/toolbarview~ToolbarView} can be added to a dropdown using  the {@link module:ui/dropdown/utils~addToolbarToDropdown} helper.
 
 ```js
 import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';

+ 1 - 1
docs/framework/guides/deep-dive/localization.md

@@ -149,7 +149,7 @@ Adding translations to the editor can be done in three ways to satisfy various n
 
 ### Using the `add()` function
 
-The first option for adding translations is via {@link module:utils/translation-service.add the translation-service's `add()` helper}. This utility adds translations to the global `window.CKEDITOR_TRANSLATIONS` object by extending it. Since it needs to be imported, it works only before building the editor.
+The first option for adding translations is via {@link module:utils/translation-service~add the translation-service's `add()` helper}. This utility adds translations to the global `window.CKEDITOR_TRANSLATIONS` object by extending it. Since it needs to be imported, it works only before building the editor.
 
 Starting with the CKEditor 5 v19.0.0 release, the `add()` method now accepts an optional `getPluralForm()` function as the third argument. This function is only needed for defining the plural form if no language file was loaded for a particular language. It also accepts an array of translations for a *message* if the *message* should support singular and plural forms.
 

+ 1 - 1
docs/framework/guides/development-tools.md

@@ -90,7 +90,7 @@ They allow for "stringifying" the {@link framework/guides/architecture/editing-e
 	Both tools are designed for prototyping, debugging, and testing purposes. Do not use them in production-grade code.
 </info-box>
 
-For instance, to take a peek at the editor model, you could use the {@link module:engine/dev-utils/model#static-function-getData `getData()`} helper:
+For instance, to take a peek at the editor model, you could use the {@link module:engine/dev-utils/model~getData `getData()`} helper:
 
 ```js
 import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';

+ 1 - 1
packages/ckeditor5-autoformat/src/blockautoformatediting.js

@@ -83,7 +83,7 @@ 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 ) {
+		if ( !blockToFormat.is( 'element', 'paragraph' ) || blockToFormat.childCount !== 1 ) {
 			return;
 		}
 

+ 1 - 1
packages/ckeditor5-autoformat/src/inlineautoformatediting.js

@@ -201,7 +201,7 @@ function getTextAfterCode( range, model ) {
 
 	const text = Array.from( range.getItems() ).reduce( ( rangeText, node ) => {
 		// Trim text to a last occurrence of an inline element and update range start.
-		if ( !( node.is( 'text' ) || node.is( 'textProxy' ) ) || node.getAttribute( 'code' ) ) {
+		if ( !( node.is( '$text' ) || node.is( '$textProxy' ) ) || node.getAttribute( 'code' ) ) {
 			start = model.createPositionAfter( node );
 
 			return '';

+ 1 - 1
packages/ckeditor5-autosave/src/autosave.js

@@ -139,7 +139,7 @@ export default class Autosave extends Plugin {
 		 * Editor's pending actions manager.
 		 *
 		 * @private
-		 * @member {@module:core/pendingactions~PendingActions} #_pendingActions
+		 * @member {module:core/pendingactions~PendingActions} #_pendingActions
 		 */
 	}
 

+ 7 - 4
packages/ckeditor5-block-quote/src/blockquoteediting.js

@@ -62,12 +62,12 @@ export default class BlockQuoteEditing extends Plugin {
 						continue;
 					}
 
-					if ( element.is( 'blockQuote' ) && element.isEmpty ) {
+					if ( element.is( 'element', 'blockQuote' ) && element.isEmpty ) {
 						// Added an empty blockQuote - remove it.
 						writer.remove( element );
 
 						return true;
-					} else if ( element.is( 'blockQuote' ) && !schema.checkChild( entry.position, element ) ) {
+					} else if ( element.is( 'element', 'blockQuote' ) && !schema.checkChild( entry.position, element ) ) {
 						// Added a blockQuote in incorrect place - most likely inside another blockQuote. Unwrap it
 						// so the content inside is not lost.
 						writer.unwrap( element );
@@ -78,7 +78,10 @@ export default class BlockQuoteEditing extends Plugin {
 						const range = writer.createRangeIn( element );
 
 						for ( const child of range.getItems() ) {
-							if ( child.is( 'blockQuote' ) && !schema.checkChild( writer.createPositionBefore( child ), child ) ) {
+							if (
+								child.is( 'element', 'blockQuote' ) &&
+								!schema.checkChild( writer.createPositionBefore( child ), child )
+							) {
 								writer.unwrap( child );
 
 								return true;
@@ -88,7 +91,7 @@ export default class BlockQuoteEditing extends Plugin {
 				} else if ( entry.type == 'remove' ) {
 					const parent = entry.position.parent;
 
-					if ( parent.is( 'blockQuote' ) && parent.isEmpty ) {
+					if ( parent.is( 'element', 'blockQuote' ) && parent.isEmpty ) {
 						// Something got removed and now blockQuote is empty. Remove the blockQuote as well.
 						writer.remove( parent );
 

+ 2 - 2
packages/ckeditor5-clipboard/src/utils/viewtoplaintext.js

@@ -21,10 +21,10 @@ const smallPaddingElements = [ 'figcaption', 'li' ];
 export default function viewToPlainText( viewItem ) {
 	let text = '';
 
-	if ( viewItem.is( 'text' ) || viewItem.is( 'textProxy' ) ) {
+	if ( viewItem.is( '$text' ) || viewItem.is( '$textProxy' ) ) {
 		// If item is `Text` or `TextProxy` simple take its text data.
 		text = viewItem.data;
-	} else if ( viewItem.is( 'img' ) && viewItem.hasAttribute( 'alt' ) ) {
+	} else if ( viewItem.is( 'element', 'img' ) && viewItem.hasAttribute( 'alt' ) ) {
 		// Special case for images - use alt attribute if it is provided.
 		text = viewItem.getAttribute( 'alt' );
 	} else {

+ 3 - 3
packages/ckeditor5-code-block/src/codeblockcommand.js

@@ -72,7 +72,7 @@ export default class CodeBlockCommand extends Command {
 	_getValue() {
 		const selection = this.editor.model.document.selection;
 		const firstBlock = first( selection.getSelectedBlocks() );
-		const isCodeBlock = !!( firstBlock && firstBlock.is( 'codeBlock' ) );
+		const isCodeBlock = !!( firstBlock && firstBlock.is( 'element', 'codeBlock' ) );
 
 		return isCodeBlock ? firstBlock.getAttribute( 'language' ) : false;
 	}
@@ -132,13 +132,13 @@ export default class CodeBlockCommand extends Command {
 	 * @param {Array.<module:engine/model/element~Element>} blocks
 	 */
 	_removeCodeBlock( writer, blocks ) {
-		const codeBlocks = blocks.filter( block => block.is( 'codeBlock' ) );
+		const codeBlocks = blocks.filter( block => block.is( 'element', 'codeBlock' ) );
 
 		for ( const block of codeBlocks ) {
 			const range = writer.createRangeOn( block );
 
 			for ( const item of Array.from( range.getItems() ).reverse() ) {
-				if ( item.is( 'softBreak' ) && item.parent.is( 'codeBlock' ) ) {
+				if ( item.is( 'element', 'softBreak' ) && item.parent.is( 'element', 'codeBlock' ) ) {
 					const { position } = writer.split( writer.createPositionBefore( item ) );
 
 					writer.rename( position.nodeAfter, 'paragraph' );

+ 8 - 8
packages/ckeditor5-code-block/src/codeblockediting.js

@@ -136,7 +136,7 @@ export default class CodeBlockEditing extends Plugin {
 		this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => {
 			const modelSelection = model.document.selection;
 
-			if ( !modelSelection.anchor.parent.is( 'codeBlock' ) ) {
+			if ( !modelSelection.anchor.parent.is( 'element', 'codeBlock' ) ) {
 				return;
 			}
 
@@ -156,7 +156,7 @@ export default class CodeBlockEditing extends Plugin {
 		this.listenTo( model, 'getSelectedContent', ( evt, [ selection ] ) => {
 			const anchor = selection.anchor;
 
-			if ( selection.isCollapsed || !anchor.parent.is( 'codeBlock' ) || !anchor.hasSameParentAs( selection.focus ) ) {
+			if ( selection.isCollapsed || !anchor.parent.is( 'element', 'codeBlock' ) || !anchor.hasSameParentAs( selection.focus ) ) {
 				return;
 			}
 
@@ -210,7 +210,7 @@ export default class CodeBlockEditing extends Plugin {
 		this.listenTo( editor.editing.view.document, 'enter', ( evt, data ) => {
 			const positionParent = editor.model.document.selection.getLastPosition().parent;
 
-			if ( !positionParent.is( 'codeBlock' ) ) {
+			if ( !positionParent.is( 'element', 'codeBlock' ) ) {
 				return;
 			}
 
@@ -248,7 +248,7 @@ function breakLineOnEnter( editor ) {
 	let leadingWhiteSpaces;
 
 	// Figure out the indentation (white space chars) at the beginning of the line.
-	if ( node && node.is( 'text' ) ) {
+	if ( node && node.is( '$text' ) ) {
 		leadingWhiteSpaces = getLeadingWhiteSpaces( node );
 	}
 
@@ -287,7 +287,7 @@ function leaveBlockStartOnEnter( editor, isSoftEnter ) {
 		return false;
 	}
 
-	if ( !nodeAfter || !nodeAfter.is( 'softBreak' ) ) {
+	if ( !nodeAfter || !nodeAfter.is( 'element', 'softBreak' ) ) {
 		return false;
 	}
 
@@ -350,7 +350,7 @@ function leaveBlockEndOnEnter( editor, isSoftEnter ) {
 	//
 	//		<codeBlock>foo[<softBreak></softBreak>]</codeBlock>
 	//
-	if ( nodeBefore.is( 'softBreak' ) ) {
+	if ( nodeBefore.is( 'element', 'softBreak' ) ) {
 		emptyLineRangeToRemoveOnEnter = model.createRangeOn( nodeBefore );
 	}
 
@@ -367,10 +367,10 @@ function leaveBlockEndOnEnter( editor, isSoftEnter ) {
 	//		<codeBlock>foo[<softBreak></softBreak>    ]</codeBlock>
 	//
 	else if (
-		nodeBefore.is( 'text' ) &&
+		nodeBefore.is( '$text' ) &&
 		!nodeBefore.data.match( /\S/ ) &&
 		nodeBefore.previousSibling &&
-		nodeBefore.previousSibling.is( 'softBreak' )
+		nodeBefore.previousSibling.is( 'element', 'softBreak' )
 	) {
 		emptyLineRangeToRemoveOnEnter = model.createRange(
 			model.createPositionBefore( nodeBefore.previousSibling ), model.createPositionAfter( nodeBefore )

+ 1 - 1
packages/ckeditor5-code-block/src/converters.js

@@ -147,7 +147,7 @@ export function dataViewToModelCodeBlockInsertion( editingView, languageDefs ) {
 		const viewItem = data.viewItem;
 		const viewChild = viewItem.getChild( 0 );
 
-		if ( !viewChild || !viewChild.is( 'code' ) ) {
+		if ( !viewChild || !viewChild.is( 'element', 'code' ) ) {
 			return;
 		}
 

+ 2 - 2
packages/ckeditor5-code-block/src/outdentcodeblockcommand.js

@@ -173,13 +173,13 @@ function getCodeLineTextNodeAtPosition( position ) {
 
 	// <codeBlock>foo^</codeBlock>
 	// <codeBlock>foo^<softBreak></softBreak>bar</codeBlock>
-	if ( !nodeAtPosition || nodeAtPosition.is( 'softBreak' ) ) {
+	if ( !nodeAtPosition || nodeAtPosition.is( 'element', 'softBreak' ) ) {
 		nodeAtPosition = position.nodeBefore;
 	}
 
 	// <codeBlock>^</codeBlock>
 	// <codeBlock>foo^<softBreak></softBreak>bar</codeBlock>
-	if ( !nodeAtPosition || nodeAtPosition.is( 'softBreak' ) ) {
+	if ( !nodeAtPosition || nodeAtPosition.is( 'element', 'softBreak' ) ) {
 		return null;
 	}
 

+ 2 - 2
packages/ckeditor5-code-block/src/utils.js

@@ -189,7 +189,7 @@ export function getIndentOutdentPositions( model ) {
 		} );
 
 		for ( const { item } of walker ) {
-			if ( item.is( 'textProxy' ) && item.parent.is( 'codeBlock' ) ) {
+			if ( item.is( '$textProxy' ) && item.parent.is( 'element', 'codeBlock' ) ) {
 				const leadingWhiteSpaces = getLeadingWhiteSpaces( item.textNode );
 				const { parent, startOffset } = item.textNode;
 
@@ -213,5 +213,5 @@ export function getIndentOutdentPositions( model ) {
 export function isModelSelectionInCodeBlock( selection ) {
 	const firstBlock = first( selection.getSelectedBlocks() );
 
-	return firstBlock && firstBlock.is( 'codeBlock' );
+	return firstBlock && firstBlock.is( 'element', 'codeBlock' );
 }

+ 2 - 0
packages/ckeditor5-core/src/editor/editor.js

@@ -90,6 +90,8 @@ export default class Editor {
 		this.plugins = new PluginCollection( this, availablePlugins, this._context.plugins );
 
 		/**
+		 * The locale instance.
+		 *
 		 * @readonly
 		 * @type {module:utils/locale~Locale}
 		 */

+ 1 - 1
packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-figure-attributes.js

@@ -131,7 +131,7 @@ function downcastCustomClasses( modelElementName ) {
 function findViewChild( viewElement, viewElementName, conversionApi ) {
 	const viewChildren = Array.from( conversionApi.writer.createRangeIn( viewElement ).getItems() );
 
-	return viewChildren.find( item => item.is( viewElementName ) );
+	return viewChildren.find( item => item.is( 'element', viewElementName ) );
 }
 
 /**

+ 1 - 1
packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-preserving-custom-content.md

@@ -429,7 +429,7 @@ function downcastCustomClassesToChild( viewElementName, modelElementName ) {
 function findViewChild( viewElement, viewElementName, conversionApi ) {
 	const viewChildren = Array.from( conversionApi.writer.createRangeIn( viewElement ).getItems() );
 
-	return viewChildren.find( item => item.is( viewElementName ) );
+	return viewChildren.find( item => item.is( 'element', viewElementName ) );
 }
 
 /**

+ 2 - 1
packages/ckeditor5-engine/src/controller/datacontroller.js

@@ -90,7 +90,8 @@ export default class DataController {
 		 * @member {module:engine/conversion/downcastdispatcher~DowncastDispatcher}
 		 */
 		this.downcastDispatcher = new DowncastDispatcher( {
-			mapper: this.mapper
+			mapper: this.mapper,
+			schema: model.schema
 		} );
 		this.downcastDispatcher.on( 'insert:$text', insertText(), { priority: 'lowest' } );
 

+ 2 - 1
packages/ckeditor5-engine/src/controller/editingcontroller.js

@@ -65,7 +65,8 @@ export default class EditingController {
 		 * @member {module:engine/conversion/downcastdispatcher~DowncastDispatcher} #downcastDispatcher
 		 */
 		this.downcastDispatcher = new DowncastDispatcher( {
-			mapper: this.mapper
+			mapper: this.mapper,
+			schema: model.schema
 		} );
 
 		const doc = this.model.document;

+ 3 - 3
packages/ckeditor5-engine/src/conversion/conversion.js

@@ -337,7 +337,7 @@ export default class Conversion {
 	 *				viewElement => {
 	 *					const fontWeight = viewElement.getStyle( 'font-weight' );
 	 *
-	 *					if ( viewElement.is( 'span' ) && fontWeight && /\d+/.test() && Number( fontWeight ) > 500 ) {
+	 *					if ( viewElement.is( 'element', 'span' ) && fontWeight && /\d+/.test() && Number( fontWeight ) > 500 ) {
 	 *						// Returned value can be an object with the matched properties.
 	 *						// These properties will be "consumed" during the conversion.
 	 *						// See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
@@ -388,7 +388,7 @@ export default class Conversion {
 	 *
 	 *					const size = Number( match[ 1 ] );
 	 *
-	 *					if ( viewElement.is( 'span' ) && size > 10 ) {
+	 *					if ( viewElement.is( 'element', 'span' ) && size > 10 ) {
 	 *						// Returned value can be an object with the matched properties.
 	 *						// These properties will be "consumed" during the conversion.
 	 *						// See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
@@ -413,7 +413,7 @@ export default class Conversion {
 	 *
 	 *					const size = Number( match[ 1 ] );
 	 *
-	 *					if ( viewElement.is( 'span' ) && size < 10 ) {
+	 *					if ( viewElement.is( 'element', 'span' ) && size < 10 ) {
 	 *						// Returned value can be an object with the matched properties.
 	 *						// These properties will be "consumed" during the conversion.
 	 *						// See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.

+ 6 - 0
packages/ckeditor5-engine/src/conversion/downcastdispatcher.js

@@ -658,6 +658,12 @@ function shouldMarkerChangeBeConverted( modelPosition, marker, mapper ) {
  * @member {module:engine/conversion/mapper~Mapper} #mapper
  */
 
+/**
+ * The {@link module:engine/model/schema~Schema} instance set for the model that is downcast.
+ *
+ * @member {module:engine/model/schema~Schema} #schema
+ */
+
 /**
  * The {@link module:engine/view/downcastwriter~DowncastWriter} instance used to manipulate data during conversion.
  *

+ 284 - 7
packages/ckeditor5-engine/src/conversion/downcasthelpers.js

@@ -239,22 +239,25 @@ export default class DowncastHelpers extends ConversionHelpers {
 	/**
 	 * Model marker to view element conversion helper.
 	 *
+	 * **Note**: This method should be used only for editing downcast. For data downcast, use
+	 * {@link #markerToData `#markerToData()`} that produces valid HTML data.
+	 *
 	 * This conversion results in creating a view element on the boundaries of the converted marker. If the converted marker
 	 * is collapsed, only one element is created. For example, model marker set like this: `<paragraph>F[oo b]ar</paragraph>`
 	 * becomes `<p>F<span data-marker="search"></span>oo b<span data-marker="search"></span>ar</p>` in the view.
 	 *
-	 *		editor.conversion.for( 'downcast' ).markerToElement( {
+	 *		editor.conversion.for( 'editingDowncast' ).markerToElement( {
 	 *			model: 'search',
 	 *			view: 'marker-search'
 	 *		} );
 	 *
-	 *		editor.conversion.for( 'downcast' ).markerToElement( {
+	 *		editor.conversion.for( 'editingDowncast' ).markerToElement( {
 	 *			model: 'search',
 	 *			view: 'search-result',
 	 *			converterPriority: 'high'
 	 *		} );
 	 *
-	 *		editor.conversion.for( 'downcast' ).markerToElement( {
+	 *		editor.conversion.for( 'editingDowncast' ).markerToElement( {
 	 *			model: 'search',
 	 *			view: {
 	 *				name: 'span',
@@ -264,7 +267,7 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *			}
 	 *		} );
 	 *
-	 *		editor.conversion.for( 'downcast' ).markerToElement( {
+	 *		editor.conversion.for( 'editingDowncast' ).markerToElement( {
 	 *			model: 'search',
 	 *			view: ( markerData, viewWriter ) => {
 	 *				return viewWriter.createUIElement( 'span', {
@@ -282,8 +285,6 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 * the `data.isOpening` parameter is passed, which is set to `true` for the marker start boundary element, and `false` to
 	 * the marker end boundary element.
 	 *
-	 * This kind of conversion is useful for saving data into the database, so it should be used in the data conversion pipeline.
-	 *
 	 * See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
 	 * to the conversion process.
 	 *
@@ -357,6 +358,119 @@ export default class DowncastHelpers extends ConversionHelpers {
 	markerToHighlight( config ) {
 		return this.add( downcastMarkerToHighlight( config ) );
 	}
+
+	/**
+	 * Model marker converter for data downcast.
+	 *
+	 * This conversion creates a representation for model marker boundaries in the view:
+	 *
+	 * * if the marker boundary is at a position where text nodes are allowed, then a view element with specified tag name
+	 * and `name` attribute is added at that position,
+	 * * in other cases, a specified attribute is set on a view element that is before/after marker boundary.
+	 *
+	 * Typically, the marker names use `group:uniqueId:otherData` convention. For example: `comment:e34zfk9k2n459df53sjl34:zx32c`.
+	 * The default configuration for this conversion is that the first part is `group` part and the rest of
+	 * the marker name becomes `name` part.
+	 *
+	 * Tag and attribute names and values are generated from the marker name:
+	 *
+	 * * templates for attributes are `data-[group]-start-before="[name]"`, `data-[group]-start-after="[name]"`,
+	 * `data-[group]-end-before="[name]"` and `data-[group]-end-after="[name]"`,
+	 * * templates for view elements are `<[group]-start name="[name]">` and `<[group]-end name="[name]">`.
+	 *
+	 * Attributes mark whether given marker start or end boundary is before or after given element.
+	 * Attributes `data-[group]-start-before` and `data-[group]-end-after` are favored.
+	 * The other two are used when the former two cannot be used.
+	 *
+	 * The conversion configuration can take a function that will generate different group and name parts.
+	 * If such function is set as the `config.view` parameter, it is passed a marker name and it is expected to return an object with two
+	 * properties: `group` and `name`. If the function returns falsy value, the conversion will not take place.
+	 *
+	 * Basic usage:
+	 *
+	 *		// Using the default conversion.
+	 *		// In this case, all markers which name starts with 'comment:' will be converted.
+	 *		// The `group` parameter will be set to `comment`.
+	 *		// The `name` parameter will be the rest of the marker name (without `:`).
+	 *		editor.conversion.for( 'dataDowncast' ).markerToData( {
+	 *			model: 'comment'
+	 *		} );
+	 *
+	 * An example of a view that may be generated by this conversion (assuming a marker with name `comment:commentId:uid` marked by `[]`):
+	 *
+	 *		// Model:
+	 *		<paragraph>Foo[bar</paragraph>
+	 *		<image src="abc.jpg"></image>]
+	 *
+	 *		// View:
+	 *		<p>Foo<comment-start name="commentId:uid"></comment-start>bar</p>
+	 *		<figure data-comment-end-after="commentId:uid" class="image"><img src="abc.jpg" /></figure>
+	 *
+	 * In the example above, the comment starts before "bar" and ends after the image.
+	 *
+	 * If `name` part is empty, following view may be generated:
+	 *
+	 *		<p>Foo <myMarker-start></myMarker-start>bar</p>
+	 *		<figure data-myMarker-end-after="" class="image"><img src="abc.jpg" /></figure>
+	 *
+	 * **Note:** situation when some markers have `name` part and some don't is incorrect and should be avoided.
+	 *
+	 * Examples where `data-group-start-after` and `data-group-end-before` are used:
+	 *
+	 *		// Model:
+	 *		<blockQuote>[]<paragraph>Foo</paragraph></blockQuote>
+	 *
+	 * 		// View:
+	 *		<blockquote><p data-group-end-before="name" data-group-start-before="name">Foo</p></blockquote>
+	 *
+	 * Similarly, when marker is collapsed after the last element:
+	 *
+	 *		// Model:
+	 *		<blockQuote><paragraph>Foo</paragraph>[]</blockQuote>
+	 *
+	 *		// View:
+	 *		<blockquote><p data-group-end-after="name" data-group-start-after="name">Foo</p></blockquote>
+	 *
+	 * When there are multiple markers from the same group stored in the same attribute of the same element, their
+	 * name parts are put together in the attribute value, for example: `data-group-start-before="name1,name2,name3"`.
+	 *
+	 * Other examples of usage:
+	 *
+	 *		// Using custom function which is the same as the default conversion:
+	 *		editor.conversion.for( 'dataDowncast' ).markerToData( {
+	 *			model: 'comment'
+	 *			view: markerName => ( {
+	 *				group: 'comment',
+	 *				name: markerName.substr( 8 ) // Removes 'comment:' part.
+	 *			} )
+	 *		} );
+	 *
+	 *		// Using converter priority:
+	 *		editor.conversion.for( 'dataDowncast' ).markerToData( {
+	 *			model: 'comment'
+	 *			view: markerName => ( {
+	 *				group: 'comment',
+	 *				name: markerName.substr( 8 ) // Removes 'comment:' part.
+	 *			} ),
+	 *			converterPriority: 'high'
+	 *		} );
+	 *
+	 * This kind of conversion is useful for saving data into the database, so it should be used in the data conversion pipeline.
+	 *
+	 * See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
+	 * to the conversion process.
+	 *
+	 * @method #markerToData
+	 * @param {Object} config Conversion configuration.
+	 * @param {String} config.model The name of the model marker (or model marker group) to convert.
+	 * @param {Function} [config.view] Function that takes the model marker name as a parameter and returns an object with `group`
+	 * and `name` properties.
+	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
+	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
+	 */
+	markerToData( config ) {
+		return this.add( downcastMarkerToData( config ) );
+	}
 }
 
 /**
@@ -755,6 +869,141 @@ function removeUIElement() {
 	};
 }
 
+// Function factory that creates a default converter for model markers.
+//
+// See {@link DowncastHelpers#markerToData} for more information what type of view is generated.
+//
+// This converter binds created UI elements and affected view elements with the marker name
+// using {@link module:engine/conversion/mapper~Mapper#bindElementToMarker}.
+//
+// @returns {Function} Add marker converter.
+function insertMarkerData( viewCreator ) {
+	return ( evt, data, conversionApi ) => {
+		const viewMarkerData = viewCreator( data.markerName );
+
+		if ( !viewMarkerData ) {
+			return;
+		}
+
+		const markerRange = data.markerRange;
+
+		if ( !conversionApi.consumable.consume( markerRange, evt.name ) ) {
+			return;
+		}
+
+		// Adding closing data first to keep the proper order in the view.
+		handleMarkerBoundary( markerRange, false, conversionApi, data, viewMarkerData );
+		handleMarkerBoundary( markerRange, true, conversionApi, data, viewMarkerData );
+
+		evt.stop();
+	};
+}
+
+// Helper function for `insertMarkerData()` that marks a marker boundary at the beginning or end of given `range`.
+function handleMarkerBoundary( range, isStart, conversionApi, data, viewMarkerData ) {
+	const modelPosition = isStart ? range.start : range.end;
+	const canInsertElement = conversionApi.schema.checkChild( modelPosition, '$text' );
+
+	if ( canInsertElement ) {
+		const viewPosition = conversionApi.mapper.toViewPosition( modelPosition );
+
+		insertMarkerAsElement( viewPosition, isStart, conversionApi, data, viewMarkerData );
+	} else {
+		let modelElement;
+		let isBefore;
+
+		// If possible, we want to add `data-group-start-before` and `data-group-end-after` attributes.
+		// Below `if` is constructed in a way that will favor adding these attributes.
+		//
+		// Also, I assume that there will be always an element either after or before the position.
+		// If not, then it is a case when we are not in a position where text is allowed and also there are no elements around...
+		if ( isStart && modelPosition.nodeAfter || !isStart && !modelPosition.nodeBefore ) {
+			modelElement = modelPosition.nodeAfter;
+			isBefore = true;
+		} else {
+			modelElement = modelPosition.nodeBefore;
+			isBefore = false;
+		}
+
+		const viewElement = conversionApi.mapper.toViewElement( modelElement );
+
+		insertMarkerAsAttribute( viewElement, isStart, isBefore, conversionApi, data, viewMarkerData );
+	}
+}
+
+// Helper function for `insertMarkerData()` that marks a marker boundary in the view as an attribute on a view element.
+function insertMarkerAsAttribute( viewElement, isStart, isBefore, conversionApi, data, viewMarkerData ) {
+	const attributeName = `data-${ viewMarkerData.group }-${ isStart ? 'start' : 'end' }-${ isBefore ? 'before' : 'after' }`;
+
+	const markerNames = viewElement.hasAttribute( attributeName ) ? viewElement.getAttribute( attributeName ).split( ',' ) : [];
+
+	// Adding marker name at the beginning to have the same order in the attribute as there is with marker elements.
+	markerNames.unshift( viewMarkerData.name );
+
+	conversionApi.writer.setAttribute( attributeName, markerNames.join( ',' ), viewElement );
+	conversionApi.mapper.bindElementToMarker( viewElement, data.markerName );
+}
+
+// Helper function for `insertMarkerData()` that marks a marker boundary in the view as a separate view ui element.
+function insertMarkerAsElement( position, isStart, conversionApi, data, viewMarkerData ) {
+	const viewElementName = `${ viewMarkerData.group }-${ isStart ? 'start' : 'end' }`;
+
+	const attrs = viewMarkerData.name ? { 'name': viewMarkerData.name } : null;
+	const viewElement = conversionApi.writer.createUIElement( viewElementName, attrs );
+
+	conversionApi.writer.insert( position, viewElement );
+	conversionApi.mapper.bindElementToMarker( viewElement, data.markerName );
+}
+
+// Function factory that creates a converter for removing a model marker data added by the {@link #insertMarkerData} converter.
+//
+// @returns {Function} Remove marker converter.
+function removeMarkerData( viewCreator ) {
+	return ( evt, data, conversionApi ) => {
+		const viewData = viewCreator( data.markerName );
+
+		if ( !viewData ) {
+			return;
+		}
+
+		const elements = conversionApi.mapper.markerNameToElements( data.markerName );
+
+		if ( !elements ) {
+			return;
+		}
+
+		for ( const element of elements ) {
+			conversionApi.mapper.unbindElementFromMarkerName( element, data.markerName );
+
+			if ( element.is( 'containerElement' ) ) {
+				removeMarkerFromAttribute( `data-${ viewData.group }-start-before`, element );
+				removeMarkerFromAttribute( `data-${ viewData.group }-start-after`, element );
+				removeMarkerFromAttribute( `data-${ viewData.group }-end-before`, element );
+				removeMarkerFromAttribute( `data-${ viewData.group }-end-after`, element );
+			} else {
+				conversionApi.writer.clear( conversionApi.writer.createRangeOn( element ), element );
+			}
+		}
+
+		conversionApi.writer.clearClonedElementsGroup( data.markerName );
+
+		evt.stop();
+
+		function removeMarkerFromAttribute( attributeName, element ) {
+			if ( element.hasAttribute( attributeName ) ) {
+				const markerNames = new Set( element.getAttribute( attributeName ).split( ',' ) );
+				markerNames.delete( viewData.name );
+
+				if ( markerNames.size == 0 ) {
+					conversionApi.writer.removeAttribute( attributeName, element );
+				} else {
+					conversionApi.writer.setAttribute( attributeName, Array.from( markerNames ).join( ',' ), element );
+				}
+			}
+		}
+	};
+}
+
 // Function factory that creates a converter which converts set/change/remove attribute changes from the model to the view.
 //
 // Attributes from the model are converted to the view element attributes in the view. You may provide a custom function to generate
@@ -906,7 +1155,7 @@ function highlightText( highlightDescriptor ) {
 			return;
 		}
 
-		if ( !( data.item instanceof ModelSelection || data.item instanceof DocumentSelection ) && !data.item.is( 'textProxy' ) ) {
+		if ( !( data.item instanceof ModelSelection || data.item instanceof DocumentSelection ) && !data.item.is( '$textProxy' ) ) {
 			return;
 		}
 
@@ -1179,6 +1428,34 @@ function downcastMarkerToElement( config ) {
 	};
 }
 
+// Model marker to view data conversion helper.
+//
+// See {@link ~DowncastHelpers#markerToData `markerToData()` downcast helper} to learn more.
+//
+// @param {Object} config
+// @param {String} config.model
+// @param {Function} [config.view]
+// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal']
+// @returns {Function} Conversion helper.
+function downcastMarkerToData( config ) {
+	config = cloneDeep( config );
+
+	const group = config.model;
+
+	// Default conversion.
+	if ( !config.view ) {
+		config.view = markerName => ( {
+			group,
+			name: markerName.substr( config.model.length + 1 )
+		} );
+	}
+
+	return dispatcher => {
+		dispatcher.on( 'addMarker:' + group, insertMarkerData( config.view ), { priority: config.converterPriority || 'normal' } );
+		dispatcher.on( 'removeMarker:' + group, removeMarkerData( config.view ), { priority: config.converterPriority || 'normal' } );
+	};
+}
+
 // Model marker to highlight conversion helper.
 //
 // See {@link ~DowncastHelpers#markerToElement `.markerToElement()` downcast helper} for examples.

+ 4 - 4
packages/ckeditor5-engine/src/conversion/mapper.js

@@ -445,7 +445,7 @@ export default class Mapper {
 		// viewBlock == viewParent, so we need to calculate the offset in the parent element.
 
 		// If the position is a text it is simple ("ba|r" -> 2).
-		if ( viewParent.is( 'text' ) ) {
+		if ( viewParent.is( '$text' ) ) {
 			return viewOffset;
 		}
 
@@ -488,7 +488,7 @@ export default class Mapper {
 			return callback( viewNode );
 		} else if ( this._viewToModelMapping.has( viewNode ) ) {
 			return 1;
-		} else if ( viewNode.is( 'text' ) ) {
+		} else if ( viewNode.is( '$text' ) ) {
 			return viewNode.data.length;
 		} else if ( viewNode.is( 'uiElement' ) ) {
 			return 0;
@@ -537,7 +537,7 @@ export default class Mapper {
 		let viewOffset = 0;
 
 		// In the text node it is simple: offset in the model equals offset in the text.
-		if ( viewParent.is( 'text' ) ) {
+		if ( viewParent.is( '$text' ) ) {
 			return new ViewPosition( viewParent, expectedOffset );
 		}
 
@@ -631,7 +631,7 @@ export default class Mapper {
 	 *			const sibling = data.modelPosition.nodeBefore;
 	 *
 	 *			// Check if this is the element we are interested in.
-	 *			if ( !sibling.is( 'customElement' ) ) {
+	 *			if ( !sibling.is( 'element', 'customElement' ) ) {
 	 *				return;
 	 *			}
 	 *

+ 1 - 1
packages/ckeditor5-engine/src/conversion/upcastdispatcher.js

@@ -237,7 +237,7 @@ export default class UpcastDispatcher {
 
 		if ( viewItem.is( 'element' ) ) {
 			this.fire( 'element:' + viewItem.name, data, this.conversionApi );
-		} else if ( viewItem.is( 'text' ) ) {
+		} else if ( viewItem.is( '$text' ) ) {
 			this.fire( 'text', data, this.conversionApi );
 		} else {
 			this.fire( 'documentFragment', data, this.conversionApi );

+ 194 - 3
packages/ckeditor5-engine/src/conversion/upcasthelpers.js

@@ -9,6 +9,9 @@ import ConversionHelpers from './conversionhelpers';
 
 import { cloneDeep } from 'lodash-es';
 import ModelSelection from '../model/selection';
+import { attachLinkToDocumentation } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+
+/* global console */
 
 /**
  * Contains {@link module:engine/view/view view} to {@link module:engine/model/model model} converters for
@@ -287,6 +290,8 @@ export default class UpcastHelpers extends ConversionHelpers {
 	/**
 	 * View element to model marker conversion helper.
 	 *
+	 * **Note**: This method was deprecated. Please use {@link #dataToMarker} instead.
+	 *
 	 * This conversion results in creating a model marker. For example, if the marker was stored in a view as an element:
 	 * `<p>Fo<span data-marker="comment" data-comment-id="7"></span>o</p><p>B<span data-marker="comment" data-comment-id="7"></span>ar</p>`,
 	 * after the conversion is done, the marker will be available in
@@ -321,6 +326,7 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 * See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
 	 * to the conversion process.
 	 *
+	 * @deprecated
 	 * @method #elementToMarker
 	 * @param {Object} config Conversion configuration.
 	 * @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
@@ -330,8 +336,93 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 */
 	elementToMarker( config ) {
+		/**
+		 * The {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToMarker `UpcastHelpers#elementToMarker()`}
+		 * method has been deprecated and will be removed in the near future.
+		 * Please use {@link module:engine/conversion/upcasthelpers~UpcastHelpers#dataToMarker `UpcastHelpers#dataToMarker()`} instead.
+		 *
+		 * @error upcast-helpers-element-to-marker-deprecated
+		 */
+		console.warn(
+			attachLinkToDocumentation(
+				'upcast-helpers-element-to-marker-deprecated: ' +
+				'The UpcastHelpers#elementToMarker() method has been deprecated and will be removed in the near future. ' +
+				'Please use UpcastHelpers#dataToMarker() instead.'
+			)
+		);
+
 		return this.add( upcastElementToMarker( config ) );
 	}
+
+	/**
+	 * View to model marker conversion helper.
+	 *
+	 * Converts view data created by {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToData `#markerToData()`}
+	 * back to a model marker.
+	 *
+	 * This converter looks for specific view elements and view attributes that mark marker boundaries. See
+	 * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToData `#markerToData()`} to learn what view data
+	 * is expected by this converter.
+	 *
+	 * The `config.view` property is equal to the marker group name to convert.
+	 *
+	 * By default, this converter creates markers with `group:name` name convention (to match the default `markerToData` conversion).
+	 *
+	 * The conversion configuration can take a function that will generate a marker name.
+	 * If such function is set as the `config.model` parameter, it is passed the `name` part from the view element or attribute and it is
+	 * expected to return a string with the marker name.
+	 *
+	 * Basic usage:
+	 *
+	 *		// Using the default conversion.
+	 *		// In this case, all markers from `comment` group will be converted.
+	 *		// The conversion will look for `<comment-start>` and `<comment-end>` tags and
+	 *		// `data-comment-start-before`, `data-comment-start-after`,
+	 *		// `data-comment-end-before` and `data-comment-end-after` attributes.
+	 *		editor.conversion.for( 'upcast' ).dataToMarker( {
+	 *			view: 'comment'
+	 *		} );
+	 *
+	 * An example of a model that may be generated by this conversion:
+	 *
+	 *		// View:
+	 *		<p>Foo<comment-start name="commentId:uid"></comment-start>bar</p>
+	 *		<figure data-comment-end-after="commentId:uid" class="image"><img src="abc.jpg" /></figure>
+	 *
+	 *		// Model:
+	 *		<paragraph>Foo[bar</paragraph>
+	 *		<image src="abc.jpg"></image>]
+	 *
+	 * Where `[]` are boundaries of a marker that will receive `comment:commentId:uid` name.
+	 *
+	 * Other examples of usage:
+	 *
+	 *		// Using custom function which is the same as the default conversion:
+	 *		editor.conversion.for( 'upcast' ).dataToMarker( {
+	 *			view: 'comment',
+	 *			model: name => 'comment:' + name,
+	 *		} );
+	 *
+	 *		// Using converter priority:
+	 *		editor.conversion.for( 'upcast' ).dataToMarker( {
+	 *			view: 'comment',
+	 *			model: name => 'comment:' + name,
+	 *			converterPriority: 'high'
+	 *		} );
+	 *
+	 * See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
+	 * to the conversion process.
+	 *
+	 * @method #dataToMarker
+	 * @param {Object} config Conversion configuration.
+	 * @param {String} config.view Marker group name to convert.
+	 * @param {Function} [config.model] Function that takes `name` part from the view element or attribute and returns the marker name.
+	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
+	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
+	 */
+	dataToMarker( config ) {
+		return this.add( upcastDataToMarker( config ) );
+	}
 }
 
 /**
@@ -512,11 +603,91 @@ function upcastAttributeToAttribute( config ) {
 function upcastElementToMarker( config ) {
 	config = cloneDeep( config );
 
-	normalizeToMarkerConfig( config );
+	normalizeElementToMarkerConfig( config );
 
 	return upcastElementToElement( config );
 }
 
+// View data to model marker conversion helper.
+//
+// See {@link ~UpcastHelpers#dataToMarker} to learn more.
+//
+// @param {Object} config
+// @param {String} config.view
+// @param {Function} [config.model]
+// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal']
+// @returns {Function} Conversion helper.
+function upcastDataToMarker( config ) {
+	config = cloneDeep( config );
+
+	// Default conversion.
+	if ( !config.model ) {
+		config.model = name => {
+			return name ? config.view + ':' + name : config.view;
+		};
+	}
+
+	const converterStart = prepareToElementConverter( normalizeDataToMarkerConfig( config, 'start' ) );
+	const converterEnd = prepareToElementConverter( normalizeDataToMarkerConfig( config, 'end' ) );
+
+	return dispatcher => {
+		dispatcher.on( 'element:' + config.view + '-start', converterStart, { priority: config.converterPriority || 'normal' } );
+		dispatcher.on( 'element:' + config.view + '-end', converterEnd, { priority: config.converterPriority || 'normal' } );
+
+		// This is attribute upcast so it has to be done after the element upcast.
+		dispatcher.on( 'element', upcastAttributeToMarker( config ), { priority: config.converterPriority || 'low' } );
+	};
+}
+
+// Function factory, returns a callback function which converts view attributes to a model marker.
+//
+// The converter looks for elements with `data-group-start-before`, `data-group-start-after`, `data-group-end-before`
+// and `data-group-end-after` attributes and inserts `$marker` model elements before/after those elements.
+// `group` part is specified in `config.view`.
+//
+// @param {Object} config
+// @param {String} config.view
+// @param {Function} [config.model]
+// @returns {Function} Marker converter.
+function upcastAttributeToMarker( config ) {
+	return ( evt, data, conversionApi ) => {
+		const attrName = `data-${ config.view }`;
+
+		if ( conversionApi.consumable.consume( data.viewItem, { attributes: attrName + '-end-after' } ) ) {
+			addMarkerElements( data.modelRange.end, data.viewItem.getAttribute( attrName + '-end-after' ).split( ',' ) );
+		}
+
+		if ( conversionApi.consumable.consume( data.viewItem, { attributes: attrName + '-start-after' } ) ) {
+			addMarkerElements( data.modelRange.end, data.viewItem.getAttribute( attrName + '-start-after' ).split( ',' ) );
+		}
+
+		if ( conversionApi.consumable.consume( data.viewItem, { attributes: attrName + '-end-before' } ) ) {
+			addMarkerElements( data.modelRange.start, data.viewItem.getAttribute( attrName + '-end-before' ).split( ',' ) );
+		}
+
+		if ( conversionApi.consumable.consume( data.viewItem, { attributes: attrName + '-start-before' } ) ) {
+			addMarkerElements( data.modelRange.start, data.viewItem.getAttribute( attrName + '-start-before' ).split( ',' ) );
+		}
+
+		function addMarkerElements( position, markerViewNames ) {
+			for ( const markerViewName of markerViewNames ) {
+				const markerName = config.model( markerViewName );
+				const element = conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
+
+				conversionApi.writer.insert( element, position );
+
+				if ( data.modelCursor.isEqual( position ) ) {
+					data.modelCursor = data.modelCursor.getShiftedBy( 1 );
+				} else {
+					data.modelCursor = data.modelCursor._getTransformedByInsertion( position, 1 );
+				}
+
+				data.modelRange = data.modelRange._getTransformedByInsertion( position, 1 )[ 0 ];
+			}
+		}
+	};
+}
+
 // Helper function for from-view-element conversion. Checks if `config.view` directly specifies converted view element's name
 // and if so, returns it.
 //
@@ -779,10 +950,10 @@ function setAttributeOn( modelRange, modelAttribute, shallow, conversionApi ) {
 }
 
 // Helper function for upcasting-to-marker conversion. Takes the config in a format requested by `upcastElementToMarker()`
-// function and converts it to a format that is supported by `_upcastElementToElement()` function.
+// function and converts it to a format that is supported by `upcastElementToElement()` function.
 //
 // @param {Object} config Conversion configuration.
-function normalizeToMarkerConfig( config ) {
+function normalizeElementToMarkerConfig( config ) {
 	const oldModel = config.model;
 
 	config.model = ( viewElement, modelWriter ) => {
@@ -791,3 +962,23 @@ function normalizeToMarkerConfig( config ) {
 		return modelWriter.createElement( '$marker', { 'data-name': markerName } );
 	};
 }
+
+// Helper function for upcasting-to-marker conversion. Takes the config in a format requested by `upcastDataToMarker()`
+// function and converts it to a format that is supported by `upcastElementToElement()` function.
+//
+// @param {Object} config Conversion configuration.
+function normalizeDataToMarkerConfig( config, type ) {
+	const configForElements = {};
+
+	// Upcast <markerGroup-start> and <markerGroup-end> elements.
+	configForElements.view = config.view + '-' + type;
+
+	configForElements.model = ( viewElement, modelWriter ) => {
+		const viewName = viewElement.getAttribute( 'name' );
+		const markerName = config.model( viewName );
+
+		return modelWriter.createElement( '$marker', { 'data-name': markerName } );
+	};
+
+	return configForElements;
+}

+ 5 - 5
packages/ckeditor5-engine/src/conversion/viewconsumable.js

@@ -80,7 +80,7 @@ export default class ViewConsumable {
 		let elementConsumables;
 
 		// For text nodes and document fragments just mark them as consumable.
-		if ( element.is( 'text' ) || element.is( 'documentFragment' ) ) {
+		if ( element.is( '$text' ) || element.is( 'documentFragment' ) ) {
 			this._consumables.set( element, true );
 
 			return;
@@ -134,7 +134,7 @@ export default class ViewConsumable {
 		}
 
 		// For text nodes and document fragments return stored boolean value.
-		if ( element.is( 'text' ) || element.is( 'documentFragment' ) ) {
+		if ( element.is( '$text' ) || element.is( 'documentFragment' ) ) {
 			return elementConsumables;
 		}
 
@@ -172,7 +172,7 @@ export default class ViewConsumable {
 	 */
 	consume( element, consumables ) {
 		if ( this.test( element, consumables ) ) {
-			if ( element.is( 'text' ) || element.is( 'documentFragment' ) ) {
+			if ( element.is( '$text' ) || element.is( 'documentFragment' ) ) {
 				// For text nodes and document fragments set value to false.
 				this._consumables.set( element, false );
 			} else {
@@ -218,7 +218,7 @@ export default class ViewConsumable {
 		const elementConsumables = this._consumables.get( element );
 
 		if ( elementConsumables !== undefined ) {
-			if ( element.is( 'text' ) || element.is( 'documentFragment' ) ) {
+			if ( element.is( '$text' ) || element.is( 'documentFragment' ) ) {
 				// For text nodes and document fragments - set consumable to true.
 				this._consumables.set( element, true );
 			} else {
@@ -287,7 +287,7 @@ export default class ViewConsumable {
 			instance = new ViewConsumable( from );
 		}
 
-		if ( from.is( 'text' ) ) {
+		if ( from.is( '$text' ) ) {
 			instance.add( from );
 
 			return instance;

+ 1 - 1
packages/ckeditor5-engine/src/dev-utils/model.js

@@ -228,7 +228,7 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
 
 	downcastDispatcher.on( 'insert:$text', insertText() );
 	downcastDispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
-		if ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection || data.item.is( 'textProxy' ) ) {
+		if ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection || data.item.is( '$textProxy' ) ) {
 			const converter = wrap( ( modelAttributeValue, viewWriter ) => {
 				return viewWriter.createAttributeElement(
 					'model-text-with-attributes',

+ 2 - 2
packages/ckeditor5-engine/src/dev-utils/view.js

@@ -452,7 +452,7 @@ class RangeParser {
 			}
 		}
 
-		if ( node.is( 'text' ) ) {
+		if ( node.is( '$text' ) ) {
 			const regexp = new RegExp(
 				`[${ TEXT_RANGE_START_TOKEN }${ TEXT_RANGE_END_TOKEN }\\${ ELEMENT_RANGE_END_TOKEN }\\${ ELEMENT_RANGE_START_TOKEN }]`,
 				'g'
@@ -688,7 +688,7 @@ class ViewStringify {
 			}
 		}
 
-		if ( root.is( 'text' ) ) {
+		if ( root.is( '$text' ) ) {
 			callback( this._stringifyTextRanges( root ) );
 		}
 	}

+ 1 - 1
packages/ckeditor5-engine/src/model/differ.js

@@ -1039,7 +1039,7 @@ function _getChildrenSnapshot( children ) {
 	const snapshot = [];
 
 	for ( const child of children ) {
-		if ( child.is( 'text' ) ) {
+		if ( child.is( '$text' ) ) {
 			for ( let i = 0; i < child.data.length; i++ ) {
 				snapshot.push( {
 					name: '$text',

+ 1 - 1
packages/ckeditor5-engine/src/model/documentfragment.js

@@ -335,7 +335,7 @@ export default class DocumentFragment {
 	// @if CK_DEBUG_ENGINE //	for ( const child of this.getChildren() ) {
 	// @if CK_DEBUG_ENGINE //		string += '\n';
 
-	// @if CK_DEBUG_ENGINE //		if ( child.is( 'text' ) ) {
+	// @if CK_DEBUG_ENGINE //		if ( child.is( '$text' ) ) {
 	// @if CK_DEBUG_ENGINE //			const textAttrs = stringifyMap( child._attrs );
 
 	// @if CK_DEBUG_ENGINE //			string += '\t'.repeat( 1 );

+ 4 - 6
packages/ckeditor5-engine/src/model/element.js

@@ -104,21 +104,19 @@ export default class Element extends Node {
 	 * Assuming that the object being checked is an element, you can also check its
 	 * {@link module:engine/model/element~Element#name name}:
 	 *
-	 *		element.is( 'image' ); // -> true if this is an <image> element
+	 *		element.is( 'element', 'image' ); // -> true if this is an <image> element
 	 *		element.is( 'element', 'image' ); // -> same as above
-	 *		text.is( 'image' ); -> false
+	 *		text.is( 'element', 'image' ); -> false
 	 *
 	 * {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
 	 *
-	 * @param {String} type Type to check when `name` parameter is present.
-	 * Otherwise, it acts like the `name` parameter.
+	 * @param {String} type Type to check.
 	 * @param {String} [name] Element name.
 	 * @returns {Boolean}
 	 */
 	is( type, name = null ) {
 		if ( !name ) {
 			return type === 'element' || type === 'model:element' ||
-				type === this.name || type === 'model:' + this.name ||
 				// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
 				type === 'node' || type === 'model:node';
 		}
@@ -381,7 +379,7 @@ export default class Element extends Node {
 	// @if CK_DEBUG_ENGINE // 	for ( const child of this.getChildren() ) {
 	// @if CK_DEBUG_ENGINE // 		string += '\n';
 
-	// @if CK_DEBUG_ENGINE // 		if ( child.is( 'text' ) ) {
+	// @if CK_DEBUG_ENGINE // 		if ( child.is( '$text' ) ) {
 	// @if CK_DEBUG_ENGINE // 			const textAttrs = convertMapToTags( child._attrs );
 
 	// @if CK_DEBUG_ENGINE // 			string += '\t'.repeat( level + 1 );

+ 10 - 0
packages/ckeditor5-engine/src/model/markercollection.js

@@ -92,6 +92,16 @@ export default class MarkerCollection {
 	 */
 	_set( markerOrName, range, managedUsingOperations = false, affectsData = false ) {
 		const markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;
+
+		if ( markerName.includes( ',' ) ) {
+			/**
+			 * Marker name cannot contain the "," character.
+			 *
+			 * @error markercollection-incorrect-marker-name
+			 */
+			throw new CKEditorError( 'markercollection-incorrect-marker-name: Marker name cannot contain "," character.', this );
+		}
+
 		const oldMarker = this._markers.get( markerName );
 
 		if ( oldMarker ) {

+ 1 - 1
packages/ckeditor5-engine/src/model/model.js

@@ -573,7 +573,7 @@ export default class Model {
 		}
 
 		for ( const item of range.getItems() ) {
-			if ( item.is( 'textProxy' ) ) {
+			if ( item.is( '$textProxy' ) ) {
 				if ( !ignoreWhitespaces ) {
 					return true;
 				} else if ( item.data.search( /\S/ ) !== -1 ) {

+ 2 - 2
packages/ckeditor5-engine/src/model/node.js

@@ -406,7 +406,7 @@ export default class Node {
 	 *
 	 * By using this method it is also possible to check a name of an element:
 	 *
-	 *		imageElement.is( 'image' ); // -> true
+	 *		imageElement.is( 'element', 'image' ); // -> true
 	 *		imageElement.is( 'element', 'image' ); // -> same as above
 	 *		imageElement.is( 'model:element', 'image' ); // -> same as above, but more precise
 	 *
@@ -427,7 +427,7 @@ export default class Node {
 	 * * {@link module:engine/model/textproxy~TextProxy#is `TextProxy#is()`}
 	 *
 	 * @method #is
-	 * @param {String} type
+	 * @param {String} type Type to check.
 	 * @returns {Boolean}
 	 */
 	is( type ) {

+ 2 - 2
packages/ckeditor5-engine/src/model/operation/utils.js

@@ -143,7 +143,7 @@ export function _setAttribute( range, key, value ) {
 		// Iterator will return `TextProxy` instances but we know that those text proxies will
 		// always represent full text nodes (this is guaranteed thanks to splitting we did before).
 		// So, we can operate on those text proxies' text nodes.
-		const node = item.is( 'textProxy' ) ? item.textNode : item;
+		const node = item.is( '$textProxy' ) ? item.textNode : item;
 
 		if ( value !== null ) {
 			node._setAttribute( key, value );
@@ -219,7 +219,7 @@ function _mergeNodesAtIndex( element, index ) {
 	const nodeAfter = element.getChild( index );
 
 	// Check if both of those nodes are text objects with same attributes.
-	if ( nodeBefore && nodeAfter && nodeBefore.is( 'text' ) && nodeAfter.is( 'text' ) && _haveSameAttributes( nodeBefore, nodeAfter ) ) {
+	if ( nodeBefore && nodeAfter && nodeBefore.is( '$text' ) && nodeAfter.is( '$text' ) && _haveSameAttributes( nodeBefore, nodeAfter ) ) {
 		// Append text of text node after index to the before one.
 		const mergedNode = new Text( nodeBefore.data + nodeAfter.data, nodeBefore.getAttributes() );
 

+ 2 - 5
packages/ckeditor5-engine/src/model/position.js

@@ -146,9 +146,6 @@ export default class Position {
 		return this.path[ this.path.length - 1 ];
 	}
 
-	/**
-	 * @param {Number} newOffset
-	 */
 	set offset( newOffset ) {
 		this.path[ this.path.length - 1 ] = newOffset;
 	}
@@ -176,7 +173,7 @@ export default class Position {
 			}
 		}
 
-		if ( parent.is( 'text' ) ) {
+		if ( parent.is( '$text' ) ) {
 			/**
 			 * The position's path is incorrect. This means that a position does not point to
 			 * a correct place in the tree and hence, some of its methods and getters cannot work correctly.
@@ -1106,7 +1103,7 @@ export default class Position {
 export function getTextNodeAtPosition( position, positionParent ) {
 	const node = positionParent.getChild( positionParent.offsetToIndex( position.offset ) );
 
-	if ( node && node.is( 'text' ) && node.startOffset < position.offset ) {
+	if ( node && node.is( '$text' ) && node.startOffset < position.offset ) {
 		return node;
 	}
 

+ 1 - 5
packages/ckeditor5-engine/src/model/rootelement.js

@@ -68,14 +68,11 @@ export default class RootElement extends Element {
 	 * Assuming that the object being checked is an element, you can also check its
 	 * {@link module:engine/model/element~Element#name name}:
 	 *
-	 *		rootElement.is( '$root' ); // -> true if this is a $root element
 	 *		rootElement.is( 'rootElement', '$root' ); // -> same as above
-	 *		text.is( '$root' ); -> false
 	 *
 	 * {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
 	 *
-	 * @param {String} type Type to check when `name` parameter is present.
-	 * Otherwise, it acts like the `name` parameter.
+	 * @param {String} type Type to check.
 	 * @param {String} [name] Element name.
 	 * @returns {Boolean}
 	 */
@@ -84,7 +81,6 @@ export default class RootElement extends Element {
 			return type === 'rootElement' || type === 'model:rootElement' ||
 				// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
 				type === 'element' || type === 'model:element' ||
-				type === this.name || type === 'model:' + this.name ||
 				type === 'node' || type === 'model:node';
 		}
 

+ 2 - 2
packages/ckeditor5-engine/src/model/schema.js

@@ -186,7 +186,7 @@ export default class Schema {
 
 		if ( typeof item == 'string' ) {
 			itemName = item;
-		} else if ( item.is && ( item.is( 'text' ) || item.is( 'textProxy' ) ) ) {
+		} else if ( item.is && ( item.is( '$text' ) || item.is( '$textProxy' ) ) ) {
 			itemName = '$text';
 		}
 		// Element or module:engine/model/schema~SchemaContextItem.
@@ -774,7 +774,7 @@ export default class Schema {
 	removeDisallowedAttributes( nodes, writer ) {
 		for ( const node of nodes ) {
 			// When node is a `Text` it has no children, so just filter it out.
-			if ( node.is( 'text' ) ) {
+			if ( node.is( '$text' ) ) {
 				removeDisallowedAttributeFromNode( this, node, writer );
 			}
 			// In a case of `Element` iterates through positions between nodes inside this element

+ 10 - 6
packages/ckeditor5-engine/src/model/text.js

@@ -67,22 +67,26 @@ export default class Text extends Node {
 	/**
 	 * Checks whether this object is of the given.
 	 *
-	 *		text.is( 'text' ); // -> true
+	 *		text.is( '$text' ); // -> true
 	 *		text.is( 'node' ); // -> true
-	 *		text.is( 'model:text' ); // -> true
+	 *		text.is( 'model:$text' ); // -> true
 	 *		text.is( 'model:node' ); // -> true
 	 *
-	 *		text.is( 'view:text' ); // -> false
+	 *		text.is( 'view:$text' ); // -> false
 	 *		text.is( 'documentSelection' ); // -> false
 	 *
 	 * {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
 	 *
-	 * @param {String} type Type to check when `name` parameter is present.
-	 * Otherwise, it acts like the `name` parameter.
+	 * **Note:** Until version 20.0.0 this method wasn't accepting `'$text'` type. The legacy `'text'` type is still
+	 * accepted for backward compatibility.
+	 *
+	 * @param {String} type Type to check.
 	 * @returns {Boolean}
 	 */
 	is( type ) {
-		return type === 'text' || type === 'model:text' ||
+		return type === '$text' || type === 'model:$text' ||
+			// This are legacy values kept for backward compatibility.
+			type === 'text' || type === 'model:text' ||
 			// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
 			type === 'node' || type === 'model:node';
 	}

+ 10 - 5
packages/ckeditor5-engine/src/model/textproxy.js

@@ -166,19 +166,24 @@ export default class TextProxy {
 	/**
 	 * Checks whether this object is of the given.
 	 *
-	 *		textProxy.is( 'textProxy' ); // -> true
-	 *		textProxy.is( 'model:textProxy' ); // -> true
+	 *		textProxy.is( '$textProxy' ); // -> true
+	 *		textProxy.is( 'model:$textProxy' ); // -> true
 	 *
-	 *		textProxy.is( 'view:textProxy' ); // -> false
+	 *		textProxy.is( 'view:$textProxy' ); // -> false
 	 *		textProxy.is( 'range' ); // -> false
 	 *
 	 * {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
 	 *
-	 * @param {String} type
+	 * **Note:** Until version 20.0.0 this method wasn't accepting `'$textProxy'` type. The legacy `'textProxt'` type is still
+	 * accepted for backward compatibility.
+	 *
+	 * @param {String} type Type to check.
 	 * @returns {Boolean}
 	 */
 	is( type ) {
-		return type === 'textProxy' || type === 'model:textProxy';
+		return type === '$textProxy' || type === 'model:$textProxy' ||
+			// This are legacy values kept for backward compatibility.
+			type === 'textProxy' || type === 'model:textProxy';
 	}
 
 	/**

+ 1 - 1
packages/ckeditor5-engine/src/model/treewalker.js

@@ -410,5 +410,5 @@ function formatReturnValue( type, item, previousPosition, nextPosition, length )
 /**
  * Tree walking directions.
  *
- * @typedef {'forward'|'backward'} module:engine/view/treewalker~TreeWalkerDirection
+ * @typedef {'forward'|'backward'} module:engine/model/treewalker~TreeWalkerDirection
  */

+ 1 - 1
packages/ckeditor5-engine/src/model/utils/getselectedcontent.js

@@ -70,7 +70,7 @@ export default function getSelectedContent( model, selection ) {
 
 		// Clone the whole contents.
 		for ( const item of flatSubtreeRange.getItems( { shallow: true } ) ) {
-			if ( item.is( 'textProxy' ) ) {
+			if ( item.is( '$textProxy' ) ) {
 				writer.appendText( item.data, item.getAttributes(), frag );
 			} else {
 				writer.append( writer.cloneElement( item, true ), frag );

+ 1 - 1
packages/ckeditor5-engine/src/model/utils/modifyselection.js

@@ -173,7 +173,7 @@ function getCorrectWordBreakPosition( walker, isForward ) {
 			const nextNode = isForward ? walker.position.nodeAfter : walker.position.nodeBefore;
 
 			// Scan only text nodes. Ignore inline elements (like `<softBreak>`).
-			if ( nextNode && nextNode.is( 'text' ) ) {
+			if ( nextNode && nextNode.is( '$text' ) ) {
 				// Check boundary char of an adjacent text node.
 				const boundaryChar = nextNode.data.charAt( isForward ? 0 : nextNode.data.length - 1 );
 

+ 3 - 5
packages/ckeditor5-engine/src/view/attributeelement.js

@@ -145,14 +145,13 @@ export default class AttributeElement extends Element {
 	 * Assuming that the object being checked is an attribute element, you can also check its
 	 * {@link module:engine/view/attributeelement~AttributeElement#name name}:
 	 *
-	 *		attributeElement.is( 'b' ); // -> true if this is a bold element
+	 *		attributeElement.is( 'element', 'b' ); // -> true if this is a bold element
 	 *		attributeElement.is( 'attributeElement', 'b' ); // -> same as above
-	 *		text.is( 'b' ); -> false
+	 *		text.is( 'element', 'b' ); -> false
 	 *
 	 * {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
 	 *
-	 * @param {String} type Type to check when `name` parameter is present.
-	 * Otherwise, it acts like the `name` parameter.
+	 * @param {String} type Type to check.
 	 * @param {String} [name] Element name.
 	 * @returns {Boolean}
 	 */
@@ -160,7 +159,6 @@ export default class AttributeElement extends Element {
 		if ( !name ) {
 			return type === 'attributeElement' || type === 'view:attributeElement' ||
 				// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
-				type === this.name || type === 'view:' + this.name ||
 				type === 'element' || type === 'view:element' ||
 				type === 'node' || type === 'view:node';
 		} else {

+ 3 - 5
packages/ckeditor5-engine/src/view/containerelement.js

@@ -71,14 +71,13 @@ export default class ContainerElement extends Element {
 	 * Assuming that the object being checked is a container element, you can also check its
 	 * {@link module:engine/view/containerelement~ContainerElement#name name}:
 	 *
-	 *		containerElement.is( 'div' ); // -> true if this is a div container element
+	 *		containerElement.is( 'element', 'div' ); // -> true if this is a div container element
 	 *		containerElement.is( 'contaienrElement', 'div' ); // -> same as above
-	 *		text.is( 'div' ); -> false
+	 *		text.is( 'element', 'div' ); -> false
 	 *
 	 * {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
 	 *
-	 * @param {String} type Type to check when `name` parameter is present.
-	 * Otherwise, it acts like the `name` parameter.
+	 * @param {String} type Type to check.
 	 * @param {String} [name] Element name.
 	 * @returns {Boolean}
 	 */
@@ -86,7 +85,6 @@ export default class ContainerElement extends Element {
 		if ( !name ) {
 			return type === 'containerElement' || type === 'view:containerElement' ||
 				// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
-				type === this.name || type === 'view:' + this.name ||
 				type === 'element' || type === 'view:element' ||
 				type === 'node' || type === 'view:node';
 		} else {

+ 1 - 1
packages/ckeditor5-engine/src/view/documentfragment.js

@@ -224,7 +224,7 @@ export default class DocumentFragment {
 	// @if CK_DEBUG_ENGINE //	let string = 'ViewDocumentFragment: [';
 
 	// @if CK_DEBUG_ENGINE //	for ( const child of this.getChildren() ) {
-	// @if CK_DEBUG_ENGINE //		if ( child.is( 'text' ) ) {
+	// @if CK_DEBUG_ENGINE //		if ( child.is( '$text' ) ) {
 	// @if CK_DEBUG_ENGINE //			string += '\n' + '\t'.repeat( 1 ) + child.data;
 	// @if CK_DEBUG_ENGINE //		} else {
 	// @if CK_DEBUG_ENGINE //			string += '\n' + child.printTree( 1 );

+ 5 - 5
packages/ckeditor5-engine/src/view/domconverter.js

@@ -201,7 +201,7 @@ export default class DomConverter {
 	 * @returns {Node|DocumentFragment} Converted node or DocumentFragment.
 	 */
 	viewToDom( viewNode, domDocument, options = {} ) {
-		if ( viewNode.is( 'text' ) ) {
+		if ( viewNode.is( '$text' ) ) {
 			const textData = this._processDataFromViewText( viewNode );
 
 			return domDocument.createTextNode( textData );
@@ -317,7 +317,7 @@ export default class DomConverter {
 	viewPositionToDom( viewPosition ) {
 		const viewParent = viewPosition.parent;
 
-		if ( viewParent.is( 'text' ) ) {
+		if ( viewParent.is( '$text' ) ) {
 			const domParent = this.findCorrespondingDomText( viewParent );
 
 			if ( !domParent ) {
@@ -348,7 +348,7 @@ export default class DomConverter {
 			} else {
 				const nodeBefore = viewPosition.nodeBefore;
 
-				domBefore = nodeBefore.is( 'text' ) ?
+				domBefore = nodeBefore.is( '$text' ) ?
 					this.findCorrespondingDomText( nodeBefore ) :
 					this.mapViewToDom( viewPosition.nodeBefore );
 
@@ -1135,11 +1135,11 @@ export default class DomConverter {
 				return null;
 			}
 			// <br> found – it works like a block boundary, so do not scan further.
-			else if ( value.item.is( 'br' ) ) {
+			else if ( value.item.is( 'element', 'br' ) ) {
 				return null;
 			}
 			// Found a text node in the same container element.
-			else if ( value.item.is( 'textProxy' ) ) {
+			else if ( value.item.is( '$textProxy' ) ) {
 				return value.item;
 			}
 		}

+ 10 - 10
packages/ckeditor5-engine/src/view/downcastwriter.js

@@ -529,7 +529,7 @@ export default class DowncastWriter {
 		const positionParent = position.parent;
 
 		// When inside text node - nothing to merge.
-		if ( positionParent.is( 'text' ) ) {
+		if ( positionParent.is( '$text' ) ) {
 			return position;
 		}
 
@@ -553,7 +553,7 @@ export default class DowncastWriter {
 		}
 
 		// When position is between two text nodes.
-		if ( nodeBefore.is( 'text' ) && nodeAfter.is( 'text' ) ) {
+		if ( nodeBefore.is( '$text' ) && nodeAfter.is( '$text' ) ) {
 			return mergeTextNodes( nodeBefore, nodeAfter );
 		}
 		// When position is between two same attribute elements.
@@ -749,7 +749,7 @@ export default class DowncastWriter {
 				// Create range on this element.
 				rangeToRemove = Range._createOn( item );
 				// When range starts inside Text or TextProxy element.
-			} else if ( !current.nextPosition.isAfter( range.start ) && item.is( 'textProxy' ) ) {
+			} else if ( !current.nextPosition.isAfter( range.start ) && item.is( '$textProxy' ) ) {
 				// We need to check if parent of this text matches to given element.
 				const parentElement = item.getAncestors().find( ancestor => {
 					return ancestor.is( 'element' ) && element.isSimilar( ancestor );
@@ -1104,7 +1104,7 @@ export default class DowncastWriter {
 
 		while ( i < endOffset ) {
 			const child = parent.getChild( i );
-			const isText = child.is( 'text' );
+			const isText = child.is( '$text' );
 			const isAttribute = child.is( 'attributeElement' );
 			const isEmpty = child.is( 'emptyElement' );
 			const isUI = child.is( 'uiElement' );
@@ -1334,7 +1334,7 @@ export default class DowncastWriter {
 		}
 
 		// When position is inside text node - break it and place new position between two text nodes.
-		if ( position.parent.is( 'text' ) ) {
+		if ( position.parent.is( '$text' ) ) {
 			position = breakTextNode( position );
 		}
 
@@ -1573,7 +1573,7 @@ export default class DowncastWriter {
 		}
 
 		// There are no attributes to break and text nodes breaking is not forced.
-		if ( !forceSplitText && positionParent.is( 'text' ) && isContainerOrFragment( positionParent.parent ) ) {
+		if ( !forceSplitText && positionParent.is( '$text' ) && isContainerOrFragment( positionParent.parent ) ) {
 			return position.clone();
 		}
 
@@ -1583,7 +1583,7 @@ export default class DowncastWriter {
 		}
 
 		// Break text and start again in new position.
-		if ( positionParent.is( 'text' ) ) {
+		if ( positionParent.is( '$text' ) ) {
 			return this._breakAttributes( breakTextNode( position ), forceSplitText );
 		}
 
@@ -1778,13 +1778,13 @@ function shouldABeOutsideB( a, b ) {
 function movePositionToTextNode( position ) {
 	const nodeBefore = position.nodeBefore;
 
-	if ( nodeBefore && nodeBefore.is( 'text' ) ) {
+	if ( nodeBefore && nodeBefore.is( '$text' ) ) {
 		return new Position( nodeBefore, nodeBefore.data.length );
 	}
 
 	const nodeAfter = position.nodeAfter;
 
-	if ( nodeAfter && nodeAfter.is( 'text' ) ) {
+	if ( nodeAfter && nodeAfter.is( '$text' ) ) {
 		return new Position( nodeAfter, 0 );
 	}
 
@@ -1866,7 +1866,7 @@ function validateNodesToInsert( nodes, errorContext ) {
 			throw new CKEditorError( 'view-writer-insert-invalid-node', errorContext );
 		}
 
-		if ( !node.is( 'text' ) ) {
+		if ( !node.is( '$text' ) ) {
 			validateNodesToInsert( node.getChildren(), errorContext );
 		}
 	}

+ 3 - 5
packages/ckeditor5-engine/src/view/editableelement.js

@@ -83,14 +83,13 @@ export default class EditableElement extends ContainerElement {
 	 * Assuming that the object being checked is an editbale element, you can also check its
 	 * {@link module:engine/view/editableelement~EditableElement#name name}:
 	 *
-	 *		editableElement.is( 'div' ); // -> true if this is a div element
+	 *		editableElement.is( 'element', 'div' ); // -> true if this is a div element
 	 *		editableElement.is( 'editableElement', 'div' ); // -> same as above
-	 *		text.is( 'div' ); -> false
+	 *		text.is( 'element', 'div' ); -> false
 	 *
 	 * {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
 	 *
-	 * @param {String} type Type to check when `name` parameter is present.
-	 * Otherwise, it acts like the `name` parameter.
+	 * @param {String} type Type to check.
 	 * @param {String} [name] Element name.
 	 * @returns {Boolean}
 	 */
@@ -99,7 +98,6 @@ export default class EditableElement extends ContainerElement {
 			return type === 'editableElement' || type === 'view:editableElement' ||
 				// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
 				type === 'containerElement' || type === 'view:containerElement' ||
-				type === this.name || type === 'view:' + this.name ||
 				type === 'element' || type === 'view:element' ||
 				type === 'node' || type === 'view:node';
 		} else {

+ 5 - 7
packages/ckeditor5-engine/src/view/element.js

@@ -164,21 +164,19 @@ export default class Element extends Node {
 	 * Assuming that the object being checked is an element, you can also check its
 	 * {@link module:engine/view/element~Element#name name}:
 	 *
-	 *		element.is( 'img' ); // -> true if this is an <img> element
+	 *		element.is( 'element', 'img' ); // -> true if this is an <img> element
 	 *		element.is( 'element', 'img' ); // -> same as above
-	 *		text.is( 'img' ); -> false
+	 *		text.is( 'element', 'img' ); -> false
 	 *
 	 * {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
 	 *
-	 * @param {String} type Type to check when `name` parameter is present.
-	 * Otherwise, it acts like the `name` parameter.
+	 * @param {String} type Type to check.
 	 * @param {String} [name] Element name.
 	 * @returns {Boolean}
 	 */
 	is( type, name = null ) {
 		if ( !name ) {
-			return type === this.name || type === 'view:' + this.name ||
-				type === 'element' || type === 'view:element' ||
+			return type === 'element' || type === 'view:element' ||
 				// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
 				type === 'node' || type === 'view:node';
 		} else {
@@ -834,7 +832,7 @@ export default class Element extends Node {
 	// @if CK_DEBUG_ENGINE //	string += '\t'.repeat( level ) + `<${ this.name }${ convertMapToTags( this.getAttributes() ) }>`;
 
 	// @if CK_DEBUG_ENGINE //	for ( const child of this.getChildren() ) {
-	// @if CK_DEBUG_ENGINE //		if ( child.is( 'text' ) ) {
+	// @if CK_DEBUG_ENGINE //		if ( child.is( '$text' ) ) {
 	// @if CK_DEBUG_ENGINE //			string += '\n' + '\t'.repeat( level + 1 ) + child.data;
 	// @if CK_DEBUG_ENGINE //		} else {
 	// @if CK_DEBUG_ENGINE //			string += '\n' + child.printTree( level + 1 );

+ 3 - 5
packages/ckeditor5-engine/src/view/emptyelement.js

@@ -62,14 +62,13 @@ export default class EmptyElement extends Element {
 	 * Assuming that the object being checked is an empty element, you can also check its
 	 * {@link module:engine/view/emptyelement~EmptyElement#name name}:
 	 *
-	 *		emptyElement.is( 'img' ); // -> true if this is a img element
+	 *		emptyElement.is( 'element', 'img' ); // -> true if this is a img element
 	 *		emptyElement.is( 'emptyElement', 'img' ); // -> same as above
-	 *		text.is( 'img' ); -> false
+	 *		text.is( 'element', 'img' ); -> false
 	 *
 	 * {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
 	 *
-	 * @param {String} type Type to check when `name` parameter is present.
-	 * Otherwise, it acts like the `name` parameter.
+	 * @param {String} type Type to check.
 	 * @param {String} [name] Element name.
 	 * @returns {Boolean}
 	 */
@@ -77,7 +76,6 @@ export default class EmptyElement extends Element {
 		if ( !name ) {
 			return type === 'emptyElement' || type === 'view:emptyElement' ||
 				// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
-				type === this.name || type === 'view:' + this.name ||
 				type === 'element' || type === 'view:element' ||
 				type === 'node' || type === 'view:node';
 		} else {

+ 2 - 0
packages/ckeditor5-engine/src/view/filler.js

@@ -64,6 +64,8 @@ export const INLINE_FILLER_LENGTH = 7;
 
 /**
  * Inline filler which is a sequence of the zero width spaces.
+ *
+ * @type {String}
  */
 export const INLINE_FILLER = ( () => {
 	let inlineFiller = '';

+ 2 - 3
packages/ckeditor5-engine/src/view/node.js

@@ -312,8 +312,7 @@ export default class Node {
 	 *
 	 * By using this method it is also possible to check a name of an element:
 	 *
-	 *		imgElement.is( 'img' ); // -> true
-	 *		imgElement.is( 'element', 'img' ); // -> same as above
+	 *		imgElement.is( 'element', 'img' ); // -> true
 	 *		imgElement.is( 'view:element', 'img' ); // -> same as above, but more precise
 	 *
 	 * The list of view objects which implement the `is()` method:
@@ -335,7 +334,7 @@ export default class Node {
 	 * * {@link module:engine/view/uielement~UIElement#is `UIElement#is()`}
 	 *
 	 * @method #is
-	 * @param {String} type
+	 * @param {String} type Type to check.
 	 * @returns {Boolean}
 	 */
 	is( type ) {

+ 1 - 1
packages/ckeditor5-engine/src/view/observer/mutationobserver.js

@@ -264,7 +264,7 @@ export default class MutationObserver extends Observer {
 				return true;
 			}
 			// Texts.
-			else if ( child1.is( 'text' ) && child2.is( 'text' ) ) {
+			else if ( child1.is( '$text' ) && child2.is( '$text' ) ) {
 				return child1.data === child2.data;
 			}
 

+ 6 - 6
packages/ckeditor5-engine/src/view/position.js

@@ -59,7 +59,7 @@ export default class Position {
 	 * @type {module:engine/view/node~Node|null}
 	 */
 	get nodeAfter() {
-		if ( this.parent.is( 'text' ) ) {
+		if ( this.parent.is( '$text' ) ) {
 			return null;
 		}
 
@@ -74,7 +74,7 @@ export default class Position {
 	 * @type {module:engine/view/node~Node|null}
 	 */
 	get nodeBefore() {
-		if ( this.parent.is( 'text' ) ) {
+		if ( this.parent.is( '$text' ) ) {
 			return null;
 		}
 
@@ -98,7 +98,7 @@ export default class Position {
 	 * @type {Boolean}
 	 */
 	get isAtEnd() {
-		const endOffset = this.parent.is( 'text' ) ? this.parent.data.length : this.parent.childCount;
+		const endOffset = this.parent.is( '$text' ) ? this.parent.data.length : this.parent.childCount;
 
 		return this.offset === endOffset;
 	}
@@ -346,7 +346,7 @@ export default class Position {
 			const node = itemOrPosition;
 
 			if ( offset == 'end' ) {
-				offset = node.is( 'text' ) ? node.data.length : node.childCount;
+				offset = node.is( '$text' ) ? node.data.length : node.childCount;
 			} else if ( offset == 'before' ) {
 				return this._createBefore( node );
 			} else if ( offset == 'after' ) {
@@ -378,7 +378,7 @@ export default class Position {
 	 */
 	static _createAfter( item ) {
 		// TextProxy is not a instance of Node so we need do handle it in specific way.
-		if ( item.is( 'textProxy' ) ) {
+		if ( item.is( '$textProxy' ) ) {
 			return new Position( item.textNode, item.offsetInText + item.data.length );
 		}
 
@@ -404,7 +404,7 @@ export default class Position {
 	 */
 	static _createBefore( item ) {
 		// TextProxy is not a instance of Node so we need do handle it in specific way.
-		if ( item.is( 'textProxy' ) ) {
+		if ( item.is( '$textProxy' ) ) {
 			return new Position( item.textNode, item.offsetInText );
 		}
 

+ 7 - 7
packages/ckeditor5-engine/src/view/range.js

@@ -113,11 +113,11 @@ export default class Range {
 		let end = this.end.getLastMatchingPosition( enlargeTrimSkip );
 
 		// Fix positions, in case if they are in Text node.
-		if ( start.parent.is( 'text' ) && start.isAtStart ) {
+		if ( start.parent.is( '$text' ) && start.isAtStart ) {
 			start = Position._createBefore( start.parent );
 		}
 
-		if ( end.parent.is( 'text' ) && end.isAtEnd ) {
+		if ( end.parent.is( '$text' ) && end.isAtEnd ) {
 			end = Position._createAfter( end.parent );
 		}
 
@@ -153,11 +153,11 @@ export default class Range {
 		const nodeBeforeEnd = end.nodeBefore;
 
 		// Because TreeWalker prefers positions next to text node, we need to move them manually into these text nodes.
-		if ( nodeAfterStart && nodeAfterStart.is( 'text' ) ) {
+		if ( nodeAfterStart && nodeAfterStart.is( '$text' ) ) {
 			start = new Position( nodeAfterStart, 0 );
 		}
 
-		if ( nodeBeforeEnd && nodeBeforeEnd.is( 'text' ) ) {
+		if ( nodeBeforeEnd && nodeBeforeEnd.is( '$text' ) ) {
 			end = new Position( nodeBeforeEnd, nodeBeforeEnd.data.length );
 		}
 
@@ -359,11 +359,11 @@ export default class Range {
 		// These are basically the same range, only the difference is if the range position is at
 		// at the end/at the beginning of a text node or just before/just after the text node.
 		//
-		if ( this.start.parent.is( 'text' ) && this.start.isAtEnd && this.start.parent.nextSibling ) {
+		if ( this.start.parent.is( '$text' ) && this.start.isAtEnd && this.start.parent.nextSibling ) {
 			nodeAfterStart = this.start.parent.nextSibling;
 		}
 
-		if ( this.end.parent.is( 'text' ) && this.end.isAtStart && this.end.parent.previousSibling ) {
+		if ( this.end.parent.is( '$text' ) && this.end.isAtStart && this.end.parent.previousSibling ) {
 			nodeBeforeEnd = this.end.parent.previousSibling;
 		}
 
@@ -517,7 +517,7 @@ export default class Range {
 	 * @returns {module:engine/view/range~Range}
 	 */
 	static _createOn( item ) {
-		const size = item.is( 'textProxy' ) ? item.offsetSize : 1;
+		const size = item.is( '$textProxy' ) ? item.offsetSize : 1;
 
 		return this._createFromPositionAndShift( Position._createBefore( item ), size );
 	}

+ 2 - 2
packages/ckeditor5-engine/src/view/renderer.js

@@ -332,7 +332,7 @@ export default class Renderer {
 	_getInlineFillerPosition() {
 		const firstPos = this.selection.getFirstPosition();
 
-		if ( firstPos.parent.is( 'text' ) ) {
+		if ( firstPos.parent.is( '$text' ) ) {
 			return ViewPosition._createBefore( this.selection.getFirstPosition().parent );
 		} else {
 			return firstPos;
@@ -659,7 +659,7 @@ export default class Renderer {
 			return;
 		}
 
-		if ( viewNode.is( 'text' ) ) {
+		if ( viewNode.is( '$text' ) ) {
 			this.markedTexts.add( viewNode );
 		} else if ( viewNode.is( 'element' ) ) {
 			for ( const child of viewNode.getChildren() ) {

+ 3 - 5
packages/ckeditor5-engine/src/view/rooteditableelement.js

@@ -55,14 +55,13 @@ export default class RootEditableElement extends EditableElement {
 	 * Assuming that the object being checked is a root editable element, you can also check its
 	 * {@link module:engine/view/rooteditableelement~RootEditableElement#name name}:
 	 *
-	 *		rootEditableElement.is( 'div' ); // -> true if this is a div root editable element
+	 *		rootEditableElement.is( 'element', 'div' ); // -> true if this is a div root editable element
 	 *		rootEditableElement.is( 'rootElement', 'div' ); // -> same as above
-	 *		text.is( 'div' ); -> false
+	 *		text.is( 'element', 'div' ); -> false
 	 *
 	 * {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
 	 *
-	 * @param {String} type Type to check when `name` parameter is present.
-	 * Otherwise, it acts like the `name` parameter.
+	 * @param {String} type Type to check.
 	 * @param {String} [name] Element name.
 	 * @returns {Boolean}
 	 */
@@ -72,7 +71,6 @@ export default class RootEditableElement extends EditableElement {
 				// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
 				type === 'editableElement' || type === 'view:editableElement' ||
 				type === 'containerElement' || type === 'view:containerElement' ||
-				type === this.name || type === 'view:' + this.name ||
 				type === 'element' || type === 'view:element' ||
 				type === 'node' || type === 'view:node';
 		} else {

+ 15 - 13
packages/ckeditor5-engine/src/view/text.js

@@ -45,22 +45,27 @@ export default class Text extends Node {
 	/**
 	 * Checks whether this object is of the given type.
 	 *
-	 *		text.is( 'text' ); // -> true
+	 *		text.is( '$text' ); // -> true
 	 *		text.is( 'node' ); // -> true
-	 *		text.is( 'view:text' ); // -> true
+	 *		text.is( 'view:$text' ); // -> true
 	 *		text.is( 'view:node' ); // -> true
 	 *
-	 *		text.is( 'model:text' ); // -> false
+	 *		text.is( 'model:$text' ); // -> false
 	 *		text.is( 'element' ); // -> false
 	 *		text.is( 'range' ); // -> false
 	 *
 	 * {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
 	 *
-	 * @param {String} type
+	 * **Note:** Until version 20.0.0 this method wasn't accepting `'$text'` type. The legacy `'text'` type is still
+	 * accepted for backward compatibility.
+	 *
+	 * @param {String} type Type to check.
 	 * @returns {Boolean}
 	 */
 	is( type ) {
-		return type === 'text' || type === 'view:text' ||
+		return type === '$text' || type === 'view:$text' ||
+			// This are legacy values kept for backward compatibility.
+			type === 'text' || type === 'view:text' ||
 			// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
 			type === 'node' || type === 'view:node';
 	}
@@ -76,7 +81,9 @@ export default class Text extends Node {
 	}
 
 	/**
-	 * This getter is required when using the addition assignment operator on protected property:
+	 * The `_data` property is controlled by a getter and a setter.
+	 *
+	 * The getter is required when using the addition assignment operator on protected property:
 	 *
 	 *		const foo = downcastWriter.createText( 'foo' );
 	 *		const bar = downcastWriter.createText( 'bar' );
@@ -86,6 +93,8 @@ export default class Text extends Node {
 	 *
 	 * If the protected getter didn't exist, `foo._data` will return `undefined` and result of the merge will be invalid.
 	 *
+	 * The setter sets data and fires the {@link module:engine/view/node~Node#event:change:text change event}.
+	 *
 	 * @protected
 	 * @type {String}
 	 */
@@ -93,13 +102,6 @@ export default class Text extends Node {
 		return this.data;
 	}
 
-	/**
-	 * Sets data and fires the {@link module:engine/view/node~Node#event:change:text change event}.
-	 *
-	 * @protected
-	 * @fires change:text
-	 * @param {String} data New data for the text node.
-	 */
 	set _data( data ) {
 		this._fireChange( 'text', this );
 

+ 10 - 5
packages/ckeditor5-engine/src/view/textproxy.js

@@ -143,20 +143,25 @@ export default class TextProxy {
 	/**
 	 * Checks whether this object is of the given type.
 	 *
-	 *		textProxy.is( 'textProxy' ); // -> true
-	 *		textProxy.is( 'view:textProxy' ); // -> true
+	 *		textProxy.is( '$textProxy' ); // -> true
+	 *		textProxy.is( 'view:$textProxy' ); // -> true
 	 *
-	 *		textProxy.is( 'model:textProxy' ); // -> false
+	 *		textProxy.is( 'model:$textProxy' ); // -> false
 	 *		textProxy.is( 'element' ); // -> false
 	 *		textProxy.is( 'range' ); // -> false
 	 *
 	 * {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
 	 *
-	 * @param {String} type
+	 * **Note:** Until version 20.0.0 this method wasn't accepting `'$textProxy'` type. The legacy `'textProxy'` type is still
+	 * accepted for backward compatibility.
+	 *
+	 * @param {String} type Type to check.
 	 * @returns {Boolean}
 	 */
 	is( type ) {
-		return type === 'textProxy' || type === 'view:textProxy';
+		return type === '$textProxy' || type === 'view:$textProxy' ||
+			// This are legacy values kept for backward compatibility.
+			type === 'textProxy' || type === 'view:textProxy';
 	}
 
 	/**

+ 3 - 5
packages/ckeditor5-engine/src/view/uielement.js

@@ -75,14 +75,13 @@ export default class UIElement extends Element {
 	 * Assuming that the object being checked is an ui element, you can also check its
 	 * {@link module:engine/view/uielement~UIElement#name name}:
 	 *
-	 *		uiElement.is( 'span' ); // -> true if this is a span ui element
+	 *		uiElement.is( 'element', 'span' ); // -> true if this is a span ui element
 	 *		uiElement.is( 'uiElement', 'span' ); // -> same as above
-	 *		text.is( 'span' ); -> false
+	 *		text.is( 'element', 'span' ); -> false
 	 *
 	 * {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
 	 *
-	 * @param {String} type Type to check when `name` parameter is present.
-	 * Otherwise, it acts like the `name` parameter.
+	 * @param {String} type Type to check.
 	 * @param {String} [name] Element name.
 	 * @returns {Boolean}
 	 */
@@ -90,7 +89,6 @@ export default class UIElement extends Element {
 		if ( !name ) {
 			return type === 'uiElement' || type === 'view:uiElement' ||
 				// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
-				type === this.name || type === 'view:' + this.name ||
 				type === 'element' || type === 'view:element' ||
 				type === 'node' || type === 'view:node';
 		} else {

+ 2 - 2
packages/ckeditor5-engine/tests/controller/datacontroller.js

@@ -342,8 +342,8 @@ describe( 'DataController', () => {
 
 			data.set( 'foo' );
 
-			downcastHelpers.markerToElement( { model: 'marker', view: 'marker' } );
-			upcastHelpers.elementToMarker( { view: 'marker', model: 'marker' } );
+			downcastHelpers.markerToData( { model: 'marker' } );
+			upcastHelpers.dataToMarker( { view: 'marker' } );
 
 			model.change( writer => {
 				writer.addMarker( 'marker', { range: writer.createRangeIn( modelDocument.getRoot() ), usingOperation: true } );

+ 3 - 3
packages/ckeditor5-engine/tests/conversion/conversion.js

@@ -334,7 +334,7 @@ describe( 'Conversion', () => {
 						},
 						// Duplicates the `x-bold` from above to test if only one attribute would be converted.
 						// It should not convert to both bold & x-bold.
-						viewElement => viewElement.is( 'x-bold' ) ? { name: 'x-bold' } : null
+						viewElement => viewElement.is( 'element', 'x-bold' ) ? { name: 'x-bold' } : null
 					]
 				} );
 
@@ -426,7 +426,7 @@ describe( 'Conversion', () => {
 
 							const size = Number( match[ 1 ] );
 
-							if ( viewElement.is( 'span' ) && size > 10 ) {
+							if ( viewElement.is( 'element', 'span' ) && size > 10 ) {
 								return { name: true, style: [ 'font-size' ] };
 							}
 
@@ -447,7 +447,7 @@ describe( 'Conversion', () => {
 
 							const size = Number( match[ 1 ] );
 
-							if ( viewElement.is( 'span' ) && size < 10 ) {
+							if ( viewElement.is( 'element', 'span' ) && size < 10 ) {
 								return { name: true, style: [ 'font-size' ] };
 							}
 

+ 482 - 1
packages/ckeditor5-engine/tests/conversion/downcasthelpers.js

@@ -996,6 +996,487 @@ describe( 'DowncastHelpers', () => {
 		} );
 	} );
 
+	describe( 'markerToData()', () => {
+		let root;
+
+		beforeEach( () => {
+			root = model.document.getRoot();
+
+			model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+			downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } );
+		} );
+
+		it( 'should be chainable', () => {
+			expect( downcastHelpers.markerToData( { model: 'search' } ) ).to.equal( downcastHelpers );
+		} );
+
+		it( 'default conversion, inside text, non-collapsed, no name', () => {
+			downcastHelpers.markerToData( { model: 'search' } );
+
+			setModelData( model, '<paragraph>Fo[ob]ar</paragraph>' );
+
+			model.change( writer => {
+				writer.addMarker( 'search', { range: model.document.selection.getFirstRange(), usingOperation: false } );
+			} );
+
+			expectResult( '<p>Fo<search-start></search-start>ob<search-end></search-end>ar</p>' );
+
+			model.change( writer => {
+				writer.removeMarker( 'search' );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+		} );
+
+		it( 'default conversion, inside text, non-collapsed, name', () => {
+			downcastHelpers.markerToData( { model: 'group' } );
+
+			setModelData( model, '<paragraph>Fo[ob]ar</paragraph>' );
+
+			model.change( writer => {
+				writer.addMarker( 'group:foo:bar:baz', { range: model.document.selection.getFirstRange(), usingOperation: false } );
+			} );
+
+			expectResult( '<p>Fo<group-start name="foo:bar:baz"></group-start>ob<group-end name="foo:bar:baz"></group-end>ar</p>' );
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo:bar:baz' );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+		} );
+
+		it( 'default conversion, inside text, collapsed, no name', () => {
+			downcastHelpers.markerToData( { model: 'search' } );
+
+			setModelData( model, '<paragraph>Foo[]bar</paragraph>' );
+
+			model.change( writer => {
+				writer.addMarker( 'search', { range: model.document.selection.getFirstRange(), usingOperation: false } );
+			} );
+
+			expectResult( '<p>Foo<search-start></search-start><search-end></search-end>bar</p>' );
+
+			model.change( writer => {
+				writer.removeMarker( 'search' );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+		} );
+
+		it( 'default conversion, inside text, collapsed, multiple markers, no name', () => {
+			downcastHelpers.markerToData( { model: 'group' } );
+
+			setModelData( model, '<paragraph>Foo[]bar</paragraph>' );
+
+			model.change( writer => {
+				writer.addMarker( 'group:foo', { range: model.document.selection.getFirstRange(), usingOperation: false } );
+				writer.addMarker( 'group:abc', { range: model.document.selection.getFirstRange(), usingOperation: false } );
+			} );
+
+			expectResult(
+				'<p>' +
+					'Foo' +
+					'<group-start name="abc"></group-start><group-end name="abc"></group-end>' +
+					'<group-start name="foo"></group-start><group-end name="foo"></group-end>' +
+					'bar' +
+				'</p>'
+			);
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo' );
+				writer.removeMarker( 'group:abc' );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+		} );
+
+		it( 'default conversion, on two elements, no name', () => {
+			downcastHelpers.markerToData( { model: 'search' } );
+
+			setModelData( model, '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
+
+			model.change( writer => {
+				const range = writer.createRangeIn( root );
+				writer.addMarker( 'search', { range, usingOperation: false } );
+			} );
+
+			expectResult( '<p data-search-start-before="">Foo</p><p data-search-end-after="">Bar</p>' );
+
+			model.change( writer => {
+				writer.removeMarker( 'search' );
+			} );
+
+			expectResult( '<p>Foo</p><p>Bar</p>' );
+		} );
+
+		it( 'default conversion, on two elements, name', () => {
+			downcastHelpers.markerToData( { model: 'group' } );
+
+			setModelData( model, '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
+
+			model.change( writer => {
+				const range = writer.createRangeIn( root );
+				writer.addMarker( 'group:foo:bar:baz', { range, usingOperation: false } );
+			} );
+
+			expectResult( '<p data-group-start-before="foo:bar:baz">Foo</p><p data-group-end-after="foo:bar:baz">Bar</p>' );
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo:bar:baz' );
+			} );
+
+			expectResult( '<p>Foo</p><p>Bar</p>' );
+		} );
+
+		it( 'default conversion, on one element, name', () => {
+			downcastHelpers.markerToData( { model: 'group' } );
+
+			setModelData( model, '<paragraph>Foobar</paragraph>' );
+
+			model.change( writer => {
+				const range = writer.createRangeIn( root );
+				writer.addMarker( 'group:foo:bar:baz', { range, usingOperation: false } );
+			} );
+
+			expectResult( '<p data-group-end-after="foo:bar:baz" data-group-start-before="foo:bar:baz">Foobar</p>' );
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo:bar:baz' );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+		} );
+
+		it( 'default conversion, collapsed before element, name', () => {
+			downcastHelpers.markerToData( { model: 'group' } );
+
+			setModelData( model, '<paragraph>Foobar</paragraph>' );
+
+			model.change( writer => {
+				// Collapsed before <paragraph>.
+				const range = writer.createRange(
+					writer.createPositionFromPath( root, [ 0 ] )
+				);
+
+				writer.addMarker( 'group:foo:bar:baz', { range, usingOperation: false } );
+			} );
+
+			expectResult( '<p data-group-end-before="foo:bar:baz" data-group-start-before="foo:bar:baz">Foobar</p>' );
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo:bar:baz' );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+		} );
+
+		it( 'default conversion, collapsed after element, name', () => {
+			downcastHelpers.markerToData( { model: 'group' } );
+
+			setModelData( model, '<paragraph>Foobar</paragraph>' );
+
+			model.change( writer => {
+				// Collapsed before <paragraph>.
+				const range = writer.createRange(
+					writer.createPositionFromPath( root, [ 1 ] )
+				);
+
+				writer.addMarker( 'group:foo:bar:baz', { range, usingOperation: false } );
+			} );
+
+			expectResult( '<p data-group-end-after="foo:bar:baz" data-group-start-after="foo:bar:baz">Foobar</p>' );
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo:bar:baz' );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+		} );
+
+		it( 'default conversion, mixed, multiple markers, name', () => {
+			downcastHelpers.markerToData( { model: 'group' } );
+
+			setModelData( model, '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
+
+			model.change( writer => {
+				const range = writer.createRange(
+					writer.createPositionFromPath( root, [ 0 ] ),
+					writer.createPositionFromPath( root, [ 1, 2 ] )
+				);
+
+				writer.addMarker( 'group:foo:bar', { range, usingOperation: false } );
+				writer.addMarker( 'group:abc:xyz', { range, usingOperation: false } );
+			} );
+
+			expectResult(
+				'<p data-group-start-before="abc:xyz,foo:bar">Foo</p>' +
+				'<p>Ba<group-end name="abc:xyz"></group-end><group-end name="foo:bar"></group-end>r</p>'
+			);
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo:bar' );
+				writer.removeMarker( 'group:abc:xyz' );
+			} );
+
+			expectResult( '<p>Foo</p><p>Bar</p>' );
+		} );
+
+		it( 'default conversion, mixed #2, multiple markers, name', () => {
+			downcastHelpers.markerToData( { model: 'group' } );
+
+			setModelData( model, '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
+
+			model.change( writer => {
+				const range = writer.createRange(
+					writer.createPositionFromPath( root, [ 0, 1 ] ),
+					writer.createPositionFromPath( root, [ 2 ] )
+				);
+
+				writer.addMarker( 'group:foo:bar', { range, usingOperation: false } );
+				writer.addMarker( 'group:abc:xyz', { range, usingOperation: false } );
+			} );
+
+			expectResult(
+				'<p>F<group-start name="abc:xyz"></group-start><group-start name="foo:bar"></group-start>oo</p>' +
+				'<p data-group-end-after="abc:xyz,foo:bar">Bar</p>'
+			);
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo:bar' );
+				writer.removeMarker( 'group:abc:xyz' );
+			} );
+
+			expectResult( '<p>Foo</p><p>Bar</p>' );
+		} );
+
+		it( 'default conversion, mixed #3, multiple markers, name', () => {
+			downcastHelpers.markerToData( { model: 'group' } );
+
+			setModelData( model, '<paragraph>Foo</paragraph>' );
+
+			model.change( writer => {
+				const range = writer.createRange(
+					writer.createPositionFromPath( root, [ 0 ] ),
+					writer.createPositionFromPath( root, [ 0, 2 ] )
+				);
+
+				writer.addMarker( 'group:foo:bar', { range, usingOperation: false } );
+				writer.addMarker( 'group:abc:xyz', { range, usingOperation: false } );
+			} );
+
+			expectResult(
+				'<p data-group-start-before="abc:xyz,foo:bar">' +
+					'Fo<group-end name="abc:xyz"></group-end><group-end name="foo:bar"></group-end>o' +
+				'</p>'
+			);
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo:bar' );
+				writer.removeMarker( 'group:abc:xyz' );
+			} );
+
+			expectResult( '<p>Foo</p>' );
+		} );
+
+		it( 'default conversion, mixed #4, multiple markers, name', () => {
+			downcastHelpers.markerToData( { model: 'group' } );
+
+			setModelData( model, '<paragraph>Foo</paragraph>' );
+
+			model.change( writer => {
+				const range = writer.createRange(
+					writer.createPositionFromPath( root, [ 0, 2 ] ),
+					writer.createPositionFromPath( root, [ 1 ] )
+				);
+
+				writer.addMarker( 'group:foo:bar', { range, usingOperation: false } );
+				writer.addMarker( 'group:abc:xyz', { range, usingOperation: false } );
+			} );
+
+			expectResult(
+				'<p data-group-end-after="abc:xyz,foo:bar">' +
+					'Fo<group-start name="abc:xyz"></group-start><group-start name="foo:bar"></group-start>o' +
+				'</p>'
+			);
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo:bar' );
+				writer.removeMarker( 'group:abc:xyz' );
+			} );
+
+			expectResult( '<p>Foo</p>' );
+		} );
+
+		it( 'conversion callback, mixed, multiple markers, name', () => {
+			const customData = {
+				foo: 'bar',
+				abc: 'xyz'
+			};
+
+			downcastHelpers.markerToData( {
+				model: 'group',
+				view: markerName => {
+					const namePart = markerName.split( ':' )[ 1 ];
+
+					return {
+						group: 'g',
+						name: namePart + '_' + customData[ namePart ]
+					};
+				}
+			} );
+
+			setModelData( model, '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
+
+			model.change( writer => {
+				const range = writer.createRange(
+					writer.createPositionFromPath( root, [ 0 ] ),
+					writer.createPositionFromPath( root, [ 1, 2 ] )
+				);
+
+				writer.addMarker( 'group:foo', { range, usingOperation: false } );
+				writer.addMarker( 'group:abc', { range, usingOperation: false } );
+			} );
+
+			expectResult(
+				'<p data-g-start-before="abc_xyz,foo_bar">Foo</p>' +
+				'<p>Ba<g-end name="abc_xyz"></g-end><g-end name="foo_bar"></g-end>r</p>'
+			);
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo' );
+				writer.removeMarker( 'group:abc' );
+			} );
+
+			expectResult( '<p>Foo</p><p>Bar</p>' );
+		} );
+
+		it( 'conversion callback, mixed #2, multiple markers, name', () => {
+			const customData = {
+				foo: 'bar',
+				abc: 'xyz'
+			};
+
+			downcastHelpers.markerToData( {
+				model: 'group',
+				view: markerName => {
+					const namePart = markerName.split( ':' )[ 1 ];
+
+					return {
+						group: 'g',
+						name: namePart + '_' + customData[ namePart ]
+					};
+				}
+			} );
+
+			setModelData( model, '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
+
+			model.change( writer => {
+				const range = writer.createRange(
+					writer.createPositionFromPath( root, [ 0, 1 ] ),
+					writer.createPositionFromPath( root, [ 2 ] )
+				);
+
+				writer.addMarker( 'group:foo', { range, usingOperation: false } );
+				writer.addMarker( 'group:abc', { range, usingOperation: false } );
+			} );
+
+			expectResult(
+				'<p>F<g-start name="abc_xyz"></g-start><g-start name="foo_bar"></g-start>oo</p>' +
+				'<p data-g-end-after="abc_xyz,foo_bar">Bar</p>'
+			);
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo' );
+				writer.removeMarker( 'group:abc' );
+			} );
+
+			expectResult( '<p>Foo</p><p>Bar</p>' );
+		} );
+
+		it( 'can be overwritten using converterPriority', () => {
+			downcastHelpers.markerToData( {
+				model: 'group'
+			} );
+
+			downcastHelpers.markerToData( {
+				model: 'group',
+				view: markerName => {
+					const name = markerName.split( ':' )[ 1 ];
+
+					return {
+						group: 'g',
+						name
+					};
+				},
+				converterPriority: 'high'
+			} );
+
+			setModelData( model, '<paragraph>F[ooba]r</paragraph>' );
+
+			model.change( writer => {
+				writer.addMarker( 'group:foo', { range: model.document.selection.getFirstRange(), usingOperation: false } );
+			} );
+
+			expectResult(
+				'<p>F<g-start name="foo"></g-start>ooba<g-end name="foo"></g-end>r</p>'
+			);
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo' );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+		} );
+
+		it( 'can be overwritten by custom callback', () => {
+			downcastHelpers.markerToData( {
+				model: 'group'
+			} );
+
+			downcastHelpers.add( dispatcher => {
+				dispatcher.on( 'addMarker:group', ( evt, data, conversionApi ) => {
+					conversionApi.consumable.consume( data.markerRange, evt.name );
+				}, { priority: 'high' } );
+			} );
+
+			setModelData( model, '<paragraph>Foo[]bar</paragraph>' );
+
+			model.change( writer => {
+				writer.addMarker( 'group:foo', { range: model.document.selection.getFirstRange(), usingOperation: false } );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo' );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+		} );
+
+		it( 'should not perform conversion if the callback returned falsy value', () => {
+			downcastHelpers.markerToData( {
+				model: 'group',
+				view: () => false
+			} );
+
+			setModelData( model, '<paragraph>F[ooba]r</paragraph>' );
+
+			model.change( writer => {
+				writer.addMarker( 'group:foo', { range: model.document.selection.getFirstRange(), usingOperation: false } );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+
+			model.change( writer => {
+				writer.removeMarker( 'group:foo' );
+			} );
+
+			expectResult( '<p>Foobar</p>' );
+		} );
+	} );
+
 	describe( 'markerToHighlight()', () => {
 		it( 'should be chainable', () => {
 			expect( downcastHelpers.markerToHighlight( { model: 'comment', view: { classes: 'comment' } } ) ).to.equal( downcastHelpers );
@@ -2277,7 +2758,7 @@ describe( 'downcast selection converters', () => {
 				for ( const range of selection.getRanges() ) {
 					const node = range.start.parent;
 
-					if ( !!node && node.is( 'td' ) ) {
+					if ( !!node && node.is( 'element', 'td' ) ) {
 						conversionApi.consumable.consume( selection, 'selection' );
 
 						const viewNode = conversionApi.mapper.toViewElement( node );

+ 196 - 11
packages/ckeditor5-engine/tests/conversion/upcasthelpers.js

@@ -24,12 +24,14 @@ import UpcastHelpers, { convertToModelFragment, convertText, convertSelectionCha
 import { getData as modelGetData, setData as modelSetData, stringify } from '../../src/dev-utils/model';
 import View from '../../src/view/view';
 import createViewRoot from '../view/_utils/createroot';
-import { setData as viewSetData } from '../../src/dev-utils/view';
+import { setData as viewSetData, parse as viewParse } from '../../src/dev-utils/view';
 import Mapper from '../../src/conversion/mapper';
 import ViewSelection from '../../src/view/selection';
 import ViewRange from '../../src/view/range';
 import { StylesProcessor } from '../../src/view/stylesmap';
 
+/* globals console */
+
 describe( 'UpcastHelpers', () => {
 	let upcastDispatcher, model, schema, upcastHelpers, viewDocument;
 
@@ -56,7 +58,7 @@ describe( 'UpcastHelpers', () => {
 		upcastHelpers = new UpcastHelpers( [ upcastDispatcher ] );
 	} );
 
-	describe( '.elementToElement()', () => {
+	describe( 'elementToElement()', () => {
 		it( 'should be chainable', () => {
 			expect( upcastHelpers.elementToElement( { view: 'p', model: 'paragraph' } ) ).to.equal( upcastHelpers );
 		} );
@@ -168,7 +170,7 @@ describe( 'UpcastHelpers', () => {
 		} );
 	} );
 
-	describe( '.elementToAttribute()', () => {
+	describe( 'elementToAttribute()', () => {
 		it( 'should be chainable', () => {
 			expect( upcastHelpers.elementToAttribute( { view: 'strong', model: 'bold' } ) ).to.equal( upcastHelpers );
 		} );
@@ -369,7 +371,7 @@ describe( 'UpcastHelpers', () => {
 		} );
 	} );
 
-	describe( '.attributeToAttribute()', () => {
+	describe( 'attributeToAttribute()', () => {
 		beforeEach( () => {
 			upcastHelpers.elementToElement( { view: 'img', model: 'image' } );
 
@@ -584,7 +586,17 @@ describe( 'UpcastHelpers', () => {
 		} );
 	} );
 
-	describe( '.elementToMarker()', () => {
+	describe( 'elementToMarker()', () => {
+		beforeEach( () => {
+			// Silence warning about deprecated method.
+			// This whole suite will be removed when the deprecated method is removed.
+			sinon.stub( console, 'warn' );
+		} );
+
+		afterEach( () => {
+			console.warn.restore();
+		} );
+
 		it( 'should be chainable', () => {
 			expect( upcastHelpers.elementToMarker( { view: 'marker-search', model: 'search' } ) ).to.equal( upcastHelpers );
 		} );
@@ -682,16 +694,189 @@ describe( 'UpcastHelpers', () => {
 		} );
 	} );
 
-	function expectResult( viewToConvert, modelString, marker ) {
+	describe( 'dataToMarker()', () => {
+		beforeEach( () => {
+			upcastHelpers.elementToElement( { view: 'p', model: 'paragraph' } );
+		} );
+
+		it( 'should be chainable', () => {
+			expect( upcastHelpers.dataToMarker( { view: 'search' } ) ).to.equal( upcastHelpers );
+		} );
+
+		it( 'default conversion, inside text, non-collapsed, no name', () => {
+			upcastHelpers.dataToMarker( { view: 'search' } );
+
+			expectResult(
+				viewParse( '<p>Fo<search-start></search-start>ob<search-end></search-end>ar</p>' ),
+				'<paragraph>Foobar</paragraph>',
+				{ name: 'search', start: [ 0, 2 ], end: [ 0, 4 ] }
+			);
+		} );
+
+		it( 'default conversion, inside text, non-collapsed, name', () => {
+			upcastHelpers.dataToMarker( { view: 'group' } );
+
+			expectResult(
+				viewParse( '<p>Fo<group-start name="foo:bar:baz"></group-start>ob<group-end name="foo:bar:baz"></group-end>ar</p>' ),
+				'<paragraph>Foobar</paragraph>',
+				{ name: 'group:foo:bar:baz', start: [ 0, 2 ], end: [ 0, 4 ] }
+			);
+		} );
+
+		it( 'default conversion, inside text, collapsed, no name', () => {
+			upcastHelpers.dataToMarker( { view: 'search' } );
+
+			expectResult(
+				viewParse( '<p>Foo<search-start></search-start><search-end></search-end>bar</p>' ),
+				'<paragraph>Foobar</paragraph>',
+				{ name: 'search', start: [ 0, 3 ], end: [ 0, 3 ] }
+			);
+		} );
+
+		it( 'default conversion, inside text, collapsed, multiple markers, no name', () => {
+			upcastHelpers.dataToMarker( { view: 'group' } );
+
+			expectResult(
+				viewParse(
+					'<p>' +
+						'Foo' +
+						'<group-start name="abc"></group-start><group-end name="abc"></group-end>' +
+						'<group-start name="foo"></group-start><group-end name="foo"></group-end>' +
+						'bar' +
+					'</p>'
+				),
+				'<paragraph>Foobar</paragraph>',
+				[
+					{ name: 'group:abc', start: [ 0, 3 ], end: [ 0, 3 ] },
+					{ name: 'group:foo', start: [ 0, 3 ], end: [ 0, 3 ] }
+				]
+			);
+		} );
+
+		it( 'default conversion, on two elements, no name', () => {
+			upcastHelpers.dataToMarker( { view: 'search' } );
+
+			expectResult(
+				viewParse( '<p data-search-start-before="">Foo</p><p data-search-end-after="">Bar</p>' ),
+				'<paragraph>Foo</paragraph><paragraph>Bar</paragraph>',
+				{ name: 'search', start: [ 0 ], end: [ 2 ] }
+			);
+		} );
+
+		it( 'default conversion, on two elements, name', () => {
+			upcastHelpers.dataToMarker( { view: 'group' } );
+
+			expectResult(
+				viewParse( '<p data-group-start-before="foo:bar:baz">Foo</p><p data-group-end-after="foo:bar:baz">Bar</p>' ),
+				'<paragraph>Foo</paragraph><paragraph>Bar</paragraph>',
+				{ name: 'group:foo:bar:baz', start: [ 0 ], end: [ 2 ] }
+			);
+		} );
+
+		it( 'default conversion, on one element, name', () => {
+			upcastHelpers.dataToMarker( { view: 'group' } );
+
+			expectResult(
+				viewParse( '<p data-group-end-after="foo:bar:baz" data-group-start-before="foo:bar:baz">Foobar</p>' ),
+				'<paragraph>Foobar</paragraph>',
+				{ name: 'group:foo:bar:baz', start: [ 0 ], end: [ 1 ] }
+			);
+		} );
+
+		it( 'default conversion, collapsed before element, name', () => {
+			upcastHelpers.dataToMarker( { view: 'group' } );
+
+			expectResult(
+				viewParse( '<p data-group-end-before="foo:bar:baz" data-group-start-before="foo:bar:baz">Foobar</p>' ),
+				'<paragraph>Foobar</paragraph>',
+				{ name: 'group:foo:bar:baz', start: [ 0 ], end: [ 0 ] }
+			);
+		} );
+
+		it( 'default conversion, collapsed after element, name', () => {
+			upcastHelpers.dataToMarker( { view: 'group' } );
+
+			expectResult(
+				viewParse( '<p data-group-end-after="foo:bar:baz" data-group-start-after="foo:bar:baz">Foobar</p>' ),
+				'<paragraph>Foobar</paragraph>',
+				{ name: 'group:foo:bar:baz', start: [ 1 ], end: [ 1 ] }
+			);
+		} );
+
+		it( 'default conversion, mixed, multiple markers, name', () => {
+			upcastHelpers.dataToMarker( { view: 'group' } );
+
+			expectResult(
+				viewParse(
+					'<p data-group-start-before="abc:xyz,foo:bar">Foo</p>' +
+					'<p>Ba<group-end name="abc:xyz"></group-end><group-end name="foo:bar"></group-end>r</p>'
+				),
+				'<paragraph>Foo</paragraph><paragraph>Bar</paragraph>',
+				[
+					{ name: 'group:foo:bar', start: [ 0 ], end: [ 1, 2 ] },
+					{ name: 'group:abc:xyz', start: [ 0 ], end: [ 1, 2 ] }
+				]
+			);
+		} );
+
+		it( 'default conversion, mixed #2, multiple markers, name', () => {
+			upcastHelpers.dataToMarker( { view: 'group' } );
+
+			expectResult(
+				viewParse(
+					'<p>F<group-start name="abc:xyz"></group-start><group-start name="foo:bar"></group-start>oo</p>' +
+					'<p data-group-end-after="abc:xyz,foo:bar">Bar</p>'
+				),
+				'<paragraph>Foo</paragraph><paragraph>Bar</paragraph>',
+				[
+					{ name: 'group:foo:bar', start: [ 0, 1 ], end: [ 2 ] },
+					{ name: 'group:abc:xyz', start: [ 0, 1 ], end: [ 2 ] }
+				]
+			);
+		} );
+
+		it( 'conversion callback, mixed, multiple markers, name', () => {
+			upcastHelpers.dataToMarker( { view: 'g', model: name => 'group:' + name.split( '_' )[ 0 ] } );
+
+			expectResult(
+				viewParse(
+					'<p data-g-start-before="abc_xyz,foo_bar">Foo</p>' +
+					'<p>Ba<g-end name="abc_xyz"></g-end><g-end name="foo_bar"></g-end>r</p>'
+				),
+				'<paragraph>Foo</paragraph><paragraph>Bar</paragraph>',
+				[
+					{ name: 'group:foo', start: [ 0 ], end: [ 1, 2 ] },
+					{ name: 'group:abc', start: [ 0 ], end: [ 1, 2 ] }
+				]
+			);
+		} );
+
+		it( 'can be overwritten using converterPriority', () => {
+			upcastHelpers.dataToMarker( { view: 'group' } );
+			upcastHelpers.dataToMarker( { view: 'group', model: name => 'g:' + name, converterPriority: 'high' } );
+
+			expectResult(
+				viewParse( '<p>Foo<group-start name="foo"></group-start><group-end name="foo"></group-end>bar</p>' ),
+				'<paragraph>Foobar</paragraph>',
+				{ name: 'g:foo', start: [ 0, 3 ], end: [ 0, 3 ] }
+			);
+		} );
+	} );
+
+	function expectResult( viewToConvert, modelString, markers ) {
 		const conversionResult = model.change( writer => upcastDispatcher.convert( viewToConvert, writer ) );
 
-		if ( marker ) {
-			expect( conversionResult.markers.has( marker.name ) ).to.be.true;
+		if ( markers ) {
+			markers = Array.isArray( markers ) ? markers : [ markers ];
+
+			for ( const marker of markers ) {
+				expect( conversionResult.markers.has( marker.name ) ).to.be.true;
 
-			const convertedMarker = conversionResult.markers.get( marker.name );
+				const convertedMarker = conversionResult.markers.get( marker.name );
 
-			expect( convertedMarker.start.path ).to.deep.equal( marker.start );
-			expect( convertedMarker.end.path ).to.deep.equal( marker.end );
+				expect( convertedMarker.start.path ).to.deep.equal( marker.start );
+				expect( convertedMarker.end.path ).to.deep.equal( marker.end );
+			}
 		}
 
 		expect( stringify( conversionResult ) ).to.equal( modelString );

+ 2 - 2
packages/ckeditor5-engine/tests/model/documentfragment.js

@@ -75,8 +75,8 @@ describe( 'DocumentFragment', () => {
 
 		it( 'should return false for other values', () => {
 			expect( frag.is( 'node' ) ).to.be.false;
-			expect( frag.is( 'text' ) ).to.be.false;
-			expect( frag.is( 'textProxy' ) ).to.be.false;
+			expect( frag.is( '$text' ) ).to.be.false;
+			expect( frag.is( '$textProxy' ) ).to.be.false;
 			expect( frag.is( 'element' ) ).to.be.false;
 			expect( frag.is( 'rootElement' ) ).to.be.false;
 			expect( frag.is( 'view:documentFragment' ) ).to.be.false;

+ 2 - 2
packages/ckeditor5-engine/tests/model/documentselection.js

@@ -719,8 +719,8 @@ describe( 'DocumentSelection', () => {
 		it( 'should return false for other values', () => {
 			expect( selection.is( 'node' ) ).to.be.false;
 			expect( selection.is( 'model:node' ) ).to.be.false;
-			expect( selection.is( 'text' ) ).to.be.false;
-			expect( selection.is( 'textProxy' ) ).to.be.false;
+			expect( selection.is( '$text' ) ).to.be.false;
+			expect( selection.is( '$textProxy' ) ).to.be.false;
 			expect( selection.is( 'element' ) ).to.be.false;
 			expect( selection.is( 'element', 'paragraph' ) ).to.be.false;
 			expect( selection.is( 'rootElement' ) ).to.be.false;

+ 7 - 6
packages/ckeditor5-engine/tests/model/element.js

@@ -52,24 +52,25 @@ describe( 'Element', () => {
 			expect( element.is( 'model:element' ) ).to.be.true;
 			expect( element.is( 'element', 'paragraph' ) ).to.be.true;
 			expect( element.is( 'model:element', 'paragraph' ) ).to.be.true;
-			expect( element.is( 'paragraph' ) ).to.be.true;
-			expect( element.is( 'model:paragraph' ) ).to.be.true;
+			expect( element.is( 'element', 'paragraph' ) ).to.be.true;
 		} );
 
 		it( 'should return false for other accept values', () => {
 			expect( element.is( 'element', 'image' ) ).to.be.false;
 			expect( element.is( 'model:element', 'image' ) ).to.be.false;
-			expect( element.is( 'image' ) ).to.be.false;
+			expect( element.is( 'element', 'image' ) ).to.be.false;
 			expect( element.is( 'model:image' ) ).to.be.false;
-			expect( element.is( 'text' ) ).to.be.false;
-			expect( element.is( 'model:text' ) ).to.be.false;
-			expect( element.is( 'textProxy' ) ).to.be.false;
+			expect( element.is( '$text' ) ).to.be.false;
+			expect( element.is( 'model:$text' ) ).to.be.false;
+			expect( element.is( '$textProxy' ) ).to.be.false;
 			expect( element.is( 'documentFragment' ) ).to.be.false;
 			expect( element.is( 'rootElement' ) ).to.be.false;
 			expect( element.is( 'model:rootElement' ) ).to.be.false;
 			expect( element.is( 'view:node' ) ).to.be.false;
 			expect( element.is( 'view:element' ) ).to.be.false;
 			expect( element.is( 'view:element' ) ).to.be.false;
+			expect( element.is( 'node', 'paragraph' ) ).to.be.false;
+			expect( element.is( 'model:node', 'paragraph' ) ).to.be.false;
 		} );
 	} );
 

+ 1 - 1
packages/ckeditor5-engine/tests/model/liveposition.js

@@ -59,7 +59,7 @@ describe( 'LivePosition', () =>
 		it( 'should return false for incorrect values', () => {
 			expect( live.is( 'model' ) ).to.be.false;
 			expect( live.is( 'model:node' ) ).to.be.false;
-			expect( live.is( 'text' ) ).to.be.false;
+			expect( live.is( '$text' ) ).to.be.false;
 			expect( live.is( 'element', 'paragraph' ) ).to.be.false;
 		} );
 	} );

+ 1 - 1
packages/ckeditor5-engine/tests/model/liverange.js

@@ -201,7 +201,7 @@ describe( 'LiveRange', () => {
 		it( 'should return false for incorrect values', () => {
 			expect( live.is( 'model' ) ).to.be.false;
 			expect( live.is( 'model:node' ) ).to.be.false;
-			expect( live.is( 'text' ) ).to.be.false;
+			expect( live.is( '$text' ) ).to.be.false;
 			expect( live.is( 'element', 'paragraph' ) ).to.be.false;
 		} );
 	} );

+ 7 - 1
packages/ckeditor5-engine/tests/model/markercollection.js

@@ -123,6 +123,12 @@ describe( 'MarkerCollection', () => {
 
 			expect( marker.getRange().isEqual( range2 ) ).to.be.true;
 		} );
+
+		it( 'should throw if marker name with "," is added', () => {
+			expectToThrowCKEditorError( () => {
+				markers._set( 'foo,bar', range );
+			}, /^markercollection-incorrect-marker-name:/, markers );
+		} );
 	} );
 
 	describe( 'has', () => {
@@ -427,7 +433,7 @@ describe( 'Marker', () => {
 		it( 'should return false for incorrect values', () => {
 			expect( marker.is( 'model' ) ).to.be.false;
 			expect( marker.is( 'model:node' ) ).to.be.false;
-			expect( marker.is( 'text' ) ).to.be.false;
+			expect( marker.is( '$text' ) ).to.be.false;
 			expect( marker.is( 'element', 'paragraph' ) ).to.be.false;
 		} );
 	} );

+ 2 - 2
packages/ckeditor5-engine/tests/model/node.js

@@ -153,8 +153,8 @@ describe( 'Node', () => {
 
 		it( 'should return false for incorrect values', () => {
 			expect( node.is( 'model' ) ).to.be.false;
-			expect( node.is( 'model:text' ) ).to.be.false;
-			expect( node.is( 'text' ) ).to.be.false;
+			expect( node.is( 'model:$text' ) ).to.be.false;
+			expect( node.is( '$text' ) ).to.be.false;
 			expect( node.is( 'element', 'paragraph' ) ).to.be.false;
 		} );
 	} );

+ 1 - 1
packages/ckeditor5-engine/tests/model/position.js

@@ -148,7 +148,7 @@ describe( 'Position', () => {
 		it( 'should return false for incorrect values', () => {
 			expect( position.is( 'model' ) ).to.be.false;
 			expect( position.is( 'model:node' ) ).to.be.false;
-			expect( position.is( 'text' ) ).to.be.false;
+			expect( position.is( '$text' ) ).to.be.false;
 			expect( position.is( 'element', 'paragraph' ) ).to.be.false;
 		} );
 	} );

+ 1 - 1
packages/ckeditor5-engine/tests/model/range.js

@@ -59,7 +59,7 @@ describe( 'Range', () => {
 		it( 'should return false for incorrect values', () => {
 			expect( range.is( 'model' ) ).to.be.false;
 			expect( range.is( 'model:node' ) ).to.be.false;
-			expect( range.is( 'text' ) ).to.be.false;
+			expect( range.is( '$text' ) ).to.be.false;
 			expect( range.is( 'element', 'paragraph' ) ).to.be.false;
 		} );
 	} );

+ 6 - 4
packages/ckeditor5-engine/tests/model/rootelement.js

@@ -37,8 +37,6 @@ describe( 'RootElement', () => {
 			expect( root.is( 'model:element', '$root' ) ).to.be.true;
 			expect( root.is( 'element' ) ).to.be.true;
 			expect( root.is( 'model:element' ) ).to.be.true;
-			expect( root.is( '$root' ) ).to.be.true;
-			expect( root.is( 'model:$root' ) ).to.be.true;
 			expect( root.is( 'rootElement', '$root' ) ).to.be.true;
 			expect( root.is( 'model:rootElement', '$root' ) ).to.be.true;
 			expect( root.is( 'rootElement' ) ).to.be.true;
@@ -53,10 +51,14 @@ describe( 'RootElement', () => {
 			expect( root.is( 'rootElement', '$graveyard' ) ).to.be.false;
 			expect( root.is( 'model:rootElement', '$graveyard' ) ).to.be.false;
 			expect( root.is( '$graveyard' ) ).to.be.false;
-			expect( root.is( 'text' ) ).to.be.false;
-			expect( root.is( 'textProxy' ) ).to.be.false;
+			expect( root.is( '$text' ) ).to.be.false;
+			expect( root.is( '$textProxy' ) ).to.be.false;
 			expect( root.is( 'documentFragment' ) ).to.be.false;
 			expect( root.is( 'view:element' ) ).to.be.false;
+			expect( root.is( '$root' ) ).to.be.false;
+			expect( root.is( 'model:$root' ) ).to.be.false;
+			expect( root.is( 'node', '$root' ) ).to.be.false;
+			expect( root.is( 'model:node', '$root' ) ).to.be.false;
 		} );
 	} );
 } );

+ 4 - 4
packages/ckeditor5-engine/tests/model/selection.js

@@ -142,7 +142,7 @@ describe( 'Selection', () => {
 		it( 'should return false for incorrect values', () => {
 			expect( selection.is( 'model' ) ).to.be.false;
 			expect( selection.is( 'model:node' ) ).to.be.false;
-			expect( selection.is( 'text' ) ).to.be.false;
+			expect( selection.is( '$text' ) ).to.be.false;
 			expect( selection.is( 'element', 'paragraph' ) ).to.be.false;
 		} );
 	} );
@@ -834,8 +834,8 @@ describe( 'Selection', () => {
 		it( 'should return false for other values', () => {
 			expect( selection.is( 'documentSelection' ) ).to.be.false;
 			expect( selection.is( 'node' ) ).to.be.false;
-			expect( selection.is( 'text' ) ).to.be.false;
-			expect( selection.is( 'textProxy' ) ).to.be.false;
+			expect( selection.is( '$text' ) ).to.be.false;
+			expect( selection.is( '$textProxy' ) ).to.be.false;
 			expect( selection.is( 'element' ) ).to.be.false;
 			expect( selection.is( 'rootElement' ) ).to.be.false;
 		} );
@@ -1376,7 +1376,7 @@ describe( 'Selection', () => {
 			let innerText = '';
 
 			for ( const child of el.getChildren() ) {
-				if ( child.is( 'text' ) ) {
+				if ( child.is( '$text' ) ) {
 					innerText += child.data;
 				}
 			}

+ 3 - 1
packages/ckeditor5-engine/tests/model/text.js

@@ -42,12 +42,14 @@ describe( 'Text', () => {
 		it( 'should return true for node, text', () => {
 			expect( text.is( 'node' ) ).to.be.true;
 			expect( text.is( 'model:node' ) ).to.be.true;
+			expect( text.is( '$text' ) ).to.be.true;
+			expect( text.is( 'model:$text' ) ).to.be.true;
 			expect( text.is( 'text' ) ).to.be.true;
 			expect( text.is( 'model:text' ) ).to.be.true;
 		} );
 
 		it( 'should return false for other accept values', () => {
-			expect( text.is( 'textProxy' ) ).to.be.false;
+			expect( text.is( '$textProxy' ) ).to.be.false;
 			expect( text.is( 'element' ) ).to.be.false;
 			expect( text.is( 'model:element' ) ).to.be.false;
 			expect( text.is( 'rootElement' ) ).to.be.false;

+ 4 - 2
packages/ckeditor5-engine/tests/model/textproxy.js

@@ -98,7 +98,9 @@ describe( 'TextProxy', () => {
 	} );
 
 	describe( 'is()', () => {
-		it( 'should return true for textProxy', () => {
+		it( 'should return true for $textProxy', () => {
+			expect( textProxy.is( '$textProxy' ) ).to.be.true;
+			expect( textProxy.is( 'model:$textProxy' ) ).to.be.true;
 			expect( textProxy.is( 'textProxy' ) ).to.be.true;
 			expect( textProxy.is( 'model:textProxy' ) ).to.be.true;
 		} );
@@ -106,7 +108,7 @@ describe( 'TextProxy', () => {
 		it( 'should return false for other accept values', () => {
 			expect( textProxy.is( 'node' ) ).to.be.false;
 			expect( textProxy.is( 'model:node' ) ).to.be.false;
-			expect( textProxy.is( 'text' ) ).to.be.false;
+			expect( textProxy.is( '$text' ) ).to.be.false;
 			expect( textProxy.is( 'element' ) ).to.be.false;
 			expect( textProxy.is( 'model:element', 'image' ) ).to.be.false;
 			expect( textProxy.is( 'documentFragment' ) ).to.be.false;

+ 5 - 5
packages/ckeditor5-engine/tests/view/attributeelement.js

@@ -44,8 +44,6 @@ describe( 'AttributeElement', () => {
 			expect( el.is( 'view:element' ) ).to.be.true;
 			expect( el.is( 'element', 'span' ) ).to.be.true;
 			expect( el.is( 'view:element', 'span' ) ).to.be.true;
-			expect( el.is( 'span' ) ).to.be.true;
-			expect( el.is( 'view:span' ) ).to.be.true;
 		} );
 
 		it( 'should return false for other accept values', () => {
@@ -53,15 +51,17 @@ describe( 'AttributeElement', () => {
 			expect( el.is( 'view:attributeElement', 'p' ) ).to.be.false;
 			expect( el.is( 'element', 'p' ) ).to.be.false;
 			expect( el.is( 'view:element', 'p' ) ).to.be.false;
-			expect( el.is( 'p' ) ).to.be.false;
+			expect( el.is( 'element', 'p' ) ).to.be.false;
 			expect( el.is( 'view:p' ) ).to.be.false;
-			expect( el.is( 'text' ) ).to.be.false;
-			expect( el.is( 'textProxy' ) ).to.be.false;
+			expect( el.is( '$text' ) ).to.be.false;
+			expect( el.is( '$textProxy' ) ).to.be.false;
 			expect( el.is( 'containerElement' ) ).to.be.false;
 			expect( el.is( 'uiElement' ) ).to.be.false;
 			expect( el.is( 'emptyElement' ) ).to.be.false;
 			expect( el.is( 'rootElement' ) ).to.be.false;
 			expect( el.is( 'documentFragment' ) ).to.be.false;
+			expect( el.is( 'node', 'span' ) ).to.be.false;
+			expect( el.is( 'view:node', 'span' ) ).to.be.false;
 		} );
 	} );
 

+ 5 - 5
packages/ckeditor5-engine/tests/view/containerelement.js

@@ -42,8 +42,6 @@ describe( 'ContainerElement', () => {
 			expect( el.is( 'view:element' ) ).to.be.true;
 			expect( el.is( 'element', 'p' ) ).to.be.true;
 			expect( el.is( 'view:element', 'p' ) ).to.be.true;
-			expect( el.is( 'p' ) ).to.be.true;
-			expect( el.is( 'view:p' ) ).to.be.true;
 		} );
 
 		it( 'should return false for other accept values', () => {
@@ -51,15 +49,17 @@ describe( 'ContainerElement', () => {
 			expect( el.is( 'view:containerElement', 'span' ) ).to.be.false;
 			expect( el.is( 'element', 'span' ) ).to.be.false;
 			expect( el.is( 'view:element', 'span' ) ).to.be.false;
-			expect( el.is( 'span' ) ).to.be.false;
+			expect( el.is( 'element', 'span' ) ).to.be.false;
 			expect( el.is( 'view:span' ) ).to.be.false;
-			expect( el.is( 'text' ) ).to.be.false;
-			expect( el.is( 'textProxy' ) ).to.be.false;
+			expect( el.is( '$text' ) ).to.be.false;
+			expect( el.is( '$textProxy' ) ).to.be.false;
 			expect( el.is( 'attributeElement' ) ).to.be.false;
 			expect( el.is( 'uiElement' ) ).to.be.false;
 			expect( el.is( 'emptyElement' ) ).to.be.false;
 			expect( el.is( 'rootElement' ) ).to.be.false;
 			expect( el.is( 'documentFragment' ) ).to.be.false;
+			expect( el.is( 'node', 'p' ) ).to.be.false;
+			expect( el.is( 'view:node', 'p' ) ).to.be.false;
 		} );
 	} );
 

+ 2 - 2
packages/ckeditor5-engine/tests/view/documentfragment.js

@@ -94,8 +94,8 @@ describe( 'DocumentFragment', () => {
 		it( 'should return false for other accept values', () => {
 			expect( frag.is( 'node' ) ).to.be.false;
 			expect( frag.is( 'view:node' ) ).to.be.false;
-			expect( frag.is( 'text' ) ).to.be.false;
-			expect( frag.is( 'textProxy' ) ).to.be.false;
+			expect( frag.is( '$text' ) ).to.be.false;
+			expect( frag.is( '$textProxy' ) ).to.be.false;
 			expect( frag.is( 'element' ) ).to.be.false;
 			expect( frag.is( 'view:element' ) ).to.be.false;
 			expect( frag.is( 'containerElement' ) ).to.be.false;

+ 3 - 3
packages/ckeditor5-engine/tests/view/documentselection.js

@@ -741,9 +741,9 @@ describe( 'DocumentSelection', () => {
 		it( 'should return false for other values', () => {
 			expect( documentSelection.is( 'node' ) ).to.be.false;
 			expect( documentSelection.is( 'view:node' ) ).to.be.false;
-			expect( documentSelection.is( 'text' ) ).to.be.false;
-			expect( documentSelection.is( 'view:text' ) ).to.be.false;
-			expect( documentSelection.is( 'textProxy' ) ).to.be.false;
+			expect( documentSelection.is( '$text' ) ).to.be.false;
+			expect( documentSelection.is( 'view:$text' ) ).to.be.false;
+			expect( documentSelection.is( '$textProxy' ) ).to.be.false;
 			expect( documentSelection.is( 'element' ) ).to.be.false;
 			expect( documentSelection.is( 'rootElement' ) ).to.be.false;
 			expect( documentSelection.is( 'model:selection' ) ).to.be.false;

+ 2 - 2
packages/ckeditor5-engine/tests/view/downcastwriter/writer.js

@@ -61,7 +61,7 @@ describe( 'DowncastWriter', () => {
 		it( 'should create Text instance', () => {
 			const text = writer.createText( 'foo bar' );
 
-			expect( text.is( 'text' ) ).to.be.true;
+			expect( text.is( '$text' ) ).to.be.true;
 			expect( text.data ).to.equal( 'foo bar' );
 		} );
 	} );
@@ -348,7 +348,7 @@ describe( 'DowncastWriter', () => {
 			);
 
 			// Find all spans.
-			const allSpans = Array.from( ViewRange._createIn( root ).getItems() ).filter( element => element.is( 'span' ) );
+			const allSpans = Array.from( ViewRange._createIn( root ).getItems() ).filter( element => element.is( 'element', 'span' ) );
 
 			// For each of the spans created above...
 			for ( const oneOfAllSpans of allSpans ) {

+ 4 - 5
packages/ckeditor5-engine/tests/view/editableelement.js

@@ -31,8 +31,7 @@ describe( 'EditableElement', () => {
 			expect( el.is( 'view:element' ) ).to.be.true;
 			expect( el.is( 'element', 'div' ) ).to.be.true;
 			expect( el.is( 'view:element', 'div' ) ).to.be.true;
-			expect( el.is( 'div' ) ).to.be.true;
-			expect( el.is( 'view:div' ) ).to.be.true;
+			expect( el.is( 'element', 'div' ) ).to.be.true;
 		} );
 
 		it( 'should return false for other accept values', () => {
@@ -42,10 +41,10 @@ describe( 'EditableElement', () => {
 			expect( el.is( 'view:containerElement', 'p' ) ).to.be.false;
 			expect( el.is( 'element', 'p' ) ).to.be.false;
 			expect( el.is( 'view:element', 'p' ) ).to.be.false;
-			expect( el.is( 'p' ) ).to.be.false;
+			expect( el.is( 'element', 'p' ) ).to.be.false;
 			expect( el.is( 'view:p' ) ).to.be.false;
-			expect( el.is( 'text' ) ).to.be.false;
-			expect( el.is( 'textProxy' ) ).to.be.false;
+			expect( el.is( '$text' ) ).to.be.false;
+			expect( el.is( '$textProxy' ) ).to.be.false;
 			expect( el.is( 'attributeElement' ) ).to.be.false;
 			expect( el.is( 'uiElement' ) ).to.be.false;
 			expect( el.is( 'emptyElement' ) ).to.be.false;

+ 6 - 6
packages/ckeditor5-engine/tests/view/element.js

@@ -103,18 +103,16 @@ describe( 'Element', () => {
 			expect( el.is( 'view:element' ) ).to.be.true;
 			expect( el.is( 'element', 'p' ) ).to.be.true;
 			expect( el.is( 'view:element', 'p' ) ).to.be.true;
-			expect( el.is( 'p' ) ).to.be.true;
-			expect( el.is( 'view:p' ) ).to.be.true;
 		} );
 
 		it( 'should return false for other accept values', () => {
 			expect( el.is( 'element', 'span' ) ).to.be.false;
 			expect( el.is( 'view:element', 'span' ) ).to.be.false;
-			expect( el.is( 'span' ) ).to.be.false;
+			expect( el.is( 'element', 'span' ) ).to.be.false;
 			expect( el.is( 'view:span' ) ).to.be.false;
-			expect( el.is( 'text' ) ).to.be.false;
-			expect( el.is( 'view:text' ) ).to.be.false;
-			expect( el.is( 'textProxy' ) ).to.be.false;
+			expect( el.is( '$text' ) ).to.be.false;
+			expect( el.is( 'view:$text' ) ).to.be.false;
+			expect( el.is( '$textProxy' ) ).to.be.false;
 			expect( el.is( 'containerElement' ) ).to.be.false;
 			expect( el.is( 'attributeElement' ) ).to.be.false;
 			expect( el.is( 'uiElement' ) ).to.be.false;
@@ -123,6 +121,8 @@ describe( 'Element', () => {
 			expect( el.is( 'rootElement' ) ).to.be.false;
 			expect( el.is( 'view:ootElement' ) ).to.be.false;
 			expect( el.is( 'documentFragment' ) ).to.be.false;
+			expect( el.is( 'node', 'p' ) ).to.be.false;
+			expect( el.is( 'view:node', 'p' ) ).to.be.false;
 		} );
 	} );
 

+ 6 - 6
packages/ckeditor5-engine/tests/view/emptyelement.js

@@ -39,8 +39,6 @@ describe( 'EmptyElement', () => {
 			expect( el.is( 'view:element' ) ).to.be.true;
 			expect( el.is( 'element', 'p' ) ).to.be.true;
 			expect( el.is( 'view:element', 'p' ) ).to.be.true;
-			expect( el.is( 'p' ) ).to.be.true;
-			expect( el.is( 'view:p' ) ).to.be.true;
 		} );
 
 		it( 'should return false for other accept values', () => {
@@ -48,16 +46,18 @@ describe( 'EmptyElement', () => {
 			expect( el.is( 'view:emptyElement', 'span' ) ).to.be.false;
 			expect( el.is( 'element', 'span' ) ).to.be.false;
 			expect( el.is( 'view:element', 'span' ) ).to.be.false;
-			expect( el.is( 'span' ) ).to.be.false;
+			expect( el.is( 'element', 'span' ) ).to.be.false;
 			expect( el.is( 'view:span' ) ).to.be.false;
-			expect( el.is( 'text' ) ).to.be.false;
-			expect( el.is( 'view:text' ) ).to.be.false;
-			expect( el.is( 'textProxy' ) ).to.be.false;
+			expect( el.is( '$text' ) ).to.be.false;
+			expect( el.is( 'view:$text' ) ).to.be.false;
+			expect( el.is( '$textProxy' ) ).to.be.false;
 			expect( el.is( 'containerElement' ) ).to.be.false;
 			expect( el.is( 'attributeElement' ) ).to.be.false;
 			expect( el.is( 'uiElement' ) ).to.be.false;
 			expect( el.is( 'rootElement' ) ).to.be.false;
 			expect( el.is( 'documentFragment' ) ).to.be.false;
+			expect( el.is( 'node', 'p' ) ).to.be.false;
+			expect( el.is( 'view:node', 'p' ) ).to.be.false;
 		} );
 	} );
 

+ 3 - 3
packages/ckeditor5-engine/tests/view/node.js

@@ -49,9 +49,9 @@ describe( 'Node', () => {
 			expect( node.is( 'rootElement' ) ).to.be.false;
 			expect( node.is( 'containerElement' ) ).to.be.false;
 			expect( node.is( 'element' ) ).to.be.false;
-			expect( node.is( 'p' ) ).to.be.false;
-			expect( node.is( 'text' ) ).to.be.false;
-			expect( node.is( 'textProxy' ) ).to.be.false;
+			expect( node.is( 'element', 'p' ) ).to.be.false;
+			expect( node.is( '$text' ) ).to.be.false;
+			expect( node.is( '$textProxy' ) ).to.be.false;
 			expect( node.is( 'attributeElement' ) ).to.be.false;
 			expect( node.is( 'uiElement' ) ).to.be.false;
 			expect( node.is( 'emptyElement' ) ).to.be.false;

+ 5 - 5
packages/ckeditor5-engine/tests/view/observer/mutationobserver.js

@@ -300,7 +300,7 @@ describe( 'MutationObserver', () => {
 		expect( lastMutations[ 0 ].type ).to.equal( 'children' );
 		expect( lastMutations[ 0 ].oldChildren.length ).to.equal( 0 );
 		expect( lastMutations[ 0 ].newChildren.length ).to.equal( 1 );
-		expect( lastMutations[ 0 ].newChildren[ 0 ].is( 'text' ) ).to.be.true;
+		expect( lastMutations[ 0 ].newChildren[ 0 ].is( '$text' ) ).to.be.true;
 		expect( lastMutations[ 0 ].newChildren[ 0 ].data ).to.equal( ' ' );
 		expect( lastMutations[ 0 ].node ).to.equal( selection.getFirstPosition().parent );
 	} );
@@ -334,9 +334,9 @@ describe( 'MutationObserver', () => {
 		expect( lastMutations[ 0 ].newChildren.length ).to.equal( 3 );
 
 		// Foo and attribute is removed and reinserted.
-		expect( lastMutations[ 0 ].oldChildren[ 0 ].is( 'text' ) ).to.be.true;
+		expect( lastMutations[ 0 ].oldChildren[ 0 ].is( '$text' ) ).to.be.true;
 		expect( lastMutations[ 0 ].oldChildren[ 0 ].data ).to.equal( 'foo' );
-		expect( lastMutations[ 0 ].newChildren[ 0 ].is( 'text' ) ).to.be.true;
+		expect( lastMutations[ 0 ].newChildren[ 0 ].is( '$text' ) ).to.be.true;
 		expect( lastMutations[ 0 ].newChildren[ 0 ].data ).to.equal( 'foo' );
 
 		expect( lastMutations[ 0 ].oldChildren[ 1 ].is( 'attributeElement' ) ).to.be.true;
@@ -344,7 +344,7 @@ describe( 'MutationObserver', () => {
 		expect( lastMutations[ 0 ].newChildren[ 1 ].is( 'attributeElement' ) ).to.be.true;
 		expect( lastMutations[ 0 ].newChildren[ 1 ].name ).to.equal( 'b' );
 
-		expect( lastMutations[ 0 ].newChildren[ 2 ].is( 'text' ) ).to.be.true;
+		expect( lastMutations[ 0 ].newChildren[ 2 ].is( '$text' ) ).to.be.true;
 		expect( lastMutations[ 0 ].newChildren[ 2 ].data ).to.equal( ' ' );
 		expect( lastMutations[ 0 ].node ).to.equal( selection.getFirstPosition().parent );
 	} );
@@ -375,7 +375,7 @@ describe( 'MutationObserver', () => {
 		expect( lastMutations[ 0 ].type ).to.equal( 'children' );
 		expect( lastMutations[ 0 ].oldChildren.length ).to.equal( 0 );
 		expect( lastMutations[ 0 ].newChildren.length ).to.equal( 1 );
-		expect( lastMutations[ 0 ].newChildren[ 0 ].is( 'text' ) ).to.be.true;
+		expect( lastMutations[ 0 ].newChildren[ 0 ].is( '$text' ) ).to.be.true;
 		expect( lastMutations[ 0 ].newChildren[ 0 ].data ).to.equal( ' ' );
 		expect( lastMutations[ 0 ].node ).to.equal( selection.getFirstPosition().parent );
 	} );

+ 3 - 3
packages/ckeditor5-engine/tests/view/position.js

@@ -53,9 +53,9 @@ describe( 'Position', () => {
 			expect( position.is( 'rootElement' ) ).to.be.false;
 			expect( position.is( 'containerElement' ) ).to.be.false;
 			expect( position.is( 'element' ) ).to.be.false;
-			expect( position.is( 'p' ) ).to.be.false;
-			expect( position.is( 'text' ) ).to.be.false;
-			expect( position.is( 'textProxy' ) ).to.be.false;
+			expect( position.is( 'element', 'p' ) ).to.be.false;
+			expect( position.is( '$text' ) ).to.be.false;
+			expect( position.is( '$textProxy' ) ).to.be.false;
 			expect( position.is( 'attributeElement' ) ).to.be.false;
 			expect( position.is( 'uiElement' ) ).to.be.false;
 			expect( position.is( 'emptyElement' ) ).to.be.false;

+ 3 - 3
packages/ckeditor5-engine/tests/view/range.js

@@ -68,9 +68,9 @@ describe( 'Range', () => {
 			expect( range.is( 'rootElement' ) ).to.be.false;
 			expect( range.is( 'containerElement' ) ).to.be.false;
 			expect( range.is( 'element' ) ).to.be.false;
-			expect( range.is( 'p' ) ).to.be.false;
-			expect( range.is( 'text' ) ).to.be.false;
-			expect( range.is( 'textProxy' ) ).to.be.false;
+			expect( range.is( 'element', 'p' ) ).to.be.false;
+			expect( range.is( '$text' ) ).to.be.false;
+			expect( range.is( '$textProxy' ) ).to.be.false;
 			expect( range.is( 'attributeElement' ) ).to.be.false;
 			expect( range.is( 'uiElement' ) ).to.be.false;
 			expect( range.is( 'emptyElement' ) ).to.be.false;

+ 8 - 6
packages/ckeditor5-engine/tests/view/rooteditableelement.js

@@ -66,8 +66,6 @@ describe( 'RootEditableElement', () => {
 			expect( el.is( 'view:element' ) ).to.be.true;
 			expect( el.is( 'element', 'div' ) ).to.be.true;
 			expect( el.is( 'view:element', 'div' ) ).to.be.true;
-			expect( el.is( 'div' ) ).to.be.true;
-			expect( el.is( 'view:div' ) ).to.be.true;
 		} );
 
 		it( 'should return false for other accept values', () => {
@@ -77,16 +75,20 @@ describe( 'RootEditableElement', () => {
 			expect( el.is( 'view:containerElement', 'p' ) ).to.be.false;
 			expect( el.is( 'element', 'p' ) ).to.be.false;
 			expect( el.is( 'view:element', 'p' ) ).to.be.false;
-			expect( el.is( 'p' ) ).to.be.false;
+			expect( el.is( 'element', 'p' ) ).to.be.false;
 			expect( el.is( 'view:p' ) ).to.be.false;
-			expect( el.is( 'text' ) ).to.be.false;
-			expect( el.is( 'view:text' ) ).to.be.false;
-			expect( el.is( 'textProxy' ) ).to.be.false;
+			expect( el.is( '$text' ) ).to.be.false;
+			expect( el.is( 'view:$text' ) ).to.be.false;
+			expect( el.is( '$textProxy' ) ).to.be.false;
 			expect( el.is( 'attributeElement' ) ).to.be.false;
 			expect( el.is( 'uiElement' ) ).to.be.false;
 			expect( el.is( 'emptyElement' ) ).to.be.false;
 			expect( el.is( 'documentFragment' ) ).to.be.false;
 			expect( el.is( 'model:rootElement' ) ).to.be.false;
+			expect( el.is( 'div' ) ).to.be.false;
+			expect( el.is( 'view:div' ) ).to.be.false;
+			expect( el.is( 'node', 'div' ) ).to.be.false;
+			expect( el.is( 'view:node', 'div' ) ).to.be.false;
 		} );
 	} );
 

+ 2 - 2
packages/ckeditor5-engine/tests/view/selection.js

@@ -613,8 +613,8 @@ describe( 'Selection', () => {
 			expect( selection.is( 'documentSelection' ) ).to.be.false;
 			expect( selection.is( 'view:documentSelection' ) ).to.be.false;
 			expect( selection.is( 'node' ) ).to.be.false;
-			expect( selection.is( 'text' ) ).to.be.false;
-			expect( selection.is( 'textProxy' ) ).to.be.false;
+			expect( selection.is( '$text' ) ).to.be.false;
+			expect( selection.is( '$textProxy' ) ).to.be.false;
 			expect( selection.is( 'element' ) ).to.be.false;
 			expect( selection.is( 'rootElement' ) ).to.be.false;
 			expect( selection.is( 'model:selection' ) ).to.be.false;

+ 5 - 3
packages/ckeditor5-engine/tests/view/text.js

@@ -35,13 +35,15 @@ describe( 'Text', () => {
 		it( 'should return true for node, text', () => {
 			expect( text.is( 'node' ) ).to.be.true;
 			expect( text.is( 'view:node' ) ).to.be.true;
+			expect( text.is( '$text' ) ).to.be.true;
+			expect( text.is( 'view:$text' ) ).to.be.true;
 			expect( text.is( 'text' ) ).to.be.true;
 			expect( text.is( 'view:text' ) ).to.be.true;
 		} );
 
 		it( 'should return false for other accept values', () => {
-			expect( text.is( 'textProxy' ) ).to.be.false;
-			expect( text.is( 'view:textProxy' ) ).to.be.false;
+			expect( text.is( '$textProxy' ) ).to.be.false;
+			expect( text.is( 'view:$textProxy' ) ).to.be.false;
 			expect( text.is( 'element' ) ).to.be.false;
 			expect( text.is( 'view:element' ) ).to.be.false;
 			expect( text.is( 'containerElement' ) ).to.be.false;
@@ -50,7 +52,7 @@ describe( 'Text', () => {
 			expect( text.is( 'emptyElement' ) ).to.be.false;
 			expect( text.is( 'rootElement' ) ).to.be.false;
 			expect( text.is( 'documentFragment' ) ).to.be.false;
-			expect( text.is( 'model:text' ) ).to.be.false;
+			expect( text.is( 'model:$text' ) ).to.be.false;
 			expect( text.is( 'model:node' ) ).to.be.false;
 		} );
 	} );

+ 6 - 4
packages/ckeditor5-engine/tests/view/textproxy.js

@@ -65,7 +65,9 @@ describe( 'TextProxy', () => {
 	} );
 
 	describe( 'is()', () => {
-		it( 'should return true for textProxy', () => {
+		it( 'should return true for $textProxy', () => {
+			expect( textProxy.is( '$textProxy' ) ).to.be.true;
+			expect( textProxy.is( 'view:$textProxy' ) ).to.be.true;
 			expect( textProxy.is( 'textProxy' ) ).to.be.true;
 			expect( textProxy.is( 'view:textProxy' ) ).to.be.true;
 		} );
@@ -73,8 +75,8 @@ describe( 'TextProxy', () => {
 		it( 'should return false for other accept values', () => {
 			expect( textProxy.is( 'node' ) ).to.be.false;
 			expect( textProxy.is( 'view:node' ) ).to.be.false;
-			expect( textProxy.is( 'text' ) ).to.be.false;
-			expect( textProxy.is( 'view:text' ) ).to.be.false;
+			expect( textProxy.is( '$text' ) ).to.be.false;
+			expect( textProxy.is( 'view:$text' ) ).to.be.false;
 			expect( textProxy.is( 'element' ) ).to.be.false;
 			expect( textProxy.is( 'containerElement' ) ).to.be.false;
 			expect( textProxy.is( 'attributeElement' ) ).to.be.false;
@@ -82,7 +84,7 @@ describe( 'TextProxy', () => {
 			expect( textProxy.is( 'emptyElement' ) ).to.be.false;
 			expect( textProxy.is( 'rootElement' ) ).to.be.false;
 			expect( textProxy.is( 'documentFragment' ) ).to.be.false;
-			expect( textProxy.is( 'model:textProxy' ) ).to.be.false;
+			expect( textProxy.is( 'model:$textProxy' ) ).to.be.false;
 		} );
 	} );
 

+ 5 - 5
packages/ckeditor5-engine/tests/view/uielement.js

@@ -59,8 +59,6 @@ describe( 'UIElement', () => {
 			expect( el.is( 'view:node' ) ).to.be.true;
 			expect( el.is( 'element', 'span' ) ).to.be.true;
 			expect( el.is( 'view:element', 'span' ) ).to.be.true;
-			expect( el.is( 'span' ) ).to.be.true;
-			expect( el.is( 'view:span' ) ).to.be.true;
 		} );
 
 		it( 'should return false for other accept values', () => {
@@ -68,10 +66,10 @@ describe( 'UIElement', () => {
 			expect( el.is( 'view:uiElement', 'p' ) ).to.be.false;
 			expect( el.is( 'element', 'p' ) ).to.be.false;
 			expect( el.is( 'view:element', 'p' ) ).to.be.false;
-			expect( el.is( 'p' ) ).to.be.false;
+			expect( el.is( 'element', 'p' ) ).to.be.false;
 			expect( el.is( 'view:p' ) ).to.be.false;
-			expect( el.is( 'text' ) ).to.be.false;
-			expect( el.is( 'textProxy' ) ).to.be.false;
+			expect( el.is( '$text' ) ).to.be.false;
+			expect( el.is( '$textProxy' ) ).to.be.false;
 			expect( el.is( 'containerElement' ) ).to.be.false;
 			expect( el.is( 'attributeElement' ) ).to.be.false;
 			expect( el.is( 'emptyElement' ) ).to.be.false;
@@ -80,6 +78,8 @@ describe( 'UIElement', () => {
 			expect( el.is( 'model:element' ) ).to.be.false;
 			expect( el.is( 'model:span' ) ).to.be.false;
 			expect( el.is( 'model:node' ) ).to.be.false;
+			expect( el.is( 'node', 'span' ) ).to.be.false;
+			expect( el.is( 'view:node', 'span' ) ).to.be.false;
 		} );
 	} );
 

+ 1 - 1
packages/ckeditor5-font/src/ui/colortableview.js

@@ -197,7 +197,7 @@ export default class ColorTableView extends View {
 			const range = model.createRangeIn( root );
 
 			for ( const node of range.getItems() ) {
-				if ( node.is( 'textProxy' ) && node.hasAttribute( attributeName ) ) {
+				if ( node.is( '$textProxy' ) && node.hasAttribute( attributeName ) ) {
 					this._addColorToDocumentColors( node.getAttribute( attributeName ) );
 
 					if ( this.documentColors.length >= maxCount ) {

+ 1 - 1
packages/ckeditor5-heading/src/headingcommand.js

@@ -76,7 +76,7 @@ export default class HeadingCommand extends Command {
 				} );
 
 			for ( const block of blocks ) {
-				if ( !block.is( modelElement ) ) {
+				if ( !block.is( 'element', modelElement ) ) {
 					writer.rename( block, modelElement );
 				}
 			}

+ 2 - 2
packages/ckeditor5-heading/src/headingediting.js

@@ -95,9 +95,9 @@ export default class HeadingEditing extends Plugin {
 		if ( enterCommand ) {
 			this.listenTo( enterCommand, 'afterExecute', ( evt, data ) => {
 				const positionParent = editor.model.document.selection.getFirstPosition().parent;
-				const isHeading = options.some( option => positionParent.is( option.model ) );
+				const isHeading = options.some( option => positionParent.is( 'element', option.model ) );
 
-				if ( isHeading && !positionParent.is( defaultModelElement ) && positionParent.childCount === 0 ) {
+				if ( isHeading && !positionParent.is( 'element', defaultModelElement ) && positionParent.childCount === 0 ) {
 					data.writer.rename( positionParent, defaultModelElement );
 				}
 			} );

+ 6 - 6
packages/ckeditor5-heading/src/title.js

@@ -237,7 +237,7 @@ export default class Title extends Plugin {
 
 		// When title element is at the beginning of the document then try to fix additional
 		// title elements (if there are any) and stop post-fixer as soon as possible.
-		if ( firstRootChild.is( 'title' ) ) {
+		if ( firstRootChild.is( 'element', 'title' ) ) {
 			return fixAdditionalTitleElements( titleElements, writer, model );
 		}
 
@@ -386,7 +386,7 @@ export default class Title extends Plugin {
 				const selection = model.document.selection;
 				const selectedElements = Array.from( selection.getSelectedBlocks() );
 
-				if ( selectedElements.length === 1 && selectedElements[ 0 ].is( 'title-content' ) ) {
+				if ( selectedElements.length === 1 && selectedElements[ 0 ].is( 'element', 'title-content' ) ) {
 					const firstBodyElement = model.document.getRoot().getChild( 1 );
 					writer.setSelection( firstBodyElement, 0 );
 					cancel();
@@ -429,7 +429,7 @@ function dataViewModelH1Insertion( evt, data, conversionApi ) {
 	const modelCursor = data.modelCursor;
 	const viewItem = data.viewItem;
 
-	if ( !modelCursor.isAtStart || !modelCursor.parent.is( '$root' ) ) {
+	if ( !modelCursor.isAtStart || !modelCursor.parent.is( 'element', '$root' ) ) {
 		return;
 	}
 
@@ -460,7 +460,7 @@ function mapModelPositionToView( editingView ) {
 	return ( evt, data ) => {
 		const positionParent = data.modelPosition.parent;
 
-		if ( !positionParent.is( 'title' ) ) {
+		if ( !positionParent.is( 'element', 'title' ) ) {
 			return;
 		}
 
@@ -477,7 +477,7 @@ function mapModelPositionToView( editingView ) {
 // @param {module:engine/model/element~Element} element
 // @returns {Boolean}
 function isTitle( element ) {
-	return element.is( 'title' );
+	return element.is( 'element', 'title' );
 }
 
 // Changes the given element to the title element.
@@ -542,7 +542,7 @@ function fixTitleElement( title, writer, model ) {
 // @param {module:engine/model/element~Element} placeholder
 // @returns {Boolean}
 function shouldRemoveLastParagraph( placeholder, root ) {
-	if ( !placeholder || !placeholder.is( 'paragraph' ) || placeholder.childCount ) {
+	if ( !placeholder || !placeholder.is( 'element', 'paragraph' ) || placeholder.childCount ) {
 		return false;
 	}
 

+ 1 - 1
packages/ckeditor5-horizontal-line/src/horizontallinecommand.js

@@ -108,7 +108,7 @@ function getInsertHorizontalLineParent( selection, model ) {
 
 	const parent = insertAt.parent;
 
-	if ( parent.isEmpty && !parent.is( '$root' ) ) {
+	if ( parent.isEmpty && !parent.is( 'element', '$root' ) ) {
 		return parent.parent;
 	}
 

Some files were not shown because too many files changed in this diff