diff --git a/build/ckeditor.js b/build/ckeditor.js
index 4adcc3dc29..c5a2206286 100644
--- a/build/ckeditor.js
+++ b/build/ckeditor.js
@@ -2,5 +2,5 @@
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/
-!function(t){t.en=Object.assign(t.en||{},{a:"Choose heading",b:"Heading",c:"Numbered List",d:"Bulleted List",e:"Block quote",f:"Bold",g:"Italic",h:"Link",i:"Paragraph",j:"Heading 1",k:"Heading 2",l:"Heading 3",m:"Heading 4",n:"Heading 5",o:"Heading 6",p:"Save",q:"Cancel",r:"Link URL",s:"Unlink",t:"Edit link",u:"Open link in new tab",v:"This link has no URL",w:"Rich Text Editor",x:"Rich Text Editor, %0",y:"Undo",z:"Redo"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})),function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.UserVoiceEditor=e():t.UserVoiceEditor=e()}(window,function(){return function(t){var e={};function n(o){if(e[o])return e[o].exports;var i=e[o]={i:o,l:!1,exports:{}};return t[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,o){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(o,i,function(e){return t[e]}.bind(null,i));return o},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=76)}([function(t,e,n){"use strict";n.d(e,"b",function(){return i}),n.d(e,"a",function(){return r});const o="https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html";class i extends Error{constructor(t,e){t=r(t),e&&(t+=" "+JSON.stringify(e)),super(t),this.name="CKEditorError",this.data=e}static isCKEditorError(t){return t instanceof i}}function r(t){const e=t.match(/^([^:]+):/);return e?t+` Read more: ${o}#error-${e[1]}\n`:t}},function(t,e,n){"use strict";var o=n(0);const i={error(t,e){console.error(Object(o.a)(t),e)},warn(t,e){console.warn(Object(o.a)(t),e)}};e.a=i},,function(t,e,n){var o={},i=function(t){var e;return function(){return void 0===e&&(e=t.apply(this,arguments)),e}}(function(){return window&&document&&document.all&&!window.atob}),r=function(t){var e={};return function(t,n){if("function"==typeof t)return t();if(void 0===e[t]){var o=function(t,e){return e?e.querySelector(t):document.querySelector(t)}.call(this,t,n);if(window.HTMLIFrameElement&&o instanceof window.HTMLIFrameElement)try{o=o.contentDocument.head}catch(t){o=null}e[t]=o}return e[t]}}(),s=null,a=0,c=[],l=n(30);function h(t,e){for(var n=0;n
").replace(/\n/g,"
").replace(/^\s/," ").replace(/\s$/," ").replace(/\s\s/g," ")).indexOf("
")>-1&&(t=`
${t}
`),t}(o.getData("text/plain"))),i=this._htmlDataProcessor.toView(i),this.fire("inputTransformation",{content:i,dataTransfer:o}),n.scrollToTheSelection()},{priority:"low"}),this.listenTo(this,"inputTransformation",(t,e)=>{if(!e.content.isEmpty){const t=this.editor.data,n=this.editor.model,o=t.toModel(e.content,"$clipboardHolder");if(0==o.childCount)return;n.insertContent(o)}},{priority:"low"}),this.listenTo(o,"copy",i,{priority:"low"}),this.listenTo(o,"cut",(e,n)=>{t.isReadOnly?n.preventDefault():i(e,n)},{priority:"low"}),this.listenTo(o,"clipboardOutput",(n,o)=>{o.content.isEmpty||(o.dataTransfer.setData("text/html",this._htmlDataProcessor.toData(o.content)),o.dataTransfer.setData("text/plain",function t(e){let n="";if(e.is("text")||e.is("textProxy"))n=e.data;else if(e.is("img")&&e.hasAttribute("alt"))n=e.getAttribute("alt");else{let o=null;for(const i of e.getChildren()){const e=t(i);o&&(o.is("containerElement")||i.is("containerElement"))&&(Gl.includes(o.name)||Gl.includes(i.name)?n+="\n":n+="\n\n"),n+=e,o=i}}return n}(o.content))),"cut"==o.method&&t.model.deleteContent(e.selection)},{priority:"low"})}}class Ql{constructor(t){this.editor=t,this.set("value",void 0),this.set("isEnabled",!1),this.decorate("execute"),this.listenTo(this.editor.model.document,"change",()=>{this.refresh()}),this.on("execute",t=>{this.isEnabled||t.stop()},{priority:"high"}),this.listenTo(t,"change:isReadOnly",(t,e,n)=>{n?(this.on("set:isEnabled",Yl,{priority:"highest"}),this.isEnabled=!1):(this.off("set:isEnabled",Yl),this.refresh())})}refresh(){this.isEnabled=!0}execute(){}destroy(){this.stopListening()}}function Yl(t){t.return=!1,t.stop()}co(Ql,zo);class Xl extends Ql{execute(){const t=this.editor.model,e=t.document;t.change(n=>{!function(t,e,n,o){const i=n.isCollapsed,r=n.getFirstRange(),s=r.start.parent,a=r.end.parent;if(o.isLimit(s)||o.isLimit(a))return void(i||s!=a||t.deleteContent(n));if(i)Zl(e,r.start);else{const o=!(r.start.isAtStart&&r.end.isAtEnd),i=s==a;t.deleteContent(n,{leaveUnmerged:o}),o&&(i?Zl(e,n.focus):e.setSelection(a,0))}}(this.editor.model,n,e.selection,t.schema),this.fire("afterExecute",{writer:n})})}}function Zl(t,e){t.split(e),t.setSelection(e.parent.nextSibling,0)}class th extends dr{constructor(t){super(t);const e=this.document;e.on("keydown",(t,n)=>{if(this.isEnabled&&n.keyCode==bi.enter){let o;e.once("enter",t=>o=t,{priority:"highest"}),e.fire("enter",new Xr(e,n.domEvent,{isSoft:n.shiftKey})),o&&o.stop.called&&t.stop()}})}observe(){}}class eh extends $l{static get pluginName(){return"Enter"}init(){const t=this.editor,e=t.editing.view,n=e.document;e.addObserver(th),t.commands.add("enter",new Xl(t)),this.listenTo(n,"enter",(n,o)=>{o.preventDefault(),o.isSoft||(t.execute("enter"),e.scrollToTheSelection())},{priority:"low"})}}class nh extends Ql{execute(){const t=this.editor.model,e=t.document;t.change(n=>{!function(t,e,n){const o=n.isCollapsed,i=n.getFirstRange(),r=i.start.parent,s=i.end.parent,a=r==s;if(o)oh(e,i.end);else{const o=!(i.start.isAtStart&&i.end.isAtEnd);t.deleteContent(n,{leaveUnmerged:o}),a?oh(e,n.focus):o&&e.setSelection(s,0)}}(t,n,e.selection),this.fire("afterExecute",{writer:n})})}refresh(){const t=this.editor.model,e=t.document;this.isEnabled=function(t,e){if(e.rangeCount>1)return!1;const n=e.anchor;if(!n||!t.checkChild(n,"softBreak"))return!1;const o=e.getFirstRange(),i=o.start.parent,r=o.end.parent;if((ih(i,t)||ih(r,t))&&i!==r)return!1;return!0}(t.schema,e.selection)}}function oh(t,e){const n=t.createElement("softBreak");t.insert(n,e),t.setSelection(n,"after")}function ih(t,e){return!t.is("rootElement")&&(e.isLimit(t)||ih(t.parent,e))}class rh extends $l{static get pluginName(){return"ShiftEnter"}init(){const t=this.editor,e=t.model.schema,n=t.conversion,o=t.editing.view,i=o.document;e.register("softBreak",{allowWhere:"$text",isInline:!0}),n.for("upcast").elementToElement({model:"softBreak",view:"br"}),n.for("downcast").elementToElement({model:"softBreak",view:(t,e)=>e.createEmptyElement("br")}),o.addObserver(th),t.commands.add("shiftEnter",new nh(t)),this.listenTo(i,"enter",(e,n)=>{n.preventDefault(),n.isSoft&&(t.execute("shiftEnter"),o.scrollToTheSelection())},{priority:"low"})}}class sh{constructor(t,e=20){this.model=t,this.size=0,this.limit=e,this.isLocked=!1,this._changeCallback=((t,e)=>{"transparent"!=e.type&&e!==this._batch&&this._reset(!0)}),this._selectionChangeCallback=(()=>{this._reset()}),this.model.document.on("change",this._changeCallback),this.model.document.selection.on("change:range",this._selectionChangeCallback),this.model.document.selection.on("change:attribute",this._selectionChangeCallback)}get batch(){return this._batch||(this._batch=this.model.createBatch()),this._batch}input(t){this.size+=t,this.size>=this.limit&&this._reset(!0)}lock(){this.isLocked=!0}unlock(){this.isLocked=!1}destroy(){this.model.document.off("change",this._changeCallback),this.model.document.selection.off("change:range",this._selectionChangeCallback),this.model.document.selection.off("change:attribute",this._selectionChangeCallback)}_reset(t){this.isLocked&&!t||(this._batch=null,this.size=0)}}class ah extends Ql{constructor(t,e){super(t),this._buffer=new sh(t.model,e)}get buffer(){return this._buffer}destroy(){super.destroy(),this._buffer.destroy()}execute(t={}){const e=this.editor.model,n=e.document,o=t.text||"",i=o.length,r=t.range||n.selection.getFirstRange(),s=t.resultRange;e.enqueueChange(this._buffer.batch,t=>{const a=r.isCollapsed;this._buffer.lock(),a||e.deleteContent(e.createSelection(r)),o&&t.insertText(o,n.selection.getAttributes(),r.start),s?t.setSelection(s):a&&t.setSelection(r.start.getShiftedBy(i)),this._buffer.unlock(),this._buffer.input(i)})}}function ch(t){let e=null;const n=t.model,o=t.editing.view,i=t.commands.get("input");function r(){const t=i.buffer;t.lock(),n.enqueueChange(t.batch,()=>{n.deleteContent(n.document.selection)}),t.unlock()}o.document.on("keydown",(t,s)=>(function(t){const s=n.document,a=o.document.isComposing,c=e&&e.isEqual(s.selection);if(e=null,!i.isEnabled)return;if(function(t){if(t.ctrlKey)return!0;return lh.includes(t.keyCode)}(t)||s.selection.isCollapsed)return;if(a&&229===t.keyCode)return;if(!a&&229===t.keyCode&&c)return;r()})(s),{priority:"lowest"}),o.document.on("compositionstart",function(){const t=n.document,e=1!==t.selection.rangeCount||t.selection.getFirstRange().isFlat;if(t.selection.isCollapsed||e)return;r()},{priority:"lowest"}),o.document.on("compositionend",()=>{e=n.createSelection(n.document.selection)},{priority:"lowest"})}const lh=[wi("arrowUp"),wi("arrowRight"),wi("arrowDown"),wi("arrowLeft"),9,16,17,18,19,20,27,33,34,35,36,45,91,93,144,145,173,174,175,176,177,178,179,255];for(let t=112;t<=135;t++)lh.push(t);function hh(t){if(0==t.length)return!1;for(const e of t)if("children"===e.type&&!dh(e))return!0;return!1}function dh(t){if(t.newChildren.length-t.oldChildren.length!=1)return;const e=function(t,e){const n=[];let o,i=0;return t.forEach(t=>{"equal"==t?(r(),i++):"insert"==t?(s("insert")?o.values.push(e[i]):(r(),o={type:"insert",index:i,values:[e[i]]}),i++):s("delete")?o.howMany++:(r(),o={type:"delete",index:i,howMany:1})}),r(),n;function r(){o&&(n.push(o),o=null)}function s(t){return o&&o.type==t}}(Gi(t.oldChildren,t.newChildren,uh),t.newChildren);if(e.length>1)return;const n=e[0];return n.values[0]&&n.values[0].is("text")?n:void 0}function uh(t,e){return t&&t.is("text")&&e&&e.is("text")?t.data===e.data:t===e}class fh{constructor(t){this.editor=t,this.editing=this.editor.editing}handle(t,e){if(hh(t))this._handleContainerChildrenMutations(t,e);else for(const n of t)this._handleTextMutation(n,e),this._handleTextNodeInsertion(n)}_handleContainerChildrenMutations(t,e){const n=function(t){const e=t.map(t=>t.node).reduce((t,e)=>t.getCommonAncestor(e,{includeSelf:!0}));if(!e)return;return e.getAncestors({includeSelf:!0,parentFirst:!0}).find(t=>t.is("containerElement")||t.is("rootElement"))}(t);if(!n)return;const o=this.editor.editing.view.domConverter.mapViewToDom(n),i=new ir,r=this.editor.data.toModel(i.domToView(o)).getChild(0),s=this.editor.editing.mapper.toModelElement(n);if(!s)return;const a=Array.from(r.getChildren()),c=Array.from(s.getChildren()),l=a[a.length-1],h=c[c.length-1];l&&l.is("softBreak")&&h&&!h.is("softBreak")&&a.pop();const d=this.editor.model.schema;if(!ph(a,d)||!ph(c,d))return;const u=a.map(t=>t.is("text")?t.data:"@").join("").replace(/\u00A0/g," "),f=c.map(t=>t.is("text")?t.data:"@").join("").replace(/\u00A0/g," ");if(f===u)return;const p=Gi(f,u),{firstChangeAt:m,insertions:g,deletions:b}=mh(p);let w=null;e&&(w=this.editing.mapper.toModelRange(e.getFirstRange()));const _=u.substr(m,g),k=this.editor.model.createRange(this.editor.model.createPositionAt(s,m),this.editor.model.createPositionAt(s,m+b));this.editor.execute("input",{text:_,range:k,resultRange:w})}_handleTextMutation(t,e){if("text"!=t.type)return;const n=t.newText.replace(/\u00A0/g," "),o=t.oldText.replace(/\u00A0/g," ");if(o===n)return;const i=Gi(o,n),{firstChangeAt:r,insertions:s,deletions:a}=mh(i);let c=null;e&&(c=this.editing.mapper.toModelRange(e.getFirstRange()));const l=this.editing.view.createPositionAt(t.node,r),h=this.editing.mapper.toModelPosition(l),d=this.editor.model.createRange(h,h.getShiftedBy(a)),u=n.substr(r,s);this.editor.execute("input",{text:u,range:d,resultRange:c})}_handleTextNodeInsertion(t){if("children"!=t.type)return;const e=dh(t),n=this.editing.view.createPositionAt(t.node,e.index),o=this.editing.mapper.toModelPosition(n),i=e.values[0].data;this.editor.execute("input",{text:i.replace(/\u00A0/g," "),range:this.editor.model.createRange(o)})}}function ph(t,e){return t.every(t=>e.isInline(t))}function mh(t){let e=null,n=null;for(let o=0;oab
c[d
e]f
\n\t\t//e[f
a]b
cd
e[f
a]b
cd
ab
c[d
e]f
-->ab
{c[d
e]f
\n\t\t//\n\t\t// This special case is applied only if the range is to be kept together (not spread).\n\t\tconst moveRange = Range._createFromPositionAndShift( sourcePosition, howMany );\n\t\tconst insertPosition = targetPosition._getTransformedByDeletion( sourcePosition, howMany );\n\n\t\tif ( this.containsPosition( targetPosition ) && !spread ) {\n\t\t\tif ( moveRange.containsPosition( this.start ) || moveRange.containsPosition( this.end ) ) {\n\t\t\t\tconst start = this.start._getTransformedByMove( sourcePosition, targetPosition, howMany );\n\t\t\t\tconst end = this.end._getTransformedByMove( sourcePosition, targetPosition, howMany );\n\n\t\t\t\treturn [ new Range( start, end ) ];\n\t\t\t}\n\t\t}\n\n\t\t// Default algorithm.\n\t\tlet result;\n\n\t\tconst differenceSet = this.getDifference( moveRange );\n\t\tlet difference = null;\n\n\t\tconst common = this.getIntersection( moveRange );\n\n\t\tif ( differenceSet.length == 1 ) {\n\t\t\t// `moveRange` and this range may intersect but may be separate.\n\t\t\tdifference = new Range(\n\t\t\t\tdifferenceSet[ 0 ].start._getTransformedByDeletion( sourcePosition, howMany ),\n\t\t\t\tdifferenceSet[ 0 ].end._getTransformedByDeletion( sourcePosition, howMany )\n\t\t\t);\n\t\t} else if ( differenceSet.length == 2 ) {\n\t\t\t// `moveRange` is inside this range.\n\t\t\tdifference = new Range(\n\t\t\t\tthis.start,\n\t\t\t\tthis.end._getTransformedByDeletion( sourcePosition, howMany )\n\t\t\t);\n\t\t} // else, `moveRange` contains this range.\n\n\t\tif ( difference ) {\n\t\t\tresult = difference._getTransformedByInsertion( insertPosition, howMany, common !== null || spread );\n\t\t} else {\n\t\t\tresult = [];\n\t\t}\n\n\t\tif ( common ) {\n\t\t\tconst transformedCommon = new Range(\n\t\t\t\tcommon.start._getCombined( moveRange.start, insertPosition ),\n\t\t\t\tcommon.end._getCombined( moveRange.start, insertPosition )\n\t\t\t);\n\n\t\t\tif ( result.length == 2 ) {\n\t\t\t\tresult.splice( 1, 0, transformedCommon );\n\t\t\t} else {\n\t\t\t\tresult.push( transformedCommon );\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Returns a copy of this range that is transformed by deletion of `howMany` nodes from `deletePosition`.\n\t *\n\t * If the deleted range is intersecting with the transformed range, the transformed range will be shrank.\n\t *\n\t * If the deleted range contains transformed range, `null` will be returned.\n\t *\n\t * @protected\n\t * @param {module:engine/model/position~Position} deletionPosition Position from which nodes are removed.\n\t * @param {Number} howMany How many nodes are removed.\n\t * @returns {module:engine/model/range~Range|null} Result of the transformation.\n\t */\n\t_getTransformedByDeletion( deletePosition, howMany ) {\n\t\tlet newStart = this.start._getTransformedByDeletion( deletePosition, howMany );\n\t\tlet newEnd = this.end._getTransformedByDeletion( deletePosition, howMany );\n\n\t\tif ( newStart == null && newEnd == null ) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif ( newStart == null ) {\n\t\t\tnewStart = deletePosition;\n\t\t}\n\n\t\tif ( newEnd == null ) {\n\t\t\tnewEnd = deletePosition;\n\t\t}\n\n\t\treturn new Range( newStart, newEnd );\n\t}\n\n\t/**\n\t * Creates a new range, spreading from specified {@link module:engine/model/position~Position position} to a position moved by\n\t * given `shift`. If `shift` is a negative value, shifted position is treated as the beginning of the range.\n\t *\n\t * @protected\n\t * @param {module:engine/model/position~Position} position Beginning of the range.\n\t * @param {Number} shift How long the range should be.\n\t * @returns {module:engine/model/range~Range}\n\t */\n\tstatic _createFromPositionAndShift( position, shift ) {\n\t\tconst start = position;\n\t\tconst end = position.getShiftedBy( shift );\n\n\t\treturn shift > 0 ? new this( start, end ) : new this( end, start );\n\t}\n\n\t/**\n\t * Creates a range inside an {@link module:engine/model/element~Element element} which starts before the first child of\n\t * that element and ends after the last child of that element.\n\t *\n\t * @protected\n\t * @param {module:engine/model/element~Element} element Element which is a parent for the range.\n\t * @returns {module:engine/model/range~Range}\n\t */\n\tstatic _createIn( element ) {\n\t\treturn new this( Position._createAt( element, 0 ), Position._createAt( element, element.maxOffset ) );\n\t}\n\n\t/**\n\t * Creates a range that starts before given {@link module:engine/model/item~Item model item} and ends after it.\n\t *\n\t * @protected\n\t * @param {module:engine/model/item~Item} item\n\t * @returns {module:engine/model/range~Range}\n\t */\n\tstatic _createOn( item ) {\n\t\treturn this._createFromPositionAndShift( Position._createBefore( item ), item.offsetSize );\n\t}\n\n\t/**\n\t * Combines all ranges from the passed array into a one range. At least one range has to be passed.\n\t * Passed ranges must not have common parts.\n\t *\n\t * The first range from the array is a reference range. If other ranges start or end on the exactly same position where\n\t * the reference range, they get combined into one range.\n\t *\n\t *\t\t[ ][] [ ][ ][ ][ ][] [ ] // Passed ranges, shown sorted\n\t *\t\t[ ] // The result of the function if the first range was a reference range.\n\t *\t [ ] // The result of the function if the third-to-seventh range was a reference range.\n\t *\t [ ] // The result of the function if the last range was a reference range.\n\t *\n\t * @param {Array.fooba|r
// _toModelOffset( b, 2, p ) -> 5\n\t *\n\t * Is a sum of:\n\t *\n\t *\t\tfoo|bar
// _toModelOffset( p, 3, p ) -> 3\n\t *\t\tfooba|r
// _toModelOffset( b, 2, b ) -> 2\n\t *\n\t * @private\n\t * @param {module:engine/view/element~Element} viewParent Position parent.\n\t * @param {Number} viewOffset Position offset.\n\t * @param {module:engine/view/element~Element} viewBlock Block used as a base to calculate offset.\n\t * @returns {Number} Offset in the model.\n\t */\n\t_toModelOffset( viewParent, viewOffset, viewBlock ) {\n\t\tif ( viewBlock != viewParent ) {\n\t\t\t// See example.\n\t\t\tconst offsetToParentStart = this._toModelOffset( viewParent.parent, viewParent.index, viewBlock );\n\t\t\tconst offsetInParent = this._toModelOffset( viewParent, viewOffset, viewParent );\n\n\t\t\treturn offsetToParentStart + offsetInParent;\n\t\t}\n\n\t\t// viewBlock == viewParent, so we need to calculate the offset in the parent element.\n\n\t\t// If the position is a text it is simple (\"ba|r\" -> 2).\n\t\tif ( viewParent.is( 'text' ) ) {\n\t\t\treturn viewOffset;\n\t\t}\n\n\t\t// If the position is in an element we need to sum lengths of siblings ( bar foo | -> 3 + 3 = 6 ).\n\t\tlet modelOffset = 0;\n\n\t\tfor ( let i = 0; i < viewOffset; i++ ) {\n\t\t\tmodelOffset += this.getModelLength( viewParent.getChild( i ) );\n\t\t}\n\n\t\treturn modelOffset;\n\t}\n\n\t/**\n\t * Gets the length of the view element in the model.\n\t *\n\t * The length is calculated as follows:\n\t * * if {@link #registerViewToModelLength length mapping callback} is provided for given `viewNode` it is used to\n\t * evaluate model length (`viewNode` is used as first and only parameter passed to the callback),\n\t * * length of a {@link module:engine/view/text~Text text node} is equal to the length of it's\n\t * {@link module:engine/view/text~Text#data data},\n\t * * length of a {@link module:engine/view/uielement~UIElement ui element} is equal to 0,\n\t * * length of a mapped {@link module:engine/view/element~Element element} is equal to 1,\n\t * * length of a not-mapped {@link module:engine/view/element~Element element} is equal to the length of it's children.\n\t *\n\t * Examples:\n\t *\n\t *\t\tfoo -> 3 // Text length is equal to it's data length.\n\t *\t\tfoo
-> 1 // Length of an element which is mapped is by default equal to 1.\n\t *\t\tfoo -> 3 // Length of an element which is not mapped is a length of its children.\n\t *\t\tx
y
are mapped.\n\t *\n\t * @param {module:engine/view/element~Element} viewNode View node.\n\t * @returns {Number} Length of the node in the tree model.\n\t */\n\tgetModelLength( viewNode ) {\n\t\tif ( this._viewToModelLengthCallbacks.get( viewNode.name ) ) {\n\t\t\tconst callback = this._viewToModelLengthCallbacks.get( viewNode.name );\n\n\t\t\treturn callback( viewNode );\n\t\t} else if ( this._viewToModelMapping.has( viewNode ) ) {\n\t\t\treturn 1;\n\t\t} else if ( viewNode.is( 'text' ) ) {\n\t\t\treturn viewNode.data.length;\n\t\t} else if ( viewNode.is( 'uiElement' ) ) {\n\t\t\treturn 0;\n\t\t} else {\n\t\t\tlet len = 0;\n\n\t\t\tfor ( const child of viewNode.getChildren() ) {\n\t\t\t\tlen += this.getModelLength( child );\n\t\t\t}\n\n\t\t\treturn len;\n\t\t}\n\t}\n\n\t/**\n\t * Finds the position in the view node (or its children) with the expected model offset.\n\t *\n\t * Example:\n\t *\n\t *\t\t
fobarbom
-> expected offset: 4\n\t *\n\t *\t\t_findPositionIn( p, 4 ):\n\t *\t\t|fobarbom
-> expected offset: 4, actual offset: 0\n\t *\t\tfo|barbom
-> expected offset: 4, actual offset: 2\n\t *\t\tfobar|bom
-> expected offset: 4, actual offset: 5 -> we are too far\n\t *\n\t *\t\t_findPositionIn( b, 4 - ( 5 - 3 ) ):\n\t *\t\tfo|barbom
-> expected offset: 2, actual offset: 0\n\t *\t\tfobar|bom
-> expected offset: 2, actual offset: 3 -> we are too far\n\t *\n\t *\t\t_findPositionIn( bar, 2 - ( 3 - 3 ) ):\n\t *\t\tWe are in the text node so we can simple find the offset.\n\t *\t\tfoba|rbom
-> expected offset: 2, actual offset: 2 -> position found\n\t *\n\t * @private\n\t * @param {module:engine/view/element~Element} viewParent Tree view element in which we are looking for the position.\n\t * @param {Number} expectedOffset Expected offset.\n\t * @returns {module:engine/view/position~Position} Found position.\n\t */\n\t_findPositionIn( viewParent, expectedOffset ) {\n\t\t// Last scanned view node.\n\t\tlet viewNode;\n\t\t// Length of the last scanned view node.\n\t\tlet lastLength = 0;\n\n\t\tlet modelOffset = 0;\n\t\tlet viewOffset = 0;\n\n\t\t// In the text node it is simple: offset in the model equals offset in the text.\n\t\tif ( viewParent.is( 'text' ) ) {\n\t\t\treturn new ViewPosition( viewParent, expectedOffset );\n\t\t}\n\n\t\t// In other cases we add lengths of child nodes to find the proper offset.\n\n\t\t// If it is smaller we add the length.\n\t\twhile ( modelOffset < expectedOffset ) {\n\t\t\tviewNode = viewParent.getChild( viewOffset );\n\t\t\tlastLength = this.getModelLength( viewNode );\n\t\t\tmodelOffset += lastLength;\n\t\t\tviewOffset++;\n\t\t}\n\n\t\t// If it equals we found the position.\n\t\tif ( modelOffset == expectedOffset ) {\n\t\t\treturn this._moveViewPositionToTextNode( new ViewPosition( viewParent, viewOffset ) );\n\t\t}\n\t\t// If it is higher we need to enter last child.\n\t\telse {\n\t\t\t// ( modelOffset - lastLength ) is the offset to the child we enter,\n\t\t\t// so we subtract it from the expected offset to fine the offset in the child.\n\t\t\treturn this._findPositionIn( viewNode, expectedOffset - ( modelOffset - lastLength ) );\n\t\t}\n\t}\n\n\t/**\n\t * Because we prefer positions in text nodes over positions next to text node moves view position to the text node\n\t * if it was next to it.\n\t *\n\t *\t\t[]foo
->[]foo
// do not touch if position is not directly next to text\n\t *\t\tfoo[]foo
->foo{}foo
// move to text node\n\t *\t\t[]foo
->{}foo
// move to text node\n\t *\n\t * @private\n\t * @param {module:engine/view/position~Position} viewPosition Position potentially next to text node.\n\t * @returns {module:engine/view/position~Position} Position in text node if possible.\n\t */\n\t_moveViewPositionToTextNode( viewPosition ) {\n\t\t// If the position is just after text node, put it at the end of that text node.\n\t\t// If the position is just before text node, put it at the beginning of that text node.\n\t\tconst nodeBefore = viewPosition.nodeBefore;\n\t\tconst nodeAfter = viewPosition.nodeAfter;\n\n\t\tif ( nodeBefore instanceof ViewText ) {\n\t\t\treturn new ViewPosition( nodeBefore, nodeBefore.data.length );\n\t\t} else if ( nodeAfter instanceof ViewText ) {\n\t\t\treturn new ViewPosition( nodeAfter, 0 );\n\t\t}\n\n\t\t// Otherwise, just return the given position.\n\t\treturn viewPosition;\n\t}\n\n\t/**\n\t * Fired for each model-to-view position mapping request. The purpose of this event is to enable custom model-to-view position\n\t * mapping. Callbacks added to this event take {@link module:engine/model/position~Position model position} and are expected to\n\t * calculate {@link module:engine/view/position~Position view position}. Calculated view position should be added as `viewPosition`\n\t * value in `data` object that is passed as one of parameters to the event callback.\n\t *\n\t * \t\t// Assume that \"captionedImage\" model element is converted to and following elements in view,\n\t * \t\t// and the model element is bound to element. Force mapping model positions inside \"captionedImage\" to that\n\t * \t\t// element.\n\t *\t\tmapper.on( 'modelToViewPosition', ( evt, data ) => {\n\t *\t\t\tconst positionParent = modelPosition.parent;\n\t *\n\t *\t\t\tif ( positionParent.name == 'captionedImage' ) {\n\t *\t\t\t\tconst viewImg = data.mapper.toViewElement( positionParent );\n\t *\t\t\t\tconst viewCaption = viewImg.nextSibling; // The element.\n\t *\n\t *\t\t\t\tdata.viewPosition = new ViewPosition( viewCaption, modelPosition.offset );\n\t *\n\t *\t\t\t\t// Stop the event if other callbacks should not modify calculated value.\n\t *\t\t\t\tevt.stop();\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t * **Note:** keep in mind that sometimes a \"phantom\" model position is being converted. \"Phantom\" model position is\n\t * a position that points to a non-existing place in model. Such position might still be valid for conversion, though\n\t * (it would point to a correct place in view when converted). One example of such situation is when a range is\n\t * removed from model, there may be a need to map the range's end (which is no longer valid model position). To\n\t * handle such situation, check `data.isPhantom` flag:\n\t *\n\t * \t\t// Assume that there is \"customElement\" model element and whenever position is before it, we want to move it\n\t * \t\t// to the inside of the view element bound to \"customElement\".\n\t *\t\tmapper.on( 'modelToViewPosition', ( evt, data ) => {\n\t *\t\t\tif ( data.isPhantom ) {\n\t *\t\t\t\treturn;\n\t *\t\t\t}\n\t *\n\t *\t\t\t// Below line might crash for phantom position that does not exist in model.\n\t *\t\t\tconst sibling = data.modelPosition.nodeBefore;\n\t *\n\t *\t\t\t// Check if this is the element we are interested in.\n\t *\t\t\tif ( !sibling.is( 'customElement' ) ) {\n\t *\t\t\t\treturn;\n\t *\t\t\t}\n\t *\n\t *\t\t\tconst viewElement = data.mapper.toViewElement( sibling );\n\t *\n\t *\t\t\tdata.viewPosition = new ViewPosition( sibling, 0 );\n\t *\n\t *\t\t\tevt.stop();\n\t *\t\t} );\n\t *\n\t * **Note:** default mapping callback is provided with `low` priority setting and does not cancel the event, so it is possible to\n\t * attach a custom callback after default callback and also use `data.viewPosition` calculated by default callback\n\t * (for example to fix it).\n\t *\n\t * **Note:** default mapping callback will not fire if `data.viewPosition` is already set.\n\t *\n\t * **Note:** these callbacks are called **very often**. For efficiency reasons, it is advised to use them only when position\n\t * mapping between given model and view elements is unsolvable using just elements mapping and default algorithm. Also,\n\t * the condition that checks if special case scenario happened should be as simple as possible.\n\t *\n\t * @event modelToViewPosition\n\t * @param {Object} data Data pipeline object that can store and pass data between callbacks. The callback should add\n\t * `viewPosition` value to that object with calculated {@link module:engine/view/position~Position view position}.\n\t * @param {module:engine/conversion/mapper~Mapper} data.mapper Mapper instance that fired the event.\n\t */\n\n\t/**\n\t * Fired for each view-to-model position mapping request. See {@link module:engine/conversion/mapper~Mapper#event:modelToViewPosition}.\n\t *\n\t * \t\t// See example in `modelToViewPosition` event description.\n\t * \t\t// This custom mapping will map positions from element next to to the \"captionedImage\" element.\n\t *\t\tmapper.on( 'viewToModelPosition', ( evt, data ) => {\n\t *\t\t\tconst positionParent = viewPosition.parent;\n\t *\n\t *\t\t\tif ( positionParent.hasClass( 'image-caption' ) ) {\n\t *\t\t\t\tconst viewImg = positionParent.previousSibling;\n\t *\t\t\t\tconst modelImg = data.mapper.toModelElement( viewImg );\n\t *\n\t *\t\t\t\tdata.modelPosition = new ModelPosition( modelImg, viewPosition.offset );\n\t *\t\t\t\tevt.stop();\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t * **Note:** default mapping callback is provided with `low` priority setting and does not cancel the event, so it is possible to\n\t * attach a custom callback after default callback and also use `data.modelPosition` calculated by default callback\n\t * (for example to fix it).\n\t *\n\t * **Note:** default mapping callback will not fire if `data.modelPosition` is already set.\n\t *\n\t * **Note:** these callbacks are called **very often**. For efficiency reasons, it is advised to use them only when position\n\t * mapping between given model and view elements is unsolvable using just elements mapping and default algorithm. Also,\n\t * the condition that checks if special case scenario happened should be as simple as possible.\n\t *\n\t * @event viewToModelPosition\n\t * @param {Object} data Data pipeline object that can store and pass data between callbacks. The callback should add\n\t * `modelPosition` value to that object with calculated {@link module:engine/model/position~Position model position}.\n\t * @param {module:engine/conversion/mapper~Mapper} data.mapper Mapper instance that fired the event.\n\t */\n}\n\nmix( Mapper, EmitterMixin );\n","/**\n * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n/**\n * @module engine/conversion/modelconsumable\n */\n\nimport TextProxy from '../model/textproxy';\n\n/**\n * Manages a list of consumable values for {@link module:engine/model/item~Item model items}.\n *\n * Consumables are various aspects of the model. A model item can be broken down into singular properties that might be\n * taken into consideration when converting that item.\n *\n * `ModelConsumable` is used by {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} while analyzing changed\n * parts of {@link module:engine/model/document~Document the document}. The added / changed / removed model items are broken down\n * into singular properties (the item itself and it's attributes). All those parts are saved in `ModelConsumable`. Then,\n * during conversion, when given part of model item is converted (i.e. the view element has been inserted into the view,\n * but without attributes), consumable value is removed from `ModelConsumable`.\n *\n * For model items, `ModelConsumable` stores consumable values of one of following types: `insert`, `addattribute: element that will be inserted in view at `viewPosition`.\n *\t\t\tconst viewElement = conversionApi.writer.createContainerElement( 'p' );\n *\n *\t\t\t// Bind the newly created view element to model element so positions will map accordingly in future.\n *\t\t\tconversionApi.mapper.bindElements( data.item, viewElement );\n *\n *\t\t\t// Add the newly created view element to the view.\n *\t\t\tconversionApi.writer.insert( viewPosition, viewElement );\n *\n *\t\t\t// Remember to stop the event propagation.\n *\t\t\tevt.stop();\n *\t\t} );\n */\nexport default class DowncastDispatcher {\n\t/**\n\t * Creates a `DowncastDispatcher` instance.\n\t *\n\t * @see module:engine/conversion/downcastdispatcher~DowncastConversionApi\n\t * @param {Object} [conversionApi] Additional properties for interface that will be passed to events fired\n\t * by `DowncastDispatcher`.\n\t */\n\tconstructor( conversionApi = {} ) {\n\t\t/**\n\t\t * Interface passed by dispatcher to the events callbacks.\n\t\t *\n\t\t * @member {module:engine/conversion/downcastdispatcher~DowncastConversionApi}\n\t\t */\n\t\tthis.conversionApi = extend( { dispatcher: this }, conversionApi );\n\t}\n\n\t/**\n\t * Takes {@link module:engine/model/differ~Differ model differ} object with buffered changes and fires conversion basing on it.\n\t *\n\t * @param {module:engine/model/differ~Differ} differ Differ object with buffered changes.\n\t * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify view document.\n\t */\n\tconvertChanges( differ, writer ) {\n\t\t// Before the view is updated, remove markers which have changed.\n\t\tfor ( const change of differ.getMarkersToRemove() ) {\n\t\t\tthis.convertMarkerRemove( change.name, change.range, writer );\n\t\t}\n\n\t\t// Convert changes that happened on model tree.\n\t\tfor ( const entry of differ.getChanges() ) {\n\t\t\tif ( entry.type == 'insert' ) {\n\t\t\t\tthis.convertInsert( Range._createFromPositionAndShift( entry.position, entry.length ), writer );\n\t\t\t} else if ( entry.type == 'remove' ) {\n\t\t\t\tthis.convertRemove( entry.position, entry.length, entry.name, writer );\n\t\t\t} else {\n\t\t\t\t// entry.type == 'attribute'.\n\t\t\t\tthis.convertAttribute( entry.range, entry.attributeKey, entry.attributeOldValue, entry.attributeNewValue, writer );\n\t\t\t}\n\t\t}\n\n\t\t// After the view is updated, convert markers which have changed.\n\t\tfor ( const change of differ.getMarkersToAdd() ) {\n\t\t\tthis.convertMarkerAdd( change.name, change.range, writer );\n\t\t}\n\t}\n\n\t/**\n\t * Starts conversion of a range insertion.\n\t *\n\t * For each node in the range, {@link #event:insert insert event is fired}. For each attribute on each node,\n\t * {@link #event:attribute attribute event is fired}.\n\t *\n\t * @fires insert\n\t * @fires attribute\n\t * @param {module:engine/model/range~Range} range Inserted range.\n\t * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify view document.\n\t */\n\tconvertInsert( range, writer ) {\n\t\tthis.conversionApi.writer = writer;\n\n\t\t// Create a list of things that can be consumed, consisting of nodes and their attributes.\n\t\tthis.conversionApi.consumable = this._createInsertConsumable( range );\n\n\t\t// Fire a separate insert event for each node and text fragment contained in the range.\n\t\tfor ( const value of range ) {\n\t\t\tconst item = value.item;\n\t\t\tconst itemRange = Range._createFromPositionAndShift( value.previousPosition, value.length );\n\t\t\tconst data = {\n\t\t\t\titem,\n\t\t\t\trange: itemRange\n\t\t\t};\n\n\t\t\tthis._testAndFire( 'insert', data );\n\n\t\t\t// Fire a separate addAttribute event for each attribute that was set on inserted items.\n\t\t\t// This is important because most attributes converters will listen only to add/change/removeAttribute events.\n\t\t\t// If we would not add this part, attributes on inserted nodes would not be converted.\n\t\t\tfor ( const key of item.getAttributeKeys() ) {\n\t\t\t\tdata.attributeKey = key;\n\t\t\t\tdata.attributeOldValue = null;\n\t\t\t\tdata.attributeNewValue = item.getAttribute( key );\n\n\t\t\t\tthis._testAndFire( `attribute:${ key }`, data );\n\t\t\t}\n\t\t}\n\n\t\tthis._clearConversionApi();\n\t}\n\n\t/**\n\t * Fires conversion of a single node removal. Fires {@link #event:remove remove event} with provided data.\n\t *\n\t * @param {module:engine/model/position~Position} position Position from which node was removed.\n\t * @param {Number} length Offset size of removed node.\n\t * @param {String} name Name of removed node.\n\t * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify view document.\n\t */\n\tconvertRemove( position, length, name, writer ) {\n\t\tthis.conversionApi.writer = writer;\n\n\t\tthis.fire( 'remove:' + name, { position, length }, this.conversionApi );\n\n\t\tthis._clearConversionApi();\n\t}\n\n\t/**\n\t * Starts conversion of attribute change on given `range`.\n\t *\n\t * For each node in the given `range`, {@link #event:attribute attribute event} is fired with the passed data.\n\t *\n\t * @fires attribute\n\t * @param {module:engine/model/range~Range} range Changed range.\n\t * @param {String} key Key of the attribute that has changed.\n\t * @param {*} oldValue Attribute value before the change or `null` if the attribute has not been set before.\n\t * @param {*} newValue New attribute value or `null` if the attribute has been removed.\n\t * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify view document.\n\t */\n\tconvertAttribute( range, key, oldValue, newValue, writer ) {\n\t\tthis.conversionApi.writer = writer;\n\n\t\t// Create a list with attributes to consume.\n\t\tthis.conversionApi.consumable = this._createConsumableForRange( range, `attribute:${ key }` );\n\n\t\t// Create a separate attribute event for each node in the range.\n\t\tfor ( const value of range ) {\n\t\t\tconst item = value.item;\n\t\t\tconst itemRange = Range._createFromPositionAndShift( value.previousPosition, value.length );\n\t\t\tconst data = {\n\t\t\t\titem,\n\t\t\t\trange: itemRange,\n\t\t\t\tattributeKey: key,\n\t\t\t\tattributeOldValue: oldValue,\n\t\t\t\tattributeNewValue: newValue\n\t\t\t};\n\n\t\t\tthis._testAndFire( `attribute:${ key }`, data );\n\t\t}\n\n\t\tthis._clearConversionApi();\n\t}\n\n\t/**\n\t * Starts model selection conversion.\n\t *\n\t * Fires events for given {@link module:engine/model/selection~Selection selection} to start selection conversion.\n\t *\n\t * @fires selection\n\t * @fires addMarker\n\t * @fires attribute\n\t * @param {module:engine/model/selection~Selection} selection Selection to convert.\n\t * @param {Array. Foo Foo bar Foo bar f^oobar f^oobar ^ ^ foo^barbar foo^barbar foo^barbar foo^barbar \n *\t\t\t|- a {bold: true} |- \n *\t\t\t|- b {bold: true} | |- ab\n *\t\t\t|- c |- c\n *\n * Passed `Function` will be provided with the attribute value and then all the parameters of the\n * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute` event}.\n * It is expected that the function returns an {@link module:engine/view/element~Element}.\n * The result of the function will be the wrapping element.\n * When the provided `Function` does not return any element, no conversion will take place.\n *\n * The converter automatically consumes the corresponding value from the consumables list and stops the event (see\n * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}).\n *\n *\t\tmodelDispatcher.on( 'attribute:bold', wrap( ( modelAttributeValue, viewWriter ) => {\n *\t\t\treturn viewWriter.createAttributeElement( 'strong' );\n *\t\t} );\n *\n * @protected\n * @param {Function} elementCreator Function returning a view element that will be used for wrapping.\n * @returns {Function} Set/change attribute converter.\n */\nexport function wrap( elementCreator ) {\n\treturn ( evt, data, conversionApi ) => {\n\t\t// Recreate current wrapping node. It will be used to unwrap view range if the attribute value has changed\n\t\t// or the attribute was removed.\n\t\tconst oldViewElement = elementCreator( data.attributeOldValue, conversionApi.writer );\n\n\t\t// Create node to wrap with.\n\t\tconst newViewElement = elementCreator( data.attributeNewValue, conversionApi.writer );\n\n\t\tif ( !oldViewElement && !newViewElement ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !conversionApi.consumable.consume( data.item, evt.name ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst viewWriter = conversionApi.writer;\n\t\tconst viewSelection = viewWriter.document.selection;\n\n\t\tif ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection ) {\n\t\t\t// Selection attribute conversion.\n\t\t\tviewWriter.wrap( viewSelection.getFirstRange(), newViewElement );\n\t\t} else {\n\t\t\t// Node attribute conversion.\n\t\t\tlet viewRange = conversionApi.mapper.toViewRange( data.range );\n\n\t\t\t// First, unwrap the range from current wrapper.\n\t\t\tif ( data.attributeOldValue !== null && oldViewElement ) {\n\t\t\t\tviewRange = viewWriter.unwrap( viewRange, oldViewElement );\n\t\t\t}\n\n\t\t\tif ( data.attributeNewValue !== null && newViewElement ) {\n\t\t\t\tviewWriter.wrap( viewRange, newViewElement );\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Function factory that creates a converter which converts node insertion changes from the model to the view.\n * The function passed will be provided with all the parameters of the dispatcher's\n * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert `insert` event}.\n * It is expected that the function returns an {@link module:engine/view/element~Element}.\n * The result of the function will be inserted into the view.\n *\n * The converter automatically consumes the corresponding value from the consumables list, stops the event (see\n * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}) and binds the model and view elements.\n *\n *\t\tdowncastDispatcher.on(\n *\t\t\t'insert:myElem',\n *\t\t\tinsertElement( ( modelItem, viewWriter ) => {\n *\t\t\t\tconst text = viewWriter.createText( 'myText' );\n *\t\t\t\tconst myElem = viewWriter.createElement( 'myElem', { myAttr: 'my-' + modelItem.getAttribute( 'myAttr' ) }, text );\n *\n *\t\t\t\t// Do something fancy with `myElem` using `modelItem` or other parameters.\n *\n *\t\t\t\treturn myElem;\n *\t\t\t}\n *\t\t) );\n *\n * @protected\n * @param {Function} elementCreator Function returning a view element, which will be inserted.\n * @returns {Function} Insert element event converter.\n */\nexport function insertElement( elementCreator ) {\n\treturn ( evt, data, conversionApi ) => {\n\t\tconst viewElement = elementCreator( data.item, conversionApi.writer );\n\n\t\tif ( !viewElement ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !conversionApi.consumable.consume( data.item, 'insert' ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst viewPosition = conversionApi.mapper.toViewPosition( data.range.start );\n\n\t\tconversionApi.mapper.bindElements( data.item, viewElement );\n\t\tconversionApi.writer.insert( viewPosition, viewElement );\n\t};\n}\n\n/**\n * Function factory that creates a converter which converts marker adding change to the\n * {@link module:engine/view/uielement~UIElement view UI element}.\n *\n * The view UI element that will be added to the view depends on the passed parameter. See {@link ~insertElement}.\n * In case of a non-collapsed range, the UI element will not wrap nodes but separate elements will be placed at the beginning\n * and at the end of the range.\n *\n * This converter binds created UI elements with the marker name using {@link module:engine/conversion/mapper~Mapper#bindElementToMarker}.\n *\n * @protected\n * @param {module:engine/view/uielement~UIElement|Function} elementCreator A view UI element or a function returning the view element\n * that will be inserted.\n * @returns {Function} Insert element event converter.\n */\nexport function insertUIElement( elementCreator ) {\n\treturn ( evt, data, conversionApi ) => {\n\t\t// Create two view elements. One will be inserted at the beginning of marker, one at the end.\n\t\t// If marker is collapsed, only \"opening\" element will be inserted.\n\t\tdata.isOpening = true;\n\t\tconst viewStartElement = elementCreator( data, conversionApi.writer );\n\n\t\tdata.isOpening = false;\n\t\tconst viewEndElement = elementCreator( data, conversionApi.writer );\n\n\t\tif ( !viewStartElement || !viewEndElement ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst markerRange = data.markerRange;\n\n\t\t// Marker that is collapsed has consumable build differently that non-collapsed one.\n\t\t// For more information see `addMarker` event description.\n\t\t// If marker's range is collapsed - check if it can be consumed.\n\t\tif ( markerRange.isCollapsed && !conversionApi.consumable.consume( markerRange, evt.name ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If marker's range is not collapsed - consume all items inside.\n\t\tfor ( const value of markerRange ) {\n\t\t\tif ( !conversionApi.consumable.consume( value.item, evt.name ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tconst mapper = conversionApi.mapper;\n\t\tconst viewWriter = conversionApi.writer;\n\n\t\t// Add \"opening\" element.\n\t\tviewWriter.insert( mapper.toViewPosition( markerRange.start ), viewStartElement );\n\t\tconversionApi.mapper.bindElementToMarker( viewStartElement, data.markerName );\n\n\t\t// Add \"closing\" element only if range is not collapsed.\n\t\tif ( !markerRange.isCollapsed ) {\n\t\t\tviewWriter.insert( mapper.toViewPosition( markerRange.end ), viewEndElement );\n\t\t\tconversionApi.mapper.bindElementToMarker( viewEndElement, data.markerName );\n\t\t}\n\n\t\tevt.stop();\n\t};\n}\n\n// Function factory that returns a default downcast converter for removing a {@link module:engine/view/uielement~UIElement UI element}\n// basing on marker remove change.\n//\n// This converter unbinds elements from the marker name.\n//\n// @returns {Function} Removed UI element converter.\nfunction removeUIElement() {\n\treturn ( evt, data, conversionApi ) => {\n\t\tconst elements = conversionApi.mapper.markerNameToElements( data.markerName );\n\n\t\tif ( !elements ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconversionApi.mapper.unbindElementsFromMarkerName( data.markerName );\n\n\t\tfor ( const element of elements ) {\n\t\t\tconversionApi.writer.clear( conversionApi.writer.createRangeOn( element ), element );\n\t\t}\n\n\t\tconversionApi.writer.clearClonedElementsGroup( data.markerName );\n\n\t\tevt.stop();\n\t};\n}\n\n// Function factory that creates a converter which converts set/change/remove attribute changes from the model to the view.\n//\n// Attributes from the model are converted to the view element attributes in the view. You may provide a custom function to generate\n// a key-value attribute pair to add/change/remove. If not provided, model attributes will be converted to view element\n// attributes on a one-to-one basis.\n//\n// *Note:** The provided attribute creator should always return the same `key` for a given attribute from the model.\n//\n// The converter automatically consumes the corresponding value from the consumables list and stops the event (see\n// {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}).\n//\n//\t\tmodelDispatcher.on( 'attribute:customAttr:myElem', changeAttribute( ( value, data ) => {\n//\t\t\t// Change attribute key from `customAttr` to `class` in the view.\n//\t\t\tconst key = 'class';\n//\t\t\tlet value = data.attributeNewValue;\n//\n//\t\t\t// Force attribute value to 'empty' if the model element is empty.\n//\t\t\tif ( data.item.childCount === 0 ) {\n//\t\t\t\tvalue = 'empty';\n//\t\t\t}\n//\n//\t\t\t// Return the key-value pair.\n//\t\t\treturn { key, value };\n//\t\t} ) );\n//\n// @param {Function} [attributeCreator] Function returning an object with two properties: `key` and `value`, which\n// represent the attribute key and attribute value to be set on a {@link module:engine/view/element~Element view element}.\n// The function is passed the model attribute value as the first parameter and additional data about the change as the second parameter.\n// @returns {Function} Set/change attribute converter.\nfunction changeAttribute( attributeCreator ) {\n\treturn ( evt, data, conversionApi ) => {\n\t\tconst oldAttribute = attributeCreator( data.attributeOldValue, data );\n\t\tconst newAttribute = attributeCreator( data.attributeNewValue, data );\n\n\t\tif ( !oldAttribute && !newAttribute ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !conversionApi.consumable.consume( data.item, evt.name ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst viewElement = conversionApi.mapper.toViewElement( data.item );\n\t\tconst viewWriter = conversionApi.writer;\n\n\t\t// If model item cannot be mapped to a view element, it means item is not an `Element` instance but a `TextProxy` node.\n\t\t// Only elements can have attributes in a view so do not proceed for anything else (#1587).\n\t\tif ( !viewElement ) {\n\t\t\t/**\n\t\t\t * This error occurs when a {@link module:engine/model/textproxy~TextProxy text node's} attribute is to be downcasted\n\t\t\t * by {@link module:engine/conversion/conversion~Conversion#attributeToAttribute `Attribute to Attribute converter`}.\n\t\t\t * In most cases it is caused by converters misconfiguration when only \"generic\" converter is defined:\n\t\t\t *\n\t\t\t *\t\teditor.conversion.for( 'downcast' ).attributeToAttribute( {\n\t\t\t *\t\t\tmodel: 'attribute-name',\n\t\t\t *\t\t\tview: 'attribute-name'\n\t\t\t *\t\t} ) );\n\t\t\t *\n\t\t\t * and given attribute is used on text node, for example:\n\t\t\t *\n\t\t\t *\t\tmodel.change( writer => {\n\t\t\t *\t\t\twriter.insertText( 'Foo', { 'attribute-name': 'bar' }, parent, 0 );\n\t\t\t *\t\t} );\n\t\t\t *\n\t\t\t * In such cases, to convert the same attribute for both {@link module:engine/model/element~Element}\n\t\t\t * and {@link module:engine/model/textproxy~TextProxy `Text`} nodes, text specific\n\t\t\t * {@link module:engine/conversion/conversion~Conversion#attributeToElement `Attribute to Element converter`}\n\t\t\t * with higher {@link module:utils/priorities~PriorityString priority} must also be defined:\n\t\t\t *\n\t\t\t *\t\teditor.conversion.for( 'downcast' ).attributeToElement( {\n\t\t\t *\t\t\tmodel: {\n\t\t\t *\t\t\t\tkey: 'attribute-name',\n\t\t\t *\t\t\t\tname: '$text'\n\t\t\t *\t\t\t},\n\t\t\t *\t\t\tview: ( value, writer ) => {\n\t\t\t *\t\t\t\treturn writer.createAttributeElement( 'span', { 'attribute-name': value } );\n\t\t\t *\t\t\t},\n\t\t\t *\t\t\tconverterPriority: 'high'\n\t\t\t *\t\t} ) );\n\t\t\t *\n\t\t\t * @error conversion-attribute-to-attribute-on-text\n\t\t\t */\n\t\t\tlog.warn( 'conversion-attribute-to-attribute-on-text: ' +\n\t\t\t\t'Trying to convert text node\\'s attribute with attribute-to-attribute converter.' );\n\n\t\t\treturn;\n\t\t}\n\n\t\t// First remove the old attribute if there was one.\n\t\tif ( data.attributeOldValue !== null && oldAttribute ) {\n\t\t\tif ( oldAttribute.key == 'class' ) {\n\t\t\t\tconst classes = Array.isArray( oldAttribute.value ) ? oldAttribute.value : [ oldAttribute.value ];\n\n\t\t\t\tfor ( const className of classes ) {\n\t\t\t\t\tviewWriter.removeClass( className, viewElement );\n\t\t\t\t}\n\t\t\t} else if ( oldAttribute.key == 'style' ) {\n\t\t\t\tconst keys = Object.keys( oldAttribute.value );\n\n\t\t\t\tfor ( const key of keys ) {\n\t\t\t\t\tviewWriter.removeStyle( key, viewElement );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tviewWriter.removeAttribute( oldAttribute.key, viewElement );\n\t\t\t}\n\t\t}\n\n\t\t// Then set the new attribute.\n\t\tif ( data.attributeNewValue !== null && newAttribute ) {\n\t\t\tif ( newAttribute.key == 'class' ) {\n\t\t\t\tconst classes = Array.isArray( newAttribute.value ) ? newAttribute.value : [ newAttribute.value ];\n\n\t\t\t\tfor ( const className of classes ) {\n\t\t\t\t\tviewWriter.addClass( className, viewElement );\n\t\t\t\t}\n\t\t\t} else if ( newAttribute.key == 'style' ) {\n\t\t\t\tconst keys = Object.keys( newAttribute.value );\n\n\t\t\t\tfor ( const key of keys ) {\n\t\t\t\t\tviewWriter.setStyle( key, newAttribute.value[ key ], viewElement );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tviewWriter.setAttribute( newAttribute.key, newAttribute.value, viewElement );\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Function factory that creates a converter which converts the text inside marker's range. The converter wraps the text with\n// {@link module:engine/view/attributeelement~AttributeElement} created from the provided descriptor.\n// See {link module:engine/conversion/downcasthelpers~createViewElementFromHighlightDescriptor}.\n//\n// It can also be used to convert the selection that is inside a marker. In that case, an empty attribute element will be\n// created and the selection will be put inside it.\n//\n// If the highlight descriptor does not provide the `priority` property, `10` will be used.\n//\n// If the highlight descriptor does not provide the `id` property, the name of the marker will be used.\n//\n// This converter binds the created {@link module:engine/view/attributeelement~AttributeElement attribute elemens} with the marker name\n// using the {@link module:engine/conversion/mapper~Mapper#bindElementToMarker} method.\n//\n// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} highlightDescriptor\n// @returns {Function}\nfunction highlightText( highlightDescriptor ) {\n\treturn ( evt, data, conversionApi ) => {\n\t\tif ( !data.item ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !( data.item instanceof ModelSelection || data.item instanceof DocumentSelection ) && !data.item.is( 'textProxy' ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst descriptor = prepareDescriptor( highlightDescriptor, data, conversionApi );\n\n\t\tif ( !descriptor ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !conversionApi.consumable.consume( data.item, evt.name ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst viewElement = createViewElementFromHighlightDescriptor( descriptor );\n\t\tconst viewWriter = conversionApi.writer;\n\t\tconst viewSelection = viewWriter.document.selection;\n\n\t\tif ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection ) {\n\t\t\tviewWriter.wrap( viewSelection.getFirstRange(), viewElement, viewSelection );\n\t\t} else {\n\t\t\tconst viewRange = conversionApi.mapper.toViewRange( data.range );\n\t\t\tconst rangeAfterWrap = viewWriter.wrap( viewRange, viewElement );\n\n\t\t\tfor ( const element of rangeAfterWrap.getItems() ) {\n\t\t\t\tif ( element.is( 'attributeElement' ) && element.isSimilar( viewElement ) ) {\n\t\t\t\t\tconversionApi.mapper.bindElementToMarker( element, data.markerName );\n\n\t\t\t\t\t// One attribute element is enough, because all of them are bound together by the view writer.\n\t\t\t\t\t// Mapper uses this binding to get all the elements no matter how many of them are registered in the mapper.\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Converter function factory. It creates a function which applies the marker's highlight to an element inside the marker's range.\n//\n// The converter checks if an element has the `addHighlight` function stored as a\n// {@link module:engine/view/element~Element#_setCustomProperty custom property} and, if so, uses it to apply the highlight.\n// In such case the converter will consume all element's children, assuming that they were handled by the element itself.\n//\n// When the `addHighlight` custom property is not present, the element is not converted in any special way.\n// This means that converters will proceed to convert the element's child nodes.\n//\n// If the highlight descriptor does not provide the `priority` property, `10` will be used.\n//\n// If the highlight descriptor does not provide the `id` property, the name of the marker will be used.\n//\n// This converter binds altered {@link module:engine/view/containerelement~ContainerElement container elements} with the marker name using\n// the {@link module:engine/conversion/mapper~Mapper#bindElementToMarker} method.\n//\n// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} highlightDescriptor\n// @returns {Function}\nfunction highlightElement( highlightDescriptor ) {\n\treturn ( evt, data, conversionApi ) => {\n\t\tif ( !data.item ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !( data.item instanceof ModelElement ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst descriptor = prepareDescriptor( highlightDescriptor, data, conversionApi );\n\n\t\tif ( !descriptor ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !conversionApi.consumable.test( data.item, evt.name ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst viewElement = conversionApi.mapper.toViewElement( data.item );\n\n\t\tif ( viewElement && viewElement.getCustomProperty( 'addHighlight' ) ) {\n\t\t\t// Consume element itself.\n\t\t\tconversionApi.consumable.consume( data.item, evt.name );\n\n\t\t\t// Consume all children nodes.\n\t\t\tfor ( const value of ModelRange._createIn( data.item ) ) {\n\t\t\t\tconversionApi.consumable.consume( value.item, evt.name );\n\t\t\t}\n\n\t\t\tviewElement.getCustomProperty( 'addHighlight' )( viewElement, descriptor, conversionApi.writer );\n\n\t\t\tconversionApi.mapper.bindElementToMarker( viewElement, data.markerName );\n\t\t}\n\t};\n}\n\n// Function factory that creates a converter which converts the removing model marker to the view.\n//\n// Both text nodes and elements are handled by this converter but they are handled a bit differently.\n//\n// Text nodes are unwrapped using the {@link module:engine/view/attributeelement~AttributeElement attribute element} created from the\n// provided highlight descriptor. See {link module:engine/conversion/downcasthelpers~HighlightDescriptor}.\n//\n// For elements, the converter checks if an element has the `removeHighlight` function stored as a\n// {@link module:engine/view/element~Element#_setCustomProperty custom property}. If so, it uses it to remove the highlight.\n// In such case, the children of that element will not be converted.\n//\n// When `removeHighlight` is not present, the element is not converted in any special way.\n// The converter will proceed to convert the element's child nodes instead.\n//\n// If the highlight descriptor does not provide the `priority` property, `10` will be used.\n//\n// If the highlight descriptor does not provide the `id` property, the name of the marker will be used.\n//\n// This converter unbinds elements from the marker name.\n//\n// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} highlightDescriptor\n// @returns {Function}\nfunction removeHighlight( highlightDescriptor ) {\n\treturn ( evt, data, conversionApi ) => {\n\t\t// This conversion makes sense only for non-collapsed range.\n\t\tif ( data.markerRange.isCollapsed ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst descriptor = prepareDescriptor( highlightDescriptor, data, conversionApi );\n\n\t\tif ( !descriptor ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// View element that will be used to unwrap `AttributeElement`s.\n\t\tconst viewHighlightElement = createViewElementFromHighlightDescriptor( descriptor );\n\n\t\t// Get all elements bound with given marker name.\n\t\tconst elements = conversionApi.mapper.markerNameToElements( data.markerName );\n\n\t\tif ( !elements ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconversionApi.mapper.unbindElementsFromMarkerName( data.markerName );\n\n\t\tfor ( const element of elements ) {\n\t\t\tif ( element.is( 'attributeElement' ) ) {\n\t\t\t\tconversionApi.writer.unwrap( conversionApi.writer.createRangeOn( element ), viewHighlightElement );\n\t\t\t} else {\n\t\t\t\t// if element.is( 'containerElement' ).\n\t\t\t\telement.getCustomProperty( 'removeHighlight' )( element, descriptor.id, conversionApi.writer );\n\t\t\t}\n\t\t}\n\n\t\tconversionApi.writer.clearClonedElementsGroup( data.markerName );\n\n\t\tevt.stop();\n\t};\n}\n\n// Model element to view element conversion helper.\n//\n// See {@link ~DowncastHelpers#elementToElement `.elementToElement()` downcast helper} for examples.\n//\n// @param {Object} config Conversion configuration.\n// @param {String} config.model The name of the model element to convert.\n// @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function\n// that takes the model element and {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer}\n// as parameters and returns a view container element.\n// @returns {Function} Conversion helper.\nfunction downcastElementToElement( config ) {\n\tconfig = cloneDeep( config );\n\n\tconfig.view = normalizeToElementConfig( config.view, 'container' );\n\n\treturn dispatcher => {\n\t\tdispatcher.on( 'insert:' + config.model, insertElement( config.view ), { priority: config.converterPriority || 'normal' } );\n\t};\n}\n\n// Model attribute to view element conversion helper.\n//\n// See {@link ~DowncastHelpers#attributeToElement `.attributeToElement()` downcast helper} for examples.\n//\n// @param {Object} config Conversion configuration.\n// @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values }` object. `values` is an array\n// of `String`s with possible values if the model attribute is an enumerable.\n// @param {module:engine/view/elementdefinition~ElementDefinition|Function|Object} config.view A view element definition or a function\n// that takes the model attribute value and {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer}\n// as parameters and returns a view attribute element. If `config.model.values` is\n// given, `config.view` should be an object assigning values from `config.model.values` to view element definitions or functions.\n// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.\n// @returns {Function} Conversion helper.\nfunction downcastAttributeToElement( config ) {\n\tconfig = cloneDeep( config );\n\n\tconst modelKey = config.model.key ? config.model.key : config.model;\n\tlet eventName = 'attribute:' + modelKey;\n\n\tif ( config.model.name ) {\n\t\teventName += ':' + config.model.name;\n\t}\n\n\tif ( config.model.values ) {\n\t\tfor ( const modelValue of config.model.values ) {\n\t\t\tconfig.view[ modelValue ] = normalizeToElementConfig( config.view[ modelValue ], 'attribute' );\n\t\t}\n\t} else {\n\t\tconfig.view = normalizeToElementConfig( config.view, 'attribute' );\n\t}\n\n\tconst elementCreator = getFromAttributeCreator( config );\n\n\treturn dispatcher => {\n\t\tdispatcher.on( eventName, wrap( elementCreator ), { priority: config.converterPriority || 'normal' } );\n\t};\n}\n\n// Model attribute to view attribute conversion helper.\n//\n// See {@link ~DowncastHelpers#attributeToAttribute `.attributeToAttribute()` downcast helper} for examples.\n//\n// @param {Object} config Conversion configuration.\n// @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values, [ name ] }` object describing\n// the attribute key, possible values and, optionally, an element name to convert from.\n// @param {String|Object|Function} config.view A view attribute key, or a `{ key, value }` object or a function that takes\n// the model attribute value and returns a `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an\n// array of `String`s. If `key` is `'style'`, `value` is an object with key-value pairs. In other cases, `value` is a `String`.\n// If `config.model.values` is set, `config.view` should be an object assigning values from `config.model.values` to\n// `{ key, value }` objects or a functions.\n// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.\n// @returns {Function} Conversion helper.\nfunction downcastAttributeToAttribute( config ) {\n\tconfig = cloneDeep( config );\n\n\tconst modelKey = config.model.key ? config.model.key : config.model;\n\tlet eventName = 'attribute:' + modelKey;\n\n\tif ( config.model.name ) {\n\t\teventName += ':' + config.model.name;\n\t}\n\n\tif ( config.model.values ) {\n\t\tfor ( const modelValue of config.model.values ) {\n\t\t\tconfig.view[ modelValue ] = normalizeToAttributeConfig( config.view[ modelValue ] );\n\t\t}\n\t} else {\n\t\tconfig.view = normalizeToAttributeConfig( config.view );\n\t}\n\n\tconst elementCreator = getFromAttributeCreator( config );\n\n\treturn dispatcher => {\n\t\tdispatcher.on( eventName, changeAttribute( elementCreator ), { priority: config.converterPriority || 'normal' } );\n\t};\n}\n\n// Model marker to view element conversion helper.\n//\n// See {@link ~DowncastHelpers#markerToElement `.markerToElement()` downcast helper} for examples.\n//\n// @param {Object} config Conversion configuration.\n// @param {String} config.model The name of the model marker (or model marker group) to convert.\n// @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function\n// that takes the model marker data as a parameter and returns a view UI element.\n// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.\n// @returns {Function} Conversion helper.\nfunction downcastMarkerToElement( config ) {\n\tconfig = cloneDeep( config );\n\n\tconfig.view = normalizeToElementConfig( config.view, 'ui' );\n\n\treturn dispatcher => {\n\t\tdispatcher.on( 'addMarker:' + config.model, insertUIElement( config.view ), { priority: config.converterPriority || 'normal' } );\n\t\tdispatcher.on( 'removeMarker:' + config.model, removeUIElement( config.view ), { priority: config.converterPriority || 'normal' } );\n\t};\n}\n\n// Model marker to highlight conversion helper.\n//\n// See {@link ~DowncastHelpers#markerToElement `.markerToElement()` downcast helper} for examples.\n//\n// @param {Object} config Conversion configuration.\n// @param {String} config.model The name of the model marker (or model marker group) to convert.\n// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} config.view A highlight descriptor\n// that will be used for highlighting or a function that takes the model marker data as a parameter and returns a highlight descriptor.\n// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.\n// @returns {Function} Conversion helper.\nfunction downcastMarkerToHighlight( config ) {\n\treturn dispatcher => {\n\t\tdispatcher.on( 'addMarker:' + config.model, highlightText( config.view ), { priority: config.converterPriority || 'normal' } );\n\t\tdispatcher.on( 'addMarker:' + config.model, highlightElement( config.view ), { priority: config.converterPriority || 'normal' } );\n\t\tdispatcher.on( 'removeMarker:' + config.model, removeHighlight( config.view ), { priority: config.converterPriority || 'normal' } );\n\t};\n}\n\n// Takes `config.view`, and if it is an {@link module:engine/view/elementdefinition~ElementDefinition}, converts it\n// to a function (because lower level converters accept only element creator functions).\n//\n// @param {module:engine/view/elementdefinition~ElementDefinition|Function} view View configuration.\n// @param {'container'|'attribute'|'ui'} viewElementType View element type to create.\n// @returns {Function} Element creator function to use in lower level converters.\nfunction normalizeToElementConfig( view, viewElementType ) {\n\tif ( typeof view == 'function' ) {\n\t\t// If `view` is already a function, don't do anything.\n\t\treturn view;\n\t}\n\n\treturn ( modelData, viewWriter ) => createViewElementFromDefinition( view, viewWriter, viewElementType );\n}\n\n// Creates a view element instance from the provided {@link module:engine/view/elementdefinition~ElementDefinition} and class.\n//\n// @param {module:engine/view/elementdefinition~ElementDefinition} viewElementDefinition\n// @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter\n// @param {'container'|'attribute'|'ui'} viewElementType\n// @returns {module:engine/view/element~Element}\nfunction createViewElementFromDefinition( viewElementDefinition, viewWriter, viewElementType ) {\n\tif ( typeof viewElementDefinition == 'string' ) {\n\t\t// If `viewElementDefinition` is given as a `String`, normalize it to an object with `name` property.\n\t\tviewElementDefinition = { name: viewElementDefinition };\n\t}\n\n\tlet element;\n\tconst attributes = Object.assign( {}, viewElementDefinition.attributes );\n\n\tif ( viewElementType == 'container' ) {\n\t\telement = viewWriter.createContainerElement( viewElementDefinition.name, attributes );\n\t} else if ( viewElementType == 'attribute' ) {\n\t\tconst options = {\n\t\t\tpriority: viewElementDefinition.priority || ViewAttributeElement.DEFAULT_PRIORITY\n\t\t};\n\n\t\telement = viewWriter.createAttributeElement( viewElementDefinition.name, attributes, options );\n\t} else {\n\t\t// 'ui'.\n\t\telement = viewWriter.createUIElement( viewElementDefinition.name, attributes );\n\t}\n\n\tif ( viewElementDefinition.styles ) {\n\t\tconst keys = Object.keys( viewElementDefinition.styles );\n\n\t\tfor ( const key of keys ) {\n\t\t\tviewWriter.setStyle( key, viewElementDefinition.styles[ key ], element );\n\t\t}\n\t}\n\n\tif ( viewElementDefinition.classes ) {\n\t\tconst classes = viewElementDefinition.classes;\n\n\t\tif ( typeof classes == 'string' ) {\n\t\t\tviewWriter.addClass( classes, element );\n\t\t} else {\n\t\t\tfor ( const className of classes ) {\n\t\t\t\tviewWriter.addClass( className, element );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn element;\n}\n\nfunction getFromAttributeCreator( config ) {\n\tif ( config.model.values ) {\n\t\treturn ( modelAttributeValue, viewWriter ) => {\n\t\t\tconst view = config.view[ modelAttributeValue ];\n\n\t\t\tif ( view ) {\n\t\t\t\treturn view( modelAttributeValue, viewWriter );\n\t\t\t}\n\n\t\t\treturn null;\n\t\t};\n\t} else {\n\t\treturn config.view;\n\t}\n}\n\n// Takes the configuration, adds default parameters if they do not exist and normalizes other parameters to be used in downcast converters\n// for generating a view attribute.\n//\n// @param {Object} view View configuration.\nfunction normalizeToAttributeConfig( view ) {\n\tif ( typeof view == 'string' ) {\n\t\treturn modelAttributeValue => ( { key: view, value: modelAttributeValue } );\n\t} else if ( typeof view == 'object' ) {\n\t\t// { key, value, ... }\n\t\tif ( view.value ) {\n\t\t\treturn () => view;\n\t\t}\n\t\t// { key, ... }\n\t\telse {\n\t\t\treturn modelAttributeValue => ( { key: view.key, value: modelAttributeValue } );\n\t\t}\n\t} else {\n\t\t// function.\n\t\treturn view;\n\t}\n}\n\n// Helper function for `highlight`. Prepares the actual descriptor object using value passed to the converter.\nfunction prepareDescriptor( highlightDescriptor, data, conversionApi ) {\n\t// If passed descriptor is a creator function, call it. If not, just use passed value.\n\tconst descriptor = typeof highlightDescriptor == 'function' ?\n\t\thighlightDescriptor( data, conversionApi ) :\n\t\thighlightDescriptor;\n\n\tif ( !descriptor ) {\n\t\treturn null;\n\t}\n\n\t// Apply default descriptor priority.\n\tif ( !descriptor.priority ) {\n\t\tdescriptor.priority = 10;\n\t}\n\n\t// Default descriptor id is marker name.\n\tif ( !descriptor.id ) {\n\t\tdescriptor.id = data.markerName;\n\t}\n\n\treturn descriptor;\n}\n\n/**\n * An object describing how the marker highlight should be represented in the view.\n *\n * Each text node contained in a highlighted range will be wrapped in a ``\n * {@link module:engine/view/attributeelement~AttributeElement view attribute element} with CSS class(es), attributes and a priority\n * described by this object.\n *\n * Additionally, each {@link module:engine/view/containerelement~ContainerElement container element} can handle displaying the highlight\n * separately by providing the `addHighlight` and `removeHighlight` custom properties. In this case:\n *\n * * The `HighlightDescriptor` object is passed to the `addHighlight` function upon conversion and should be used to apply the highlight to\n * the element.\n * * The descriptor `id` is passed to the `removeHighlight` function upon conversion and should be used to remove the highlight with the\n * given ID from the element.\n *\n * @typedef {Object} module:engine/conversion/downcasthelpers~HighlightDescriptor\n *\n * @property {String|Array. Foo Foo ` element, `bold=\"true\"` was added to the text. See\n\t * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#attributeToAttribute} for comparison.\n\t *\n\t * Keep in mind that the attribute will be set only if it is allowed by {@link module:engine/model/schema~Schema schema} configuration.\n\t *\n\t *\t\teditor.conversion.for( 'upcast' ).elementToAttribute( {\n\t *\t\t\tview: 'strong',\n\t *\t\t\tmodel: 'bold'\n\t *\t\t} );\n\t *\n\t *\t\teditor.conversion.for( 'upcast' ).elementToAttribute( {\n\t *\t\t\tview: 'strong',\n\t *\t\t\tmodel: 'bold',\n\t *\t\t\tconverterPriority: 'high'\n\t *\t\t} );\n\t *\n\t *\t\teditor.conversion.for( 'upcast' ).elementToAttribute( {\n\t *\t\t\tview: {\n\t *\t\t\t\tname: 'span',\n\t *\t\t\t\tclasses: 'bold'\n\t *\t\t\t},\n\t *\t\t\tmodel: 'bold'\n\t *\t\t} );\n\t *\n\t *\t\teditor.conversion.for( 'upcast' ).elementToAttribute( {\n\t *\t\t\tview: {\n\t *\t\t\t\tname: 'span',\n\t *\t\t\t\tclasses: [ 'styled', 'styled-dark' ]\n\t *\t\t\t},\n\t *\t\t\tmodel: {\n\t *\t\t\t\tkey: 'styled',\n\t *\t\t\t\tvalue: 'dark'\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t * \t\teditor.conversion.for( 'upcast' ).elementToAttribute( {\n\t *\t\t\tview: {\n\t *\t\t\t\tname: 'span',\n\t *\t\t\t\tstyles: {\n\t *\t\t\t\t\t'font-size': /[\\s\\S]+/\n\t *\t\t\t\t}\n\t *\t\t\t},\n\t *\t\t\tmodel: {\n\t *\t\t\t\tkey: 'fontSize',\n\t *\t\t\t\tvalue: viewElement => {\n\t *\t\t\t\t\tconst fontSize = viewElement.getStyle( 'font-size' );\n\t *\t\t\t\t\tconst value = fontSize.substr( 0, fontSize.length - 2 );\n\t *\n\t *\t\t\t\t\tif ( value <= 10 ) {\n\t *\t\t\t\t\t\treturn 'small';\n\t *\t\t\t\t\t} else if ( value > 12 ) {\n\t *\t\t\t\t\t\treturn 'big';\n\t *\t\t\t\t\t}\n\t *\n\t *\t\t\t\t\treturn null;\n\t *\t\t\t\t}\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t * See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter\n\t * to the conversion process.\n\t *\n\t * @method #elementToAttribute\n\t * @param {Object} config Conversion configuration.\n\t * @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.\n\t * @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing\n\t * the model attribute. `value` property may be set as a function that takes a view element and returns the value.\n\t * If `String` is given, the model attribute value will be set to `true`.\n\t * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.\n\t * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}\n\t */\n\telementToAttribute( config ) {\n\t\treturn this.add( upcastElementToAttribute( config ) );\n\t}\n\n\t/**\n\t * View attribute to model attribute conversion helper.\n\t *\n\t * This conversion results in setting an attribute on a model node. For example, view `` becomes\n\t * ` Foo ` element, `bold=\"true\"` was added to the text.\n\t *\n\t * Keep in mind that the attribute will be set only if it is allowed by {@link module:engine/model/schema~Schema schema} configuration.\n\t *\n\t *\t\teditor.conversion.for( 'upcast' ).attributeToAttribute( {\n\t *\t\t\tview: 'src',\n\t *\t\t\tmodel: 'source'\n\t *\t\t} );\n\t *\n\t *\t\teditor.conversion.for( 'upcast' ).attributeToAttribute( {\n\t *\t\t\tview: { key: 'src' },\n\t *\t\t\tmodel: 'source'\n\t *\t\t} );\n\t *\n\t *\t\teditor.conversion.for( 'upcast' ).attributeToAttribute( {\n\t *\t\t\tview: { key: 'src' },\n\t *\t\t\tmodel: 'source',\n\t *\t\t\tconverterPriority: 'normal'\n\t *\t\t} );\n\t *\n\t *\t\teditor.conversion.for( 'upcast' ).attributeToAttribute( {\n\t *\t\t\tview: {\n\t *\t\t\t\tkey: 'data-style',\n\t *\t\t\t\tvalue: /[\\s\\S]+/\n\t *\t\t\t},\n\t *\t\t\tmodel: 'styled'\n\t *\t\t} );\n\t *\n\t *\t\teditor.conversion.for( 'upcast' ).attributeToAttribute( {\n\t *\t\t\tview: {\n\t *\t\t\t\tname: 'img',\n\t *\t\t\t\tkey: 'class',\n\t *\t\t\t\tvalue: 'styled-dark'\n\t *\t\t\t},\n\t *\t\t\tmodel: {\n\t *\t\t\t\tkey: 'styled',\n\t *\t\t\t\tvalue: 'dark'\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t *\t\teditor.conversion.for( 'upcast' ).attributeToAttribute( {\n\t *\t\t\tview: {\n\t *\t\t\t\tkey: 'class',\n\t *\t\t\t\tvalue: /styled-[\\S]+/\n\t *\t\t\t},\n\t *\t\t\tmodel: {\n\t *\t\t\t\tkey: 'styled'\n\t *\t\t\t\tvalue: viewElement => {\n\t *\t\t\t\t\tconst regexp = /styled-([\\S]+)/;\n\t *\t\t\t\t\tconst match = viewElement.getAttribute( 'class' ).match( regexp );\n\t *\n\t *\t\t\t\t\treturn match[ 1 ];\n\t *\t\t\t\t}\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t * See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter\n\t * to the conversion process.\n\t *\n\t * @method #attributeToAttribute\n\t * @param {Object} config Conversion configuration.\n\t * @param {String|Object} config.view Specifies which view attribute will be converted. If a `String` is passed,\n\t * attributes with given key will be converted. If an `Object` is passed, it must have a required `key` property,\n\t * specifying view attribute key, and may have an optional `value` property, specifying view attribute value and optional `name`\n\t * property specifying a view element name from/on which the attribute should be converted. `value` can be given as a `String`,\n\t * a `RegExp` or a function callback, that takes view attribute value as the only parameter and returns `Boolean`.\n\t * @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing\n\t * the model attribute. `value` property may be set as a function that takes a view element and returns the value.\n\t * If `String` is given, the model attribute value will be same as view attribute value.\n\t * @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.\n\t * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}\n\t */\n\tattributeToAttribute( config ) {\n\t\treturn this.add( upcastAttributeToAttribute( config ) );\n\t}\n\n\t/**\n\t * View element to model marker conversion helper.\n\t *\n\t * This conversion results in creating a model marker. For example, if the marker was stored in a view as an element:\n\t * ` Foo Bar\n\t *\t\t\t
\n\t *\t\t\n\t *\t\t\t
\n\t *\t\t