8
0
Просмотр исходного кода

Merge branch 'master' into i/3287

Maciej Gołaszewski 6 лет назад
Родитель
Сommit
39341d5b2f
16 измененных файлов с 290 добавлено и 177 удалено
  1. 1 0
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-external-link-target.html
  2. 1 0
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.html
  3. 3 3
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-div-attributes.html
  4. 7 7
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-div-attributes.js
  5. 1 1
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-link-target.html
  6. 6 5
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-arbitrary-attribute-values.js
  7. 19 20
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-figure-attributes.js
  8. 31 31
      packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-extending-output.md
  9. 21 21
      packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-introduction.md
  10. 56 51
      packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-preserving-custom-content.md
  11. 26 17
      packages/ckeditor5-engine/src/model/model.js
  12. 5 1
      packages/ckeditor5-engine/src/view/domconverter.js
  13. 24 20
      packages/ckeditor5-engine/src/view/view.js
  14. 54 0
      packages/ckeditor5-engine/tests/model/model.js
  15. 9 0
      packages/ckeditor5-engine/tests/view/domconverter/view-to-dom.js
  16. 26 0
      packages/ckeditor5-engine/tests/view/view/view.js

+ 1 - 0
packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-external-link-target.html

@@ -8,4 +8,5 @@
 	<p>External links in this <a href="https://ckeditor.com">editor</a> have a <code>target="_blank"</code>
 	<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes">attribute</a> and will open in a new
 	<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window">window</a>.</p>
+	<p>Edit the URL of the links including "ckeditor.com" and other domains to see them marked as "internal" or "external".</p>
 </div>

+ 1 - 0
packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.html

@@ -9,4 +9,5 @@
 <div id="snippet-link-unsafe-classes">
 	<p>All links in this <a href="https://ckeditor.com">editor</a> that do not use the <a href="http://developer.mozilla.org/en-US/docs/Glossary/https">HTTPS</a> protocol
 	have a custom <code>.unsafe-link</code> CSS <a href="http://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class">class</a> that marks them red.</p>
+	<p>Edit the URL of the links using "http://" or "https://" to see them marked as "safe" or "unsafe".</p>
 </div>

+ 3 - 3
packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-div-attributes.html

@@ -1,12 +1,12 @@
 <div id="snippet-div-attributes">
 	<div id="special-section-a" style="background: #eafbd7;padding:.8em .8em 0;margin-bottom:.8em;border:1px solid #8bc34a;">
-		<p><strong>Special section A</strong>: it has set "style" and "id" attributes.</p>
+		<p><strong>Special section A</strong>: It has set "style" and "id" attributes.</p>
 	</div>
 
 	<p>Regular content of the editor.</p>
 
 	<div id="special-section-b" style="background: #ffeed0;padding:.8em .8em 0;margin-bottom:.8em;border:1px solid #ff8f00;" spellcheck="false">
-		<p><strong>Special section B</strong>: it has set "style", "id" and spellcheck="false" attributes.</p>
-		<p>This section disables native browser spellchecker.</p>
+		<p><strong>Special section B</strong>: It has set "style", "id" and spellcheck="false" attributes.</p>
+		<p>This section disables the native browser spellchecker.</p>
 	</div>
 </div>

+ 7 - 7
packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-div-attributes.js

@@ -8,20 +8,20 @@
 import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
 
 function ConvertDivAttributes( editor ) {
-	// Allow divs in the model.
+	// Allow <div> elements in the model.
 	editor.model.schema.register( 'div', {
 		allowWhere: '$block',
 		allowContentOf: '$root'
 	} );
 
-	// Allow divs in the model to have all attributes.
+	// Allow <div> elements in the model to have all attributes.
 	editor.model.schema.addAttributeCheck( context => {
 		if ( context.endsWith( 'div' ) ) {
 			return true;
 		}
 	} );
 
-	// View-to-model converter converting a view div with all its attributes to the model.
+	// View-to-model converter converting a view <div> with all its attributes to the model.
 	editor.conversion.for( 'upcast' ).elementToElement( {
 		view: 'div',
 		model: ( viewElement, modelWriter ) => {
@@ -29,17 +29,17 @@ function ConvertDivAttributes( editor ) {
 		}
 	} );
 
-	// Model-to-view convert for the div element (attrbiutes are converted separately).
+	// Model-to-view converter for the <div> element (attrbiutes are converted separately).
 	editor.conversion.for( 'downcast' ).elementToElement( {
 		model: 'div',
 		view: 'div'
 	} );
 
-	// Model-to-view converter for div attributes.
-	// Note that we use a lower-level, event-based API here.
+	// Model-to-view converter for <div> attributes.
+	// Note that a lower-level, event-based API is used here.
 	editor.conversion.for( 'downcast' ).add( dispatcher => {
 		dispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
-			// Convert div attributes only.
+			// Convert <div> attributes only.
 			if ( data.item.name != 'div' ) {
 				return;
 			}

+ 1 - 1
packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-link-target.html

@@ -25,7 +25,7 @@
 <p><b>Note</b>: You can play with the content to see that different link <code>target</code> values are also handled.</p>
 
 <textarea id="snippet-link-target-content">
-&lt;p&gt;A couple of links with different targets:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&#9;&lt;li&gt;&lt;a href=&quot;https://ckeditor.com&quot; target=&quot;_blank&quot;&gt;CKEditor&lt;/a&gt;&lt;/li&gt;&#10;&#9;&lt;li&gt;&lt;a href=&quot;https://ckeditor.com/ckfinder/&quot;&gt;CKFinder&lt;/a&gt;&lt;/li&gt;&#10;&#9;&lt;li&gt;&lt;a href=&quot;https://cksource.com&quot; target=&quot;_parent&quot;&gt;CKSource&lt;/a&gt;&lt;/li&gt;&#10;&#9;&lt;li&gt;&lt;a href=&quot;https://cksource.com/team/&quot; target=&quot;_top&quot;&gt;Team&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;
+&lt;p&gt;A few links with different targets:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&#9;&lt;li&gt;&lt;a href=&quot;https://ckeditor.com&quot; target=&quot;_blank&quot;&gt;CKEditor&lt;/a&gt;&lt;/li&gt;&#10;&#9;&lt;li&gt;&lt;a href=&quot;https://ckeditor.com/ckfinder/&quot;&gt;CKFinder&lt;/a&gt;&lt;/li&gt;&#10;&#9;&lt;li&gt;&lt;a href=&quot;https://cksource.com&quot; target=&quot;_parent&quot;&gt;CKSource&lt;/a&gt;&lt;/li&gt;&#10;&#9;&lt;li&gt;&lt;a href=&quot;https://cksource.com/team/&quot; target=&quot;_top&quot;&gt;Team&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;
 </textarea>
 
 <p>

+ 6 - 5
packages/ckeditor5-engine/docs/_snippets/framework/extending-content-arbitrary-attribute-values.js

@@ -8,7 +8,7 @@
 import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
 
 function HandleFontSizeValue( editor ) {
-	// Add special catch-all converter for font-size feature.
+	// Add a special catch-all converter for the font size feature.
 	editor.conversion.for( 'upcast' ).elementToAttribute( {
 		view: {
 			name: 'span',
@@ -22,18 +22,19 @@ function HandleFontSizeValue( editor ) {
 				const value = parseFloat( viewElement.getStyle( 'font-size' ) ).toFixed( 0 );
 
 				// It might be needed to further convert the value to meet business requirements.
-				// In the sample the font-size is configured to handle only the sizes:
-				// 10, 12, 14, 'default', 18, 20, 22
+				// In the sample the font size is configured to handle only the sizes:
+				// 12, 14, 'default', 18, 20, 22, 24, 26, 28, 30
 				// Other sizes will be converted to the model but the UI might not be aware of them.
 
-				// The font-size feature expects numeric values to be Number not String.
+				// The font size feature expects numeric values to be Number, not String.
 				return parseInt( value );
 			}
 		},
 		converterPriority: 'high'
 	} );
 
-	// Add special converter for font-size feature to convert all (even not configured) model attribute values.
+	// Add a special converter for the font size feature to convert all (even not configured)
+	// model attribute values.
 	editor.conversion.for( 'downcast' ).attributeToElement( {
 		model: {
 			key: 'fontSize'

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

@@ -11,7 +11,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
  * Plugin that converts custom attributes for elements that are wrapped in <figure> in the view.
  */
 function CustomFigureAttributes( editor ) {
-	// Define on which elements the css classes should be preserved:
+	// Define on which elements the CSS classes should be preserved:
 	setupCustomClassConversion( 'img', 'image', editor );
 	setupCustomClassConversion( 'table', 'table', editor );
 
@@ -23,30 +23,29 @@ function CustomFigureAttributes( editor ) {
 }
 
 /**
- * Setups conversion that preservers classes on img/table elements
+ * Sets up a conversion that preservers classes on <img> and <table> elements.
  */
 function setupCustomClassConversion( viewElementName, modelElementName, editor ) {
-	// The 'customClass' attribute will store custom classes from data in the model so schema definitions to allow this attribute.
+	// The 'customClass' attribute will store custom classes from the data in the model so schema definitions allow this attribute.
 	editor.model.schema.extend( modelElementName, { allowAttributes: [ 'customClass' ] } );
 
-	// Define upcast converters for <img> and <table> elements with "low" priority so they are run after default converters.
+	// Define upcast converters for the <img> and <table> elements with a "low" priority so they are run after the default converters.
 	editor.conversion.for( 'upcast' ).add( upcastCustomClasses( viewElementName ), { priority: 'low' } );
 
-	// Define downcast converters for model element with "low" priority so they are run after default converters.
+	// Define downcast converters for a model element with a "low" priority so they are run after the default converters.
 	editor.conversion.for( 'downcast' ).add( downcastCustomClasses( modelElementName ), { priority: 'low' } );
 }
 
 /**
- * Setups conversion for custom attribute on view elements contained inside figure.
+ * Sets up a conversion for a custom attribute on view elements contained inside a <figure>.
  *
  * This method:
- *
- * - adds proper schema rules
- * - adds an upcast converter
- * - adds a downcast converter
+ * - Adds proper schema rules.
+ * - Adds an upcast converter.
+ * - Adds a downcast converter.
  */
 function setupCustomAttributeConversion( viewElementName, modelElementName, viewAttribute, editor ) {
-	// Extend schema to store attribute in the model.
+	// Extend the schema to store an attribute in the model.
 	const modelAttribute = `custom${ viewAttribute }`;
 
 	editor.model.schema.extend( modelElementName, { allowAttributes: [ modelAttribute ] } );
@@ -56,7 +55,7 @@ function setupCustomAttributeConversion( viewElementName, modelElementName, view
 }
 
 /**
- * Creates upcast converter that will pass all classes from view element to model element.
+ * Creates an upcast converter that will pass all classes from the view element to the model element.
  */
 function upcastCustomClasses( elementName ) {
 	return dispatcher => dispatcher.on( `element:${ elementName }`, ( evt, data, conversionApi ) => {
@@ -69,7 +68,7 @@ function upcastCustomClasses( elementName ) {
 			return;
 		}
 
-		// The upcast conversion pick up classes from base element and from figure element also so it should be extensible.
+		// The upcast conversion picks up classes from the base element and from the <figure> element so it should be extensible.
 		const currentAttributeValue = modelElement.getAttribute( 'customClass' ) || [];
 
 		currentAttributeValue.push( ...viewItem.getClassNames() );
@@ -79,9 +78,9 @@ function upcastCustomClasses( elementName ) {
 }
 
 /**
- * Creates downcast converter that add classes defined in `customClass` attribute to given view element.
+ * Creates a downcast converter that adds classes defined in the `customClass` attribute to a given view element.
  *
- * This converter expects that view element is nested in figure element.
+ * This converter expects that the view element is nested in a <figure> element.
  */
 function downcastCustomClasses( modelElementName ) {
 	return dispatcher => dispatcher.on( `insert:${ modelElementName }`, ( evt, data, conversionApi ) => {
@@ -93,10 +92,10 @@ function downcastCustomClasses( modelElementName ) {
 			return;
 		}
 
-		// The below code assumes that classes are set on <figure> element...
+		// The code below assumes that classes are set on the <figure> element...
 		conversionApi.writer.addClass( modelElement.getAttribute( 'customClass' ), viewFigure );
 
-		// ... but if you preferIf the classes should be passed to the <img> find the view element inside figure:
+		// ... but if you prefer the classes to be passed to the <img> element, find the view element inside the <figure>:
 		//
 		// const viewElement = findViewChild( viewFigure, viewElementName, conversionApi );
 		//
@@ -105,7 +104,7 @@ function downcastCustomClasses( modelElementName ) {
 }
 
 /**
- * Helper method that search for given view element in all children of model element.
+ * Helper method that searches for a given view element in all children of the model element.
  *
  * @param {module:engine/view/item~Item} viewElement
  * @param {String} viewElementName
@@ -119,7 +118,7 @@ function findViewChild( viewElement, viewElementName, conversionApi ) {
 }
 
 /**
- * Returns custom attribute upcast converter.
+ * Returns the custom attribute upcast converter.
  */
 function upcastAttribute( viewElementName, viewAttribute, modelAttribute ) {
 	return dispatcher => dispatcher.on( `element:${ viewElementName }`, ( evt, data, conversionApi ) => {
@@ -137,7 +136,7 @@ function upcastAttribute( viewElementName, viewAttribute, modelAttribute ) {
 }
 
 /**
- * Returns custom attribute downcast converter.
+ * Returns the custom attribute downcast converter.
  */
 function downcastAttribute( modelElementName, viewElementName, viewAttribute, modelAttribute ) {
 	return dispatcher => dispatcher.on( `insert:${ modelElementName }`, ( evt, data, conversionApi ) => {

+ 31 - 31
packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-extending-output.md

@@ -6,25 +6,25 @@ order: 20
 
 {@snippet framework/build-extending-content-source}
 
-# Extending editor output
+# Extending the editor output
 
-In this guide, we will focus on customization to the one–way {@link framework/guides/architecture/editing-engine#editing-pipeline "downcast"} pipeline of the editor, which transforms data from the model to the editing view and the output data only. The following examples do not customize the model and do not process the (input) data  you can picture them as post–processors (filters) applied to the output only.
+This guide focuses on customization of the one–way {@link framework/guides/architecture/editing-engine#editing-pipeline "downcast"} pipeline of CKEditor 5. This pipeline transforms the data from the model to the editing view and the output data. The following examples do not customize the model and do not process the (input) data &mdash; you can picture them as post–processors (filters) applied to the output only.
 
-If you want to learn how to load some extra content (element, attributes, classes) into the editor, check out the {@link framework/guides/deep-dive/conversion-preserving-custom-content next guide} of this guide.
+If you want to learn how to load some extra content (element, attributes, classes) into the rich-text editor, check out the {@link framework/guides/deep-dive/conversion-preserving-custom-content next guide} of this section.
 
 ## Before starting
 
 ### Code architecture
 
-It is recommended that the code that customizes editor data and editing pipelines is delivered as {@link framework/guides/architecture/core-editor-architecture#plugins plugins} and all examples in this chapter follow this convention.
+It is recommended that the code that customizes the editor data and editing pipelines is delivered as {@link framework/guides/architecture/core-editor-architecture#plugins plugins} and all examples in this guide follow this convention.
 
-Also for the sake of simplicity all examples use the same {@link module:editor-classic/classiceditor~ClassicEditor `ClassicEditor`} but keep in mind that code snippets will work with other editors too.
+Also for the sake of simplicity all examples use the same {@link module:editor-classic/classiceditor~ClassicEditor `ClassicEditor`}, but keep in mind that code snippets will work with other editors, too.
 
-Finally, none of the converters covered in this guide require to import any module from CKEditor 5 Framework, hence, you can write them without rebuilding the editor. In other words, such converters can easily be added to existing CKEditor 5 builds.
+Finally, none of the converters covered in this guide require to import any module from CKEditor 5 Framework, hence, you can write them without rebuilding the editor. In other words, such converters can easily be added to existing {@link builds/guides/overview CKEditor 5 builds}.
 
 ### Granular converters
 
-You can create separate converters for data and editing (downcast) pipelines. The former (`dataDowncast`) will customize the data in the editor output (e.g. when {@link module:core/editor/utils/dataapimixin~DataApi#getData `editor.getData()`}) and the later (`editingDowncast`) will only work for the content of the editor when editing.
+You can create separate converters for the data and editing (downcast) pipelines. The former (`dataDowncast`) will customize the data in the editor output (e.g. when {@link builds/guides/integration/saving-data#manually-retrieving-the-data obtaining the editor data}). The latter (`editingDowncast`) will only work for the content of the editor when editing.
 
 If you do not want to complicate your conversion, you can just add a single (`downcast`) converter which will apply both to the data and the editing view. We did that in all examples to keep them simple but keep in mind you have options:
 
@@ -39,7 +39,7 @@ editor.conversion.for( 'dataDowncast' ).add( dispatcher => {
 	// ...
 } );
 
-// Adds a conversion dispatcher for both data and editing downcast pipelines.
+// Adds a conversion dispatcher for both the data and the editing downcast pipelines.
 editor.conversion.for( 'downcast' ).add( dispatcher => {
 	// ...
 } );
@@ -47,11 +47,11 @@ editor.conversion.for( 'downcast' ).add( dispatcher => {
 
 ### CKEditor 5 inspector
 
-{@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} is an invaluable help with working with the model and view structures. It allows browsing their structure and checking selection positions like in typical browser dev tools. Make sure to enable CKEditor 5 inspector when playing with CKEditor 5.
+{@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} is an invaluable help when working with the model and view structures. It allows browsing their structure and checking selection positions like in typical browser developer tools. Make sure to enable the inspector when playing with CKEditor 5.
 
 ## Adding a CSS class to inline elements
 
-In this example all links (`<a href="...">...</a>`) get the `.my-green-link` CSS class. That includes all links in the editor output (`editor.getData()`) and all links in the edited content (existing and future ones).
+In this example all links (`<a href="...">...</a>`) get the `.my-green-link` CSS class. This includes all links in the editor output (`editor.getData()`) and all links in the edited content (existing and future ones).
 
 <info-box>
 	Note that the same behavior can be obtained with {@link features/link#custom-link-attributes-decorators link decorators}:
@@ -76,15 +76,15 @@ In this example all links (`<a href="...">...</a>`) get the `.my-green-link` CSS
 
 {@snippet framework/extending-content-add-link-class}
 
-Adding a custom CSS class to all links is made by a custom converter plugged into the downcast pipeline, following the default converters brought by the {@link features/link Link} feature:
+A custom CSS class is added to all links by a custom converter plugged into the downcast pipeline, following the default converters brought by the {@link features/link link} feature:
 
 ```js
 // This plugin brings customization to the downcast pipeline of the editor.
 function AddClassToAllLinks( editor ) {
-	// Both data and editing pipelines are affected by this conversion.
+	// Both the data and the editing pipelines are affected by this conversion.
 	editor.conversion.for( 'downcast' ).add( dispatcher => {
 		// Links are represented in the model as a "linkHref" attribute.
-		// Use the "low" listener priority to apply the changes after the Link feature.
+		// Use the "low" listener priority to apply the changes after the link feature.
 		dispatcher.on( 'attribute:linkHref', ( evt, data, conversionApi ) => {
 			const viewWriter = conversionApi.writer;
 			const viewSelection = viewWriter.document.selection;
@@ -136,7 +136,7 @@ Add some CSS styles for `.my-green-link` to see the customization in action:
 
 ## Adding an HTML attribute to certain inline elements
 
-In this example all links (`<a href="...">...</a>`) which do not have "ckeditor.com" in their `href="..."` get the `target="_blank"` attribute. That includes all links in the editor output (`editor.getData()`) and all links in the edited content (existing and future ones).
+In this example all links (`<a href="...">...</a>`) that do not have "ckeditor.com" in their `href="..."` get the `target="_blank"` attribute. This includes all links in the editor output (`editor.getData()`) and all links in the edited content (existing and future ones).
 
 <info-box>
 	Note that similar behavior can be obtained with {@link module:link/link~LinkConfig#addTargetToExternalLinks link decorators}:
@@ -154,17 +154,15 @@ In this example all links (`<a href="...">...</a>`) which do not have "ckeditor.
 
 {@snippet framework/extending-content-add-external-link-target}
 
-**Note:** Edit the URL of the links including "ckeditor.com" and other domains to see them marked as "internal" or "external".
-
-Adding the `target` attribute to all "external" links is made by a custom converter plugged into the downcast pipeline, following the default converters brought by the {@link features/link Link} feature:
+The `target` attribute is added to all "external" links by a custom converter plugged into the downcast pipeline, following the default converters brought by the {@link features/link link} feature:
 
 ```js
 // This plugin brings customization to the downcast pipeline of the editor.
 function AddTargetToExternalLinks( editor ) {
-	// Both data and editing pipelines are affected by this conversion.
+	// Both the data and the editing pipelines are affected by this conversion.
 	editor.conversion.for( 'downcast' ).add( dispatcher => {
 		// Links are represented in the model as a "linkHref" attribute.
-		// Use the "low" listener priority to apply the changes after the Link feature.
+		// Use the "low" listener priority to apply the changes after the link feature.
 		dispatcher.on( 'attribute:linkHref', ( evt, data, conversionApi ) => {
 			const viewWriter = conversionApi.writer;
 			const viewSelection = viewWriter.document.selection;
@@ -216,7 +214,7 @@ a[target="_blank"]::after {
 
 ## Adding a CSS class to certain inline elements
 
-In this example all links (`<a href="...">...</a>`) which do not have "https://" in their `href="..."` attribute get the `.unsafe-link` CSS class. That includes all links in the editor output (`editor.getData()`) and all links in the edited content (existing and future ones).
+In this example all links (`<a href="...">...</a>`) that do not have `https://` in their `href="..."` attribute get the `.unsafe-link` CSS class. This includes all links in the editor output (`editor.getData()`) and all links in the edited content (existing and future ones).
 
 <info-box>
 	Note that the same behavior can be obtained with {@link features/link#custom-link-attributes-decorators link decorators}:
@@ -242,17 +240,15 @@ In this example all links (`<a href="...">...</a>`) which do not have "https://"
 
 {@snippet framework/extending-content-add-unsafe-link-class}
 
-**Note:** Edit the URL of the links using "http://" or "https://" to see them marked as "safe" or "unsafe".
-
-Adding the `.unsafe-link` CSS class to all "unsafe" links is made by a custom converter plugged into the downcast pipeline, following the default converters brought by the {@link features/link Link} feature:
+The `.unsafe-link` CSS class is added to all "unsafe" links by a custom converter plugged into the downcast pipeline, following the default converters brought by the {@link features/link link} feature:
 
 ```js
 // This plugin brings customization to the downcast pipeline of the editor.
 function AddClassToUnsafeLinks( editor ) {
-	// Both data and editing pipelines are affected by this conversion.
+	// Both the data and the editing pipelines are affected by this conversion.
 	editor.conversion.for( 'downcast' ).add( dispatcher => {
 		// Links are represented in the model as a "linkHref" attribute.
-		// Use the "low" listener priority to apply the changes after the Link feature.
+		// Use the "low" listener priority to apply the changes after the link feature.
 		dispatcher.on( 'attribute:linkHref', ( evt, data, conversionApi ) => {
 			const viewWriter = conversionApi.writer;
 			const viewSelection = viewWriter.document.selection;
@@ -294,7 +290,7 @@ ClassicEditor
 	} );
 ```
 
-Add some CSS styles for "unsafe" to make them visible:
+Add some CSS styles for "unsafe" links to make them visible:
 
 ```css
 .unsafe-link {
@@ -306,23 +302,23 @@ Add some CSS styles for "unsafe" to make them visible:
 
 ## Adding a CSS class to block elements
 
-In this example all second–level headings (`<h2>...</h2>`) get the `.my-heading` CSS class. That includes all heading elements in the editor output (`editor.getData()`) and in the edited content (existing and future ones).
+In this example all second–level headings (`<h2>...</h2>`) get the `.my-heading` CSS class. This includes all heading elements in the editor output (`editor.getData()`) and in the edited content (existing and future ones).
 
 {@snippet framework/extending-content-add-heading-class}
 
-Adding a custom CSS class to all `<h2>...</h2>` elements is made by a custom converter plugged into the downcast pipeline, following the default converters brought by the {@link features/headings Headings} feature:
+A custom CSS class is added to all `<h2>...</h2>` elements by a custom converter plugged into the downcast pipeline, following the default converters brought by the {@link features/headings headings} feature:
 
 <info-box>
-	The `heading1` element in the model corresponds to `<h2>...</h2>` in the output HTML because in the default {@link features/headings#configuring-heading-levels headings feature configuration} `<h1>...</h1>` is reserved for the top–most heading of a webpage.
+	The `heading1` element in the model corresponds to `<h2>...</h2>` in the output HTML because in the default {@link features/headings#configuring-heading-levels headings feature configuration} `<h1>...</h1>` is reserved for the top–most heading of the webpage.
 </info-box>
 
 ```js
 // This plugin brings customization to the downcast pipeline of the editor.
 function AddClassToAllHeading1( editor ) {
-	// Both data and editing pipelines are affected by this conversion.
+	// Both the data and the editing pipelines are affected by this conversion.
 	editor.conversion.for( 'downcast' ).add( dispatcher => {
 		// Headings are represented in the model as a "heading1" element.
-		// Use the "low" listener priority to apply the changes after the Headings feature.
+		// Use the "low" listener priority to apply the changes after the headings feature.
 		dispatcher.on( 'insert:heading1', ( evt, data, conversionApi ) => {
 			const viewWriter = conversionApi.writer;
 
@@ -357,3 +353,7 @@ Add some CSS styles for `.my-heading` to see the customization in action:
 	padding: .1em .8em;
 }
 ```
+
+## What's next?
+
+If you would like to read more about how to make CKEditor 5 accept more content, refer to the {@link framework/guides/deep-dive/conversion-preserving-custom-content Preserving custom content} guide.

+ 21 - 21
packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-introduction.md

@@ -18,9 +18,9 @@ In this guide we will dive deeper into some of the conversion concepts.
 
 Generally speaking, there are two main types of the content in the editor view and data output: inline and block.
 
-The inline content means elements like `<strong>`, `<a>` or `<span>`. Unlike `<p>`, `<blockquote>` or `<div>`, inline elements do not structure the data. Instead, they mark some text in a specific (visual and semantical) way. These elements are a characteristic of a text. For instance, we could say that some part of a text is bold, or a linked, etc.. This concept has its reflection in the model of the editor where `<a>` or `<strong>` are not represented as elements. Instead, they are attributes of the text.
+The inline content means elements like `<strong>`, `<a>` or `<span>`. Unlike `<p>`, `<blockquote>` or `<div>`, inline elements do not structure the data. Instead, they mark some text in a specific (visual and semantical) way. These elements are a characteristic of a text. For instance, you could say that some part of the text is bold, or is linked, etc.. This concept has its reflection in the model of the rich-text editor where `<a>` or `<strong>` are not represented as elements. Instead, they are attributes of the text.
 
-For example &mdash; in the model, we might have a `<paragraph>` element with "Foo bar" text, where "bar" has the `bold` attribute set `true`. A pseudo–code of this *model* data structure could look as follows:
+For example &mdash; in the model, you might have a `<paragraph>` element with the "Foo bar" text, where "bar" has the `bold` attribute set to `true`. A pseudo–code of this *model* data structure could look as follows:
 
 ```html
 <paragraph>
@@ -30,7 +30,7 @@ For example &mdash; in the model, we might have a `<paragraph>` element with "Fo
 ```
 
 <info-box>
-	Throughout the rest of this guide we will use the following, shorter convention to represent model text attributes:
+	Throughout the rest of this guide the following, shorter convention will be used to represent model text attributes:
 
 	```html
 	<paragraph>Foo <$text bold="true">bar</$text></paragraph>
@@ -39,13 +39,13 @@ For example &mdash; in the model, we might have a `<paragraph>` element with "Fo
 
 Note that there is no `<strong>` or any other additional element there, it is just some text with an attribute.
 
-So, when this text becomes wrapped with a `<strong>` element? This happens during conversion to the view. It is also important to know which type of a view element needs to be used. In case of elements which represent inline formatting, this should be a {@link module:engine/view/attributeelement~AttributeElement}.
+So, when does this text become wrapped with a `<strong>` element? This happens during the conversion to the view. It is also important to know which type of a view element needs to be used. In case of elements that represent inline formatting, this should be an {@link module:engine/view/attributeelement~AttributeElement}.
 
 ## Conversion of multiple text attributes
 
 A model text node may have multiple attributes (e.g. be bolded and linked) and all of them are converted to their respective view elements by independent converters.
 
-Keep in mind that in the model, attributes do not have any specific order. This is contrary to the editor view or HTML output, where inline elements are nested one in another. Fortunately, the nesting happens automatically during conversion from the model to the view. This makes working in the model simpler, as features do not need to take care of breaking or rearranging elements in the model.
+Keep in mind that in the model, attributes do not have any specific order. This is contrary to the editor view or HTML output, where inline elements are nested in one another. Fortunately, the nesting happens automatically during the conversion from the model to the view. This makes working in the model simpler, as features do not need to take care of breaking or rearranging elements in the model.
 
 For instance, consider the following model structure:
 
@@ -57,7 +57,7 @@ For instance, consider the following model structure:
 </paragraph>
 ```
 
-During conversion, it will be converted to the following view structure:
+During the conversion, it will be converted to the following view structure:
 
 ```html
 <p>
@@ -65,7 +65,7 @@ During conversion, it will be converted to the following view structure:
 </p>
 ```
 
-Note, that the `<a>` element is converted in such way that it always becomes the "topmost" element. This is intentional so that no element ever breaks a link, which would otherwise look as follows:
+Note that the `<a>` element is converted in such way that it always becomes the "topmost" element. This is intentional so that no element ever breaks a link, which would otherwise look as follows:
 
 ```html
 <p>
@@ -73,28 +73,28 @@ Note, that the `<a>` element is converted in such way that it always becomes the
 </p>
 ```
 
-There are two links with the same `href` next to each other in the generated view (editor output), which is semantically wrong. To make sure that it never happens the view element which represents  a link must have a *priority* defined. Most elements, like for instance `<strong>` do not care about it and stick to the default priority (`10`). The {@link features/link link feature} ensures that all view `<a>` elements have priority set to `5` so they are kept outside other elements.
+There are two links with the same `href` attribute next to each other in the generated view (editor output), which is semantically wrong. To make sure that it never happens, the view element that represents a link must have a *priority* defined. Most elements, like for instance `<strong>`, do not care about it and stick to the default priority (`10`). The {@link features/link link feature} ensures that all view `<a>` elements have the priority set to `5` so they are kept outside other elements.
 
 ## Merging attribute elements during conversion
 
 Most of the simple view inline elements like `<strong>` or `<em>` do not have any attributes. Some of them have just one, for instance `<a>` has its `href`.
 
-But it is easy to come up with features that style a part of a text in a more complex way. An example would be a {@link features/font Font family feature}. When used, it adds the `fontFamily` attribute to a text in the model, which is later converted to a `<span>` element with a corresponding `style` attribute.
+But it is easy to come up with features that style a part of a text in a more complex way. An example would be the {@link features/font font family feature}. When used, it adds the `fontFamily` attribute to the text in the model, which is later converted to a `<span>` element with a corresponding `style` attribute.
 
-So what would happen if several attributes are set on the same part of a text? Take this model example where `fontSize` is used next to `fontFamily`:
+So what would happen if several attributes were set on the same part of the text? Take this model example where `fontSize` is used next to `fontFamily`:
 
-```
+```html
 <paragraph>
 	<$text fontFamily="Tahoma" fontSize="big">foo</$text>
 </paragraph>
 ```
 
-Editor features are implemented in a granular way, which means that e.g. the font size converter is completely independent from the font family converter. This means that the above converts as follows:
+CKEditor 5 features are implemented in a granular way, which means that e.g. the font size converter is completely independent from the font family converter. This means that the above example is converted as follows:
 
 * `fontFamily="value"` converts to `<span style="font-family: value;">`,
 * `fontSize="value"` converts to `<span class="text-value">`.
 
-and, in theory, we could expect the following HTML as a result:
+And, in theory, you could expect the following HTML as a result:
 
 ```html
 <p>
@@ -104,7 +104,7 @@ and, in theory, we could expect the following HTML as a result:
 </p>
 ```
 
-But this is not the most optimal output we can get from the editor. Why not have just one `<span>` element instead?
+But this is not the most optimal output you can get from the rich-text editor. Why not have just one `<span>` element instead?
 
 ```html
 <p>
@@ -112,17 +112,17 @@ But this is not the most optimal output we can get from the editor. Why not have
 </p>
 ```
 
-Obviously a single `<span>` makes more sense. And thanks to the merging mechanism built in the conversion process, this would be the actual result of the conversion.
+Obviously a single `<span>` makes more sense. And thanks to the merging mechanism built into the conversion process, this would be the actual result of the conversion.
 
-Why is it so? In the above scenario, two model attributes are converted to `<span>` elements. When the first attribute (say, `fontFamily`) is converted, there is no `<span>` in the view yet. So the first `<span>` is added with the `style` attribute. But then, when `fontSize` is converted, the `<span>` is already in the view. The {@link module:engine/view/downcastwriter~DowncastWriter downcast writer} recognizes it and checks whether those elements can be merged, following these 3 rules:
+Why is it so? In the above scenario, two model attributes are converted to `<span>` elements. When the first attribute (say, `fontFamily`) is converted, there is no `<span>` in the view yet. So the first `<span>` is added with the `style` attribute. But then, when `fontSize` is converted, the `<span>` is already in the view. The {@link module:engine/view/downcastwriter~DowncastWriter downcast writer} recognizes it and checks whether the elements can be merged, following these 3 rules:
 
-1. both elements must have the same {@link module:engine/view/element~Element#name name},
-2. both elements must have the same {@link module:engine/view/attributeelement~AttributeElement#priority priority},
-3. neither can have an {@link module:engine/view/attributeelement~AttributeElement#id id}.
+1. Both elements must have the same {@link module:engine/view/element~Element#name name}.
+2. Both elements must have the same {@link module:engine/view/attributeelement~AttributeElement#priority priority}.
+3. Neither can have an {@link module:engine/view/attributeelement~AttributeElement#id ID}.
 
 ## Examples
 
 Once you understand more about the conversion of model attributes, you can check some examples of:
 
-* {@link framework/guides/deep-dive/conversion-extending-output How to extend the editor output} &mdash; extending the output of existing CKEditor 5 features.
-* {@link framework/guides/deep-dive/conversion-preserving-custom-content How to extend the editor with custom content} &mdash; how to make CKEditor 5 accept more content.
+* {@link framework/guides/deep-dive/conversion-extending-output Extending the editor output} &mdash; How to extend the output of existing CKEditor 5 features.
+* {@link framework/guides/deep-dive/conversion-preserving-custom-content Preserving custom content} &mdash; How to make CKEditor 5 accept more content.

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

@@ -8,7 +8,7 @@ order: 30
 
 # Preserving custom content
 
-In the {@link framework/guides/deep-dive/conversion-extending-output previous guide} we focused on post–processing of the editor data output. In this one, we will also extend the editor model so custom data can be loaded into it ({@link framework/guides/architecture/editing-engine#data-pipeline "upcasted"}). This will allow you not only to "correct" the editor output but, for instance, losslessly load data unsupported by editor features.
+The {@link framework/guides/deep-dive/conversion-extending-output previous guide} focused on post–processing of the CKEditor 5 data output. In this one, you will also extend the editor model so custom data can be loaded into it ({@link framework/guides/architecture/editing-engine#data-pipeline "upcasted"}). This will allow you not only to "correct" the editor output but, for instance, losslessly load data unsupported by the CKEditor 5 features.
 
 Eventually, this knowledge will allow you to create your custom features on top of the core features of CKEditor 5.
 
@@ -16,19 +16,19 @@ Eventually, this knowledge will allow you to create your custom features on top
 
 ### Code architecture
 
-It is recommended that the code that customizes editor data and editing pipelines is delivered as {@link framework/guides/architecture/core-editor-architecture#plugins plugins} and all examples in this chapter follow this convention.
+It is recommended that the code that customizes the editor data and editing pipelines is delivered as {@link framework/guides/architecture/core-editor-architecture#plugins plugins} and all examples in this guide follow this convention.
 
-Also for the sake of simplicity all examples use the same {@link module:editor-classic/classiceditor~ClassicEditor `ClassicEditor`} but keep in mind that code snippets will work with other editors too.
+Also for the sake of simplicity all examples use the same {@link module:editor-classic/classiceditor~ClassicEditor `ClassicEditor`}, but keep in mind that code snippets will work with other editors, too.
 
-Finally, none of the converters covered in this guide require to import any module from CKEditor 5 Framework, hence, you can write them without rebuilding the editor. In other words, such converters can easily be added to existing CKEditor 5 builds.
+Finally, none of the converters covered in this guide require to import any module from CKEditor 5 Framework, hence, you can write them without rebuilding the editor. In other words, such converters can easily be added to existing {@link builds/guides/overview CKEditor 5 builds}.
 
 ### CKEditor 5 inspector
 
-{@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} is an invaluable help with working with the model and view structures. It allows browsing their structure and checking selection positions like in typical browser dev tools. Make sure to enable CKEditor 5 inspector when playing with CKEditor 5.
+{@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} is an invaluable help when working with the model and view structures. It allows browsing their structure and checking selection positions like in typical browser developer tools. Make sure to enable the inspector when playing with CKEditor 5.
 
 ## Loading content with a custom attribute
 
-In this example links (`<a href="...">...</a>`) loaded in editor content will preserve their `target` attribute, which is not supported by the {@link features/link Link} feature. The DOM `target` attribute will be stored in the editor model as a `linkTarget` attribute.
+In this example links (`<a href="...">...</a>`) loaded into the editor content will preserve their `target` attribute, which is not supported by the {@link features/link Link} feature. The DOM `target` attribute will be stored in the editor model as a `linkTarget` attribute.
 
 Unlike the {@link framework/guides/deep-dive/conversion-extending-output#adding-an-html-attribute-to-certain-inline-elements downcast–only solution}, this approach does not change the content loaded into the editor. Links without the `target` attribute will not get one and links with the attribute will preserve its value.
 
@@ -55,7 +55,7 @@ Unlike the {@link framework/guides/deep-dive/conversion-extending-output#adding-
 
 {@snippet framework/extending-content-allow-link-target}
 
-Allowing the `target` attribute in the editor is made by two custom converters plugged into the downcast and "upcast" pipelines, following the default converters brought by the {@link features/link Link} feature:
+The `target` attribute in the editor is allowed thanks to two custom converters plugged into the "downcast" and "upcast" pipelines, following the default converters brought by the {@link features/link Link} feature:
 
 ```js
 function AllowLinkTarget( editor ) {
@@ -74,7 +74,7 @@ function AllowLinkTarget( editor ) {
 		converterPriority: 'low'
 	} );
 
-	// Tell the editor that <a target="..."></a> converts into "linkTarget" attribute in the model.
+	// Tell the editor that <a target="..."></a> converts into the "linkTarget" attribute in the model.
 	editor.conversion.for( 'upcast' ).attributeToAttribute( {
 		view: {
 			name: 'a',
@@ -119,38 +119,38 @@ a[target]::after {
 
 ## Loading content with all attributes
 
-In this example divs (`<div>...</div>`) loaded in editor content will preserve their attributes. All the DOM attributes will be stored in the editor model as corresponding attributes.
+In this example `<div>` elements (`<div>...</div>`) loaded into the editor content will preserve their attributes. All the DOM attributes will be stored in the editor model as corresponding attributes.
 
 {@snippet framework/extending-content-allow-div-attributes}
 
-Allowing all attributes on `div` elements is achieved by custom "upcast" and "downcast" converters that copies each attribute one by one.
+All attributes are allowed on `<div>` elements thanks to custom "upcast" and "downcast" converters that copy each attribute one by one.
 
-Allowing every possible attribute on div in the model is done by adding a {@link module:engine/model/schema~Schema#addAttributeCheck addAttributeCheck()} callback.
+Allowing every possible attribute on a `<div>` element in the model is done by adding an {@link module:engine/model/schema~Schema#addAttributeCheck addAttributeCheck()} callback.
 
 <info-box>
-	Allowing every attribute on `<div>` elements might introduce security issues - ise XSS attacks. The production code should use only application related attributes and/or properly encode data.
+	Allowing every attribute on `<div>` elements might introduce security issues &mdash; including XSS attacks. The production code should use only application-related attributes and/or properly encode data.
 </info-box>
 
-Adding "upcast" and "downcast" converters for the `<div>` element is enough for cases where its attributes does not change. If attributes in the model are modified those `elementToElement()` converters will not be called as `div` is already converted. To overcome this a lower-level API is used.
+Adding "upcast" and "downcast" converters for the `<div>` element is enough for cases where its attributes do not change. If the attributes in the model are modified, these `elementToElement()` converters will not be called as the `<div>` is already converted. To overcome this, a lower-level API is used.
 
-Instead of using predefined converters the {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event-attribute `attribute`} event listener is registered for "downcast" dispatcher.
+Instead of using predefined converters, the {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event-attribute `attribute`} event listener is registered for the "downcast" dispatcher.
 
 ```js
 function ConvertDivAttributes( editor ) {
-	// Allow divs in the model.
+	// Allow <div> elements in the model.
 	editor.model.schema.register( 'div', {
 		allowWhere: '$block',
 		allowContentOf: '$root'
 	} );
 
-	// Allow divs in the model to have all attributes.
+	// Allow <div> elements in the model to have all attributes.
 	editor.model.schema.addAttributeCheck( context => {
 		if ( context.endsWith( 'div' ) ) {
 			return true;
 		}
 	} );
 
-	// View-to-model converter converting a view div with all its attributes to the model.
+	// View-to-model converter converting a view <div> with all its attributes to the model.
 	editor.conversion.for( 'upcast' ).elementToElement( {
 		view: 'div',
 		model: ( viewElement, modelWriter ) => {
@@ -158,17 +158,17 @@ function ConvertDivAttributes( editor ) {
 		}
 	} );
 
-	// Model-to-view convert for the div element (attrbiutes are converted separately).
+	// Model-to-view converter for the <div> element (attrbiutes are converted separately).
 	editor.conversion.for( 'downcast' ).elementToElement( {
 		model: 'div',
 		view: 'div'
 	} );
 
-	// Model-to-view converter for div attributes.
-	// Note that we use a lower-level, event-based API here.
+	// Model-to-view converter for <div> attributes.
+	// Note that a lower-level, event-based API is used here.
 	editor.conversion.for( 'downcast' ).add( dispatcher => {
 		dispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
-			// Convert div attributes only.
+			// Convert <div> attributes only.
 			if ( data.item.name != 'div' ) {
 				return;
 			}
@@ -204,19 +204,19 @@ ClassicEditor
 	} );
 ```
 
-## Parse attribute values
+## Parsing attribute values
 
-Some features, like {@link features/font Font}, allows only specific values for inline attributes. In this example we'll add a converter that will parse any `font-size` value into one of defined values.
+Some features, like {@link features/font Font}, allow only specific values for inline attributes. In this example you will add a converter that will parse any `font-size` value into one of the defined values.
 
 {@snippet framework/extending-content-arbitrary-attribute-values}
 
-Parsing any font value to model requires writing adding custom "upcast" converter that will override default converter from `FontSize`. Unlike the default one, this converter parses values set in CSS nad sets them into the model.
+Parsing any font value to the model requires adding a custom "upcast" converter that will override the default converter from `FontSize`. Unlike the default one, this converter parses values set in CSS nad sets them into the model.
 
-As the default "downcast" converter only operates on pre-defined values we're also adding a model-to-view converter that simply outputs any model value to font-size using `px` units.
+As the default "downcast" converter only operates on pre-defined values, you will also add a model-to-view converter that simply outputs any model value to font size using `px` units.
 
 ```js
 function HandleFontSizeValue( editor ) {
-	// Add special catch-all converter for font-size feature.
+	// Add a special catch-all converter for the font size feature.
 	editor.conversion.for( 'upcast' ).elementToAttribute( {
 		view: {
 			name: 'span',
@@ -230,18 +230,18 @@ function HandleFontSizeValue( editor ) {
 				const value = parseFloat( viewElement.getStyle( 'font-size' ) ).toFixed( 0 );
 
 				// It might be needed to further convert the value to meet business requirements.
-				// In the sample the font-size is configured to handle only the sizes:
+				// In the sample the font size is configured to handle only the sizes:
 				// 12, 14, 'default', 18, 20, 22, 24, 26, 28, 30
 				// Other sizes will be converted to the model but the UI might not be aware of them.
 
-				// The font-size feature expects numeric values to be Number not String.
+				// The font size feature expects numeric values to be Number, not String.
 				return parseInt( value );
 			}
 		},
 		converterPriority: 'high'
 	} );
 
-	// Add special converter for font-size feature to convert all (even not configured)
+	// Add a special converter for the font size feature to convert all (even not configured)
 	// model attribute values.
 	editor.conversion.for( 'downcast' ).attributeToElement( {
 		model: {
@@ -278,18 +278,20 @@ ClassicEditor
 
 ## Adding extra attributes to elements contained in a figure
 
-The {@link features/image Image} and {@link features/table Table} features wraps view elements (`<img>` for Image nad `<table>` for Table) in `<figure>`. During the downcast conversion the model element is mapped to `<figure>` not the inner element. In such cases the default `conversion.attributeToAttribute()` conversion helpers could lost information on which element the attribute should be set. To overcome this limitation it is sufficient to write a custom converter that add custom attributes to elements already converted by base features. The key point is to add those converters with lower priority the base converters so they will be called after the base ones.
+The {@link features/image Image} and {@link features/table Table} features wrap view elements (`<img>` for Image nad `<table>` for Table) in `<figure>`. During the downcast conversion, the model element is mapped to `<figure>` and not the inner element. In such cases the default `conversion.attributeToAttribute()` conversion helpers could lose information about the element that the attribute should be set on.
+
+To overcome this limitation it is sufficient to write a custom converter that adds custom attributes to elements already converted by base features. The key point is to add these converters with a lower priority than the base converters so they will be called after the base ones.
 
 {@snippet framework/extending-content-custom-figure-attributes}
 
-The sample below is extensible - to add own attributes to preserve just add another `setupCustomAttributeConversion()` call with desired names.
+The sample below is extensible. To add your own attributes to preserve, just add another `setupCustomAttributeConversion()` call with desired names.
 
 ```js
 /**
  * Plugin that converts custom attributes for elements that are wrapped in <figure> in the view.
  */
 function CustomFigureAttributes( editor ) {
-	// Define on which elements the css classes should be preserved:
+	// Define on which elements the CSS classes should be preserved:
 	setupCustomClassConversion( 'img', 'image', editor );
 	setupCustomClassConversion( 'table', 'table', editor );
 
@@ -301,30 +303,29 @@ function CustomFigureAttributes( editor ) {
 }
 
 /**
- * Setups conversion that preservers classes on img/table elements
+ * Sets up a conversion that preservers classes on <img> and <table> elements.
  */
 function setupCustomClassConversion( viewElementName, modelElementName, editor ) {
-	// The 'customClass' attribute will store custom classes from data in the model so schema definitions to allow this attribute.
+	// The 'customClass' attribute will store custom classes from the data in the model so schema definitions allow this attribute.
 	editor.model.schema.extend( modelElementName, { allowAttributes: [ 'customClass' ] } );
 
-	// Define upcast converters for <img> and <table> elements with "low" priority so they are run after default converters.
+	// Define upcast converters for the <img> and <table> elements with a "low" priority so they are run after the default converters.
 	editor.conversion.for( 'upcast' ).add( upcastCustomClasses( viewElementName ), { priority: 'low' } );
 
-	// Define downcast converters for model element with "low" priority so they are run after default converters.
+	// Define downcast converters for a model element with a "low" priority so they are run after the default converters.
 	editor.conversion.for( 'downcast' ).add( downcastCustomClasses( modelElementName ), { priority: 'low' } );
 }
 
 /**
- * Setups conversion for custom attribute on view elements contained inside figure.
+ * Sets up a conversion for a custom attribute on view elements contained inside a <figure>.
  *
  * This method:
- *
- * - adds proper schema rules
- * - adds an upcast converter
- * - adds a downcast converter
+ * - Adds proper schema rules.
+ * - Adds an upcast converter.
+ * - Adds a downcast converter.
  */
 function setupCustomAttributeConversion( viewElementName, modelElementName, viewAttribute, editor ) {
-	// Extend schema to store attribute in the model.
+	// Extend the schema to store an attribute in the model.
 	const modelAttribute = `custom${ viewAttribute }`;
 
 	editor.model.schema.extend( modelElementName, { allowAttributes: [ modelAttribute ] } );
@@ -334,7 +335,7 @@ function setupCustomAttributeConversion( viewElementName, modelElementName, view
 }
 
 /**
- * Creates upcast converter that will pass all classes from view element to model element.
+ * Creates an upcast converter that will pass all classes from the view element to the model element.
  */
 function upcastCustomClasses( elementName ) {
 	return dispatcher => dispatcher.on( `element:${ elementName }`, ( evt, data, conversionApi ) => {
@@ -347,7 +348,7 @@ function upcastCustomClasses( elementName ) {
 			return;
 		}
 
-		// The upcast conversion pick up classes from base element and from figure element also so it should be extensible.
+		// The upcast conversion picks up classes from the base element and from the <figure> element so it should be extensible.
 		const currentAttributeValue = modelElement.getAttribute( 'customClass' ) || [];
 
 		currentAttributeValue.push( ...viewItem.getClassNames() );
@@ -357,9 +358,9 @@ function upcastCustomClasses( elementName ) {
 }
 
 /**
- * Creates downcast converter that add classes defined in `customClass` attribute to given view element.
+ * Creates a downcast converter that adds classes defined in the `customClass` attribute to a given view element.
  *
- * This converter expects that view element is nested in figure element.
+ * This converter expects that the view element is nested in a <figure> element.
  */
 function downcastCustomClasses( modelElementName ) {
 	return dispatcher => dispatcher.on( `insert:${ modelElementName }`, ( evt, data, conversionApi ) => {
@@ -371,10 +372,10 @@ function downcastCustomClasses( modelElementName ) {
 			return;
 		}
 
-		// The below code assumes that classes are set on <figure> element...
+		// The code below assumes that classes are set on the <figure> element...
 		conversionApi.writer.addClass( modelElement.getAttribute( 'customClass' ), viewFigure );
 
-		// ... but if you preferIf the classes should be passed to the <img> find the view element inside figure:
+		// ... but if you prefer the classes to be passed to the <img> element, find the view element inside the <figure>:
 		//
 		// const viewElement = findViewChild( viewFigure, viewElementName, conversionApi );
 		//
@@ -383,7 +384,7 @@ function downcastCustomClasses( modelElementName ) {
 }
 
 /**
- * Helper method that search for given view element in all children of model element.
+ * Helper method that searches for a given view element in all children of the model element.
  *
  * @param {module:engine/view/item~Item} viewElement
  * @param {String} viewElementName
@@ -397,7 +398,7 @@ function findViewChild( viewElement, viewElementName, conversionApi ) {
 }
 
 /**
- * Returns custom attribute upcast converter.
+ * Returns the custom attribute upcast converter.
  */
 function upcastAttribute( viewElementName, viewAttribute, modelAttribute ) {
 	return dispatcher => dispatcher.on( `element:${ viewElementName }`, ( evt, data, conversionApi ) => {
@@ -415,7 +416,7 @@ function upcastAttribute( viewElementName, viewAttribute, modelAttribute ) {
 }
 
 /**
- * Returns custom attribute downcast converter.
+ * Returns the custom attribute downcast converter.
  */
 function downcastAttribute( modelElementName, viewElementName, viewAttribute, modelAttribute ) {
 	return dispatcher => dispatcher.on( `insert:${ modelElementName }`, ( evt, data, conversionApi ) => {
@@ -447,3 +448,7 @@ ClassicEditor
 		console.error( err.stack );
 	} );
 ```
+
+## What's next?
+
+If you would like to read more about how to extend the output of existing CKEditor 5 features, refer to the {@link framework/guides/deep-dive/conversion-extending-output Extending the editor output} guide.

+ 26 - 17
packages/ckeditor5-engine/src/model/model.js

@@ -24,6 +24,7 @@ import deleteContent from './utils/deletecontent';
 import modifySelection from './utils/modifyselection';
 import getSelectedContent from './utils/getselectedcontent';
 import { injectSelectionPostFixer } from './utils/selection-post-fixer';
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 
 /**
  * Editor's data model. Read about the model in the
@@ -153,14 +154,18 @@ export default class Model {
 	 * @returns {*} Value returned by the callback.
 	 */
 	change( callback ) {
-		if ( this._pendingChanges.length === 0 ) {
-			// If this is the outermost block, create a new batch and start `_runPendingChanges` execution flow.
-			this._pendingChanges.push( { batch: new Batch(), callback } );
-
-			return this._runPendingChanges()[ 0 ];
-		} else {
-			// If this is not the outermost block, just execute the callback.
-			return callback( this._currentWriter );
+		try {
+			if ( this._pendingChanges.length === 0 ) {
+				// If this is the outermost block, create a new batch and start `_runPendingChanges` execution flow.
+				this._pendingChanges.push( { batch: new Batch(), callback } );
+
+				return this._runPendingChanges()[ 0 ];
+			} else {
+				// If this is not the outermost block, just execute the callback.
+				return callback( this._currentWriter );
+			}
+		} catch ( err ) {
+			CKEditorError.rethrowUnexpectedError( err, this );
 		}
 	}
 
@@ -198,17 +203,21 @@ export default class Model {
 	 * @param {Function} callback Callback function which may modify the model.
 	 */
 	enqueueChange( batchOrType, callback ) {
-		if ( typeof batchOrType === 'string' ) {
-			batchOrType = new Batch( batchOrType );
-		} else if ( typeof batchOrType == 'function' ) {
-			callback = batchOrType;
-			batchOrType = new Batch();
-		}
+		try {
+			if ( typeof batchOrType === 'string' ) {
+				batchOrType = new Batch( batchOrType );
+			} else if ( typeof batchOrType == 'function' ) {
+				callback = batchOrType;
+				batchOrType = new Batch();
+			}
 
-		this._pendingChanges.push( { batch: batchOrType, callback } );
+			this._pendingChanges.push( { batch: batchOrType, callback } );
 
-		if ( this._pendingChanges.length == 1 ) {
-			this._runPendingChanges();
+			if ( this._pendingChanges.length == 1 ) {
+				this._runPendingChanges();
+			}
+		} catch ( err ) {
+			CKEditorError.rethrowUnexpectedError( err, this );
 		}
 	}
 

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

@@ -224,7 +224,11 @@ export default class DomConverter {
 				return domElement;
 			} else {
 				// Create DOM element.
-				domElement = domDocument.createElement( viewNode.name );
+				if ( viewNode.hasAttribute( 'xmlns' ) ) {
+					domElement = domDocument.createElementNS( viewNode.getAttribute( 'xmlns' ), viewNode.name );
+				} else {
+					domElement = domDocument.createElement( viewNode.name );
+				}
 
 				if ( options.bind ) {
 					this.bindElements( domElement, viewNode );

+ 24 - 20
packages/ckeditor5-engine/src/view/view.js

@@ -457,29 +457,33 @@ export default class View {
 			);
 		}
 
-		// Recursive call to view.change() method - execute listener immediately.
-		if ( this._ongoingChange ) {
-			return callback( this._writer );
-		}
-
-		// This lock will assure that all recursive calls to view.change() will end up in same block - one "render"
-		// event for all nested calls.
-		this._ongoingChange = true;
-		const callbackResult = callback( this._writer );
-		this._ongoingChange = false;
+		try {
+			// Recursive call to view.change() method - execute listener immediately.
+			if ( this._ongoingChange ) {
+				return callback( this._writer );
+			}
 
-		// This lock is used by editing controller to render changes from outer most model.change() once. As plugins might call
-		// view.change() inside model.change() block - this will ensures that postfixers and rendering are called once after all changes.
-		// Also, we don't need to render anything if there're no changes since last rendering.
-		if ( !this._renderingDisabled && this._hasChangedSinceTheLastRendering ) {
-			this._postFixersInProgress = true;
-			this.document._callPostFixers( this._writer );
-			this._postFixersInProgress = false;
+			// This lock will assure that all recursive calls to view.change() will end up in same block - one "render"
+			// event for all nested calls.
+			this._ongoingChange = true;
+			const callbackResult = callback( this._writer );
+			this._ongoingChange = false;
+
+			// This lock is used by editing controller to render changes from outer most model.change() once. As plugins might call
+			// view.change() inside model.change() block - this will ensures that postfixers and rendering are called once after all
+			// changes. Also, we don't need to render anything if there're no changes since last rendering.
+			if ( !this._renderingDisabled && this._hasChangedSinceTheLastRendering ) {
+				this._postFixersInProgress = true;
+				this.document._callPostFixers( this._writer );
+				this._postFixersInProgress = false;
+
+				this.fire( 'render' );
+			}
 
-			this.fire( 'render' );
+			return callbackResult;
+		} catch ( err ) {
+			CKEditorError.rethrowUnexpectedError( err, this );
 		}
-
-		return callbackResult;
 	}
 
 	/**

+ 54 - 0
packages/ckeditor5-engine/tests/model/model.js

@@ -12,6 +12,8 @@ import ModelSelection from '../../src/model/selection';
 import ModelDocumentFragment from '../../src/model/documentfragment';
 import Batch from '../../src/model/batch';
 import { getData, setData, stringify } from '../../src/dev-utils/model';
+import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 
 describe( 'Model', () => {
 	let model, schema, changes;
@@ -321,6 +323,58 @@ describe( 'Model', () => {
 				expect( writer.batch.type ).to.equal( 'transparent' );
 			} );
 		} );
+
+		it( 'should catch a non-ckeditor error inside the `change()` block and throw the CKEditorError error outside of it', () => {
+			const error = new TypeError( 'foo' );
+			error.stack = 'bar';
+
+			expectToThrowCKEditorError( () => {
+				model.change( () => {
+					throw error;
+				} );
+			}, /unexpected-error/, model, {
+				originalError: {
+					message: 'foo',
+					stack: 'bar',
+					name: 'TypeError'
+				}
+			} );
+		} );
+
+		it( 'should throw the original CKEditorError error if it was thrown inside the `change()` block', () => {
+			expectToThrowCKEditorError( () => {
+				model.change( () => {
+					throw new CKEditorError( 'foo', null, { foo: 1 } );
+				} );
+			}, /foo/, null, { foo: 1 } );
+		} );
+
+		it( 'should catch a non-ckeditor error inside the `enqueueChange()` block and throw the CKEditorError error outside of it', () => {
+			const error = new TypeError( 'foo' );
+			error.stack = 'bar';
+
+			expectToThrowCKEditorError( () => {
+				model.enqueueChange( () => {
+					throw error;
+				} );
+			}, /unexpected-error/, model, {
+				originalError: {
+					message: 'foo',
+					stack: 'bar',
+					name: 'TypeError'
+				}
+			} );
+		} );
+
+		it( 'should throw the original CKEditorError error if it was thrown inside the `enqueueChange()` block', () => {
+			const err = new CKEditorError( 'foo', null, { foo: 1 } );
+
+			expectToThrowCKEditorError( () => {
+				model.enqueueChange( () => {
+					throw err;
+				} );
+			}, /foo/, null, { foo: 1 } );
+		} );
 	} );
 
 	describe( 'applyOperation()', () => {

+ 9 - 0
packages/ckeditor5-engine/tests/view/domconverter/view-to-dom.js

@@ -174,6 +174,15 @@ describe( 'DomConverter', () => {
 			expect( domTextNode.data ).to.equal( 'foo' );
 		} );
 
+		it( 'should create namespaced elements', () => {
+			const namespace = 'http://www.w3.org/2000/svg';
+			const viewSvg = new ViewElement( 'svg', { xmlns: namespace } );
+
+			const domSvg = converter.viewToDom( viewSvg, document );
+
+			expect( domSvg.createSVGRect ).to.be.a( 'function' );
+		} );
+
 		describe( 'it should convert spaces to &nbsp;', () => {
 			it( 'at the beginning of each container element', () => {
 				const viewDiv = new ViewContainerElement( 'div', null, [

+ 26 - 0
packages/ckeditor5-engine/tests/view/view/view.js

@@ -25,6 +25,7 @@ import createViewRoot from '../_utils/createroot';
 import createElement from '@ckeditor/ckeditor5-utils/src/dom/createelement';
 import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
 import env from '@ckeditor/ckeditor5-utils/src/env';
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 
 describe( 'view', () => {
 	const DEFAULT_OBSERVERS_COUNT = 6;
@@ -802,6 +803,31 @@ describe( 'view', () => {
 			expect( result2 ).to.equal( true );
 			expect( result3 ).to.undefined;
 		} );
+
+		it( 'should catch native errors and wrap them into the CKEditorError errors', () => {
+			const error = new TypeError( 'foo' );
+			error.stack = 'bar';
+
+			expectToThrowCKEditorError( () => {
+				view.change( () => {
+					throw error;
+				} );
+			}, /unexpected-error/, view, {
+				originalError: {
+					message: 'foo',
+					stack: 'bar',
+					name: 'TypeError'
+				}
+			} );
+		} );
+
+		it( 'should rethrow custom CKEditorError errors', () => {
+			expectToThrowCKEditorError( () => {
+				view.change( () => {
+					throw new CKEditorError( 'foo', view );
+				} );
+			}, /foo/, view );
+		} );
 	} );
 
 	describe( 'createPositionAt()', () => {