diff --git a/application/utils.inc.php b/application/utils.inc.php
index 2665b5f37..9a7528fd1 100644
--- a/application/utils.inc.php
+++ b/application/utils.inc.php
@@ -2490,6 +2490,15 @@ SQL;
$sData = @file_get_contents($sPath);
if ($sData === false)
{
+ IssueLog::Error(<<<br> element)"),keystroke:"Shift+Enter"}]})}}class Fw extends Fr{refresh(){this.value=this._getValue(),this.isEnabled=this._checkEnabled()}execute(t={}){const e=this.editor.model,n=e.schema,o=e.document.selection,i=Array.from(o.getSelectedBlocks()),r=void 0===t.forceValue?!this.value:t.forceValue;e.change((t=>{if(r){const e=i.filter((t=>Rw(t)||Mw(n,t)));this._applyQuote(t,e)}else this._removeQuote(t,i.filter(Rw))}))}_getValue(){const t=$i(this.editor.model.document.selection.getSelectedBlocks());return!(!t||!Rw(t))}_checkEnabled(){if(this.value)return!0;const t=this.editor.model.document.selection,e=this.editor.model.schema,n=$i(t.getSelectedBlocks());return!!n&&Mw(e,n)}_removeQuote(t,e){zw(t,e).reverse().forEach((e=>{if(e.start.isAtStart&&e.end.isAtEnd)return void t.unwrap(e.start.parent);if(e.start.isAtStart){const n=t.createPositionBefore(e.start.parent);return void t.move(e,n)}e.end.isAtEnd||t.split(e.end);const n=t.createPositionAfter(e.end.parent);t.move(e,n)}))}_applyQuote(t,e){const n=[];zw(t,e).reverse().forEach((e=>{let o=Rw(e.start);o||(o=t.createElement("blockQuote"),t.wrap(e,o)),n.push(o)})),n.reverse().reduce(((e,n)=>e.nextSibling==n?(t.merge(t.createPositionAfter(e)),e):n))}}function Rw(t){return"blockQuote"==t.parent.name?t.parent:null}function zw(t,e){let n,o=0;const i=[];for(;o
").replace(/\r?\n/g,"
").replace(/\t/g," ").replace(/^\s/," ").replace(/\s$/," ").replace(/\s\s/g," ")).includes("
")||r.includes("
"))&&(r=`
${r}
`),t=r),i=this.editor.data.htmlProcessor.toView(t)}var r;const s=new m(this,"inputTransformation");this.fire(s,{content:i,dataTransfer:o,targetRanges:e.targetRanges,method:e.method}),s.stop.called&&t.stop(),n.scrollToTheSelection()}),{priority:"low"}),this.listenTo(this,"inputTransformation",((t,n)=>{if(n.content.isEmpty)return;const o=this.editor.data.toModel(n.content,"$clipboardHolder");0!=o.childCount&&(t.stop(),e.change((()=>{this.fire("contentInsertion",{content:o,method:n.method,dataTransfer:n.dataTransfer,targetRanges:n.targetRanges})})))}),{priority:"low"}),this.listenTo(this,"contentInsertion",((t,e)=>{e.resultRange=i._pasteFragmentWithMarkers(e.content)}),{priority:"low"})}_setupCopyCut(){const t=this.editor,e=t.model.document,n=t.editing.view.document,o=(t,n)=>{const o=n.dataTransfer;n.preventDefault(),this._fireOutputTransformationEvent(o,e.selection,t.name)};this.listenTo(n,"copy",o,{priority:"low"}),this.listenTo(n,"cut",((e,n)=>{t.model.canEditAt(t.model.document.selection)?o(e,n):n.preventDefault()}),{priority:"low"}),this.listenTo(this,"outputTransformation",((e,o)=>{const i=t.data.toView(o.content);n.fire("clipboardOutput",{dataTransfer:o.dataTransfer,content:i,method:o.method})}),{priority:"low"}),this.listenTo(n,"clipboardOutput",((n,o)=>{o.content.isEmpty||(o.dataTransfer.setData("text/html",this.editor.data.htmlProcessor.toData(o.content)),o.dataTransfer.setData("text/plain",dA(o.content))),"cut"==o.method&&t.model.deleteContent(e.selection)}),{priority:"low"})}}class SA extends(S()){constructor(){super(...arguments),this._stack=[]}add(t,e){const n=this._stack,o=n[0];this._insertDescriptor(t);const i=n[0];o===i||TA(o,i)||this.fire("change:top",{oldDescriptor:o,newDescriptor:i,writer:e})}remove(t,e){const n=this._stack,o=n[0];this._removeDescriptor(t);const i=n[0];o===i||TA(o,i)||this.fire("change:top",{oldDescriptor:o,newDescriptor:i,writer:e})}_insertDescriptor(t){const e=this._stack,n=e.findIndex((e=>e.id===t.id));if(TA(t,e[n]))return;n>-1&&e.splice(n,1);let o=0;for(;e[o]&&IA(e[o],t);)o++;e.splice(o,0,t)}_removeDescriptor(t){const e=this._stack,n=e.findIndex((e=>e.id===t));n>-1&&e.splice(n,1)}}function TA(t,e){return t&&e&&t.priority==e.priority&&PA(t.classes)==PA(e.classes)}function IA(t,e){return t.priority>e.priority||!(t.priority").test(t)?"first":new RegExp("").test(t)?"last":("first"===e||"middle"===e)&&"middle"}(t,r),function(t,e){return e.some((e=>!e.isVoid&&!!new RegExp(`<${e.name}( .*?)?>`).test(t)))}(t,e)?tV(t,i++):function(t,e){return e.some((e=>new RegExp(`${e.name}>`).test(t)))}(t,e)?tV(t,--i):"middle"===r||"last"===r?t:tV(t,i)))).join("\n")}function tV(t,e,n=" "){return`${n.repeat(Math.max(0,e))}${t}`}var eV=i(6784),nV={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};yr()(eV.A,nV);eV.A.locals;const oV="SourceEditingMode";function iV(t){return function(t){return t.startsWith("<")}(t)?XP(t):t}const rV='';class sV extends Td{constructor(t,e,n){super(t),this._htmlDP=new Td(t),this._initialValue=e,this._transformedInitialValue=n}setTransformedInitialValue(t){this._transformedInitialValue=t}toData(t){const e=this._htmlDP.toData(t);return e===this._transformedInitialValue?this._initialValue:e}}class aV extends Pr{static get pluginName(){return"Disabler"}init(){const t=this.editor;t.ui.on("ready",(()=>{aV.processDisabling(t,e)}));const e=t.sourceElement;$("#"+e.id).on("update",(function(){aV.processDisabling(t,e)}))}static processDisabling(t,e){const n=$(t.ui.element);"function"==typeof n.block&&BlockFieldElement(n,e.disabled),e.disabled?t.enableReadOnlyMode("ibo"):t.disableReadOnlyMode("ibo")}}class cV extends Fr{execute(t){this.editor.setData(this.editor.getData()+t)}}var lV=i(1977),dV={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};yr()(lV.A,dV);lV.A.locals;class uV extends hb{}uV.builtinPlugins=[class extends Pr{static get requires(){return[Ab,Cb]}static get pluginName(){return"Alignment"}},AB,class extends Pr{static get requires(){return[Ow,Hw]}static get pluginName(){return"BlockQuote"}},class extends Pr{static get requires(){return[xb,Db]}static get pluginName(){return"Bold"}},class extends Pr{static get requires(){return[eA,sA]}static get pluginName(){return"CodeBlock"}},class extends Pr{static get requires(){return[im,V_,Sw,N_,Vw,qb,G_]}static get pluginName(){return"Essentials"}},class extends Pr{static get requires(){return[yC,xC]}static get pluginName(){return"FontBackgroundColor"}},class extends Pr{static get requires(){return[AC,CC]}static get pluginName(){return"FontColor"}},class extends Pr{static get requires(){return[sC,cC]}static get pluginName(){return"FontFamily"}},class extends Pr{static get requires(){return[gC,bC]}static get pluginName(){return"FontSize"}normalizeSizeOptions(t){return dC(t)}},class extends Pr{static get pluginName(){return"GeneralHtmlSupport"}static get requires(){return[gy,Ay,_y,Cy,yy,xy,Ey,By,Dy,Sy,Py]}init(){const t=this.editor,e=t.plugins.get(gy);e.loadAllowedEmptyElementsConfig(t.config.get("htmlSupport.allowEmpty")||[]),e.loadAllowedConfig(t.config.get("htmlSupport.allow")||[]),e.loadDisallowedConfig(t.config.get("htmlSupport.disallow")||[])}getGhsAttributeNameForElement(t){const e=this.editor.plugins.get("DataSchema"),n=Array.from(e.getDefinitionsForView(t,!1)),o=n.find((t=>t.isInline&&!n[0].isObject));return o?o.model:Xv(t)}addModelHtmlClass(t,e,n){const o=this.editor.model,i=this.getGhsAttributeNameForElement(t);o.change((t=>{for(const r of Vy(o,n,i))Qv(t,r,i,"classes",(t=>{for(const n of Ai(e))t.add(n)}))}))}removeModelHtmlClass(t,e,n){const o=this.editor.model,i=this.getGhsAttributeNameForElement(t);o.change((t=>{for(const r of Vy(o,n,i))Qv(t,r,i,"classes",(t=>{for(const n of Ai(e))t.delete(n)}))}))}setModelHtmlAttributes(t,e,n){const o=this.editor.model,i=this.getGhsAttributeNameForElement(t);o.change((t=>{for(const r of Vy(o,n,i))Qv(t,r,i,"attributes",(t=>{for(const[n,o]of Object.entries(e))t.set(n,o)}))}))}removeModelHtmlAttributes(t,e,n){const o=this.editor.model,i=this.getGhsAttributeNameForElement(t);o.change((t=>{for(const r of Vy(o,n,i))Qv(t,r,i,"attributes",(t=>{for(const n of Ai(e))t.delete(n)}))}))}setModelHtmlStyles(t,e,n){const o=this.editor.model,i=this.getGhsAttributeNameForElement(t);o.change((t=>{for(const r of Vy(o,n,i))Qv(t,r,i,"styles",(t=>{for(const[n,o]of Object.entries(e))t.set(n,o)}))}))}removeModelHtmlStyles(t,e,n){const o=this.editor.model,i=this.getGhsAttributeNameForElement(t);o.change((t=>{for(const r of Vy(o,n,i))Qv(t,r,i,"styles",(t=>{for(const n of Ai(e))t.delete(n)}))}))}},class extends Pr{static get requires(){return[FC,MC]}static get pluginName(){return"Heading"}},class extends Pr{static get requires(){return[NC,jC]}static get pluginName(){return"Highlight"}},class extends Pr{static get requires(){return[GC,KC,r_]}static get pluginName(){return"HorizontalLine"}},class extends Pr{static get requires(){return[kx,wx]}static get pluginName(){return"Image"}},class extends Pr{static get requires(){return[Cx,vx]}static get pluginName(){return"ImageCaption"}},class extends Pr{static get requires(){return[Qx,rE,hE,tE]}static get pluginName(){return"ImageResize"}},class extends Pr{static get requires(){return[SE,PE]}static get pluginName(){return"ImageStyle"}},class extends Pr{static get requires(){return[a_,Ly]}static get pluginName(){return"ImageToolbar"}afterInit(){const t=this.editor,e=t.t,n=t.plugins.get(a_),o=t.plugins.get("ImageUtils");var i;n.register("image",{ariaLabel:e("Image toolbar"),items:(i=t.config.get("image.toolbar")||[],i.map((t=>M(t)?t.name:t))),getRelatedElement:t=>o.getClosestSelectedImageWidget(t)})}},class extends Pr{static get pluginName(){return"ImageUpload"}static get requires(){return[$x,Sx,Nx]}},class extends Pr{static get pluginName(){return"Indent"}static get requires(){return[zE,ME]}},class extends Pr{constructor(t){super(t),t.config.define("indentBlock",{offset:40,unit:"px"})}static get pluginName(){return"IndentBlock"}init(){const t=this.editor,e=t.config.get("indentBlock");e.classes&&e.classes.length?(this._setupConversionUsingClasses(e.classes),t.commands.add("indentBlock",new OE(t,new LE({direction:"forward",classes:e.classes}))),t.commands.add("outdentBlock",new OE(t,new LE({direction:"backward",classes:e.classes})))):(t.data.addStyleProcessorRules(Nh),this._setupConversionUsingOffset(),t.commands.add("indentBlock",new OE(t,new NE({direction:"forward",offset:e.offset,unit:e.unit}))),t.commands.add("outdentBlock",new OE(t,new NE({direction:"backward",offset:e.offset,unit:e.unit}))))}afterInit(){const t=this.editor,e=t.model.schema,n=t.commands.get("indent"),o=t.commands.get("outdent"),i=t.config.get("heading.options");(i&&i.map((t=>t.model))||HE).forEach((t=>{e.isRegistered(t)&&e.extend(t,{allowAttributes:"blockIndent"})})),e.setAttributeProperties("blockIndent",{isFormatting:!0}),n.registerChildCommand(t.commands.get("indentBlock")),o.registerChildCommand(t.commands.get("outdentBlock"))}_setupConversionUsingOffset(){const t=this.editor.conversion,e="rtl"===this.editor.locale.contentLanguageDirection?"margin-right":"margin-left";t.for("upcast").attributeToAttribute({view:{styles:{[e]:/[\s\S]+/}},model:{key:"blockIndent",value:t=>{if(!t.is("element","li"))return t.getStyle(e)}}}),t.for("downcast").attributeToAttribute({model:"blockIndent",view:t=>({key:"style",value:{[e]:t}})})}_setupConversionUsingClasses(t){const e={model:{key:"blockIndent",values:[]},view:{}};for(const n of t)e.model.values.push(n),e.view[n]={key:"class",value:[n]};this.editor.conversion.attributeToAttribute(e)}},class extends Pr{static get requires(){return[rw,aw]}static get pluginName(){return"Italic"}},class extends Pr{static get requires(){return[cB,kB,AB]}static get pluginName(){return"Link"}},class extends Pr{static get requires(){return[CB,xB]}static get pluginName(){return"LinkImage"}},xD,YD,class extends Pr{toMentionAttribute(t,e){return iS(t,e)}static get pluginName(){return"Mention"}static get requires(){return[nS,fS]}},TC,class extends Pr{static get requires(){return[ox,Ly]}static get pluginName(){return"PictureEditing"}afterInit(){const t=this.editor;t.plugins.has("ImageBlockEditing")&&t.model.schema.extend("imageBlock",{allowAttributes:["sources"]}),t.plugins.has("ImageInlineEditing")&&t.model.schema.extend("imageInline",{allowAttributes:["sources"]}),this._setupConversion(),this._setupImageUploadEditingIntegration()}_setupConversion(){const t=this.editor,e=t.conversion,n=t.plugins.get("ImageUtils");e.for("upcast").add(function(t){const e=["srcset","media","type","sizes"],n=(n,o,i)=>{const r=o.viewItem;if(!i.consumable.test(r,{name:!0}))return;const s=new Map;for(const t of r.getChildren())if(t.is("element","source")){const n={};for(const o of e)t.hasAttribute(o)&&i.consumable.test(t,{attributes:o})&&(n[o]=t.getAttribute(o));Object.keys(n).length&&s.set(t,n)}const a=t.findViewImgElement(r);if(!a)return;let c=o.modelCursor.parent;if(!c.is("element","imageBlock")){const t=i.convertItem(a,o.modelCursor);o.modelRange=t.modelRange,o.modelCursor=t.modelCursor,c=$i(t.modelRange.getItems())}i.consumable.consume(r,{name:!0});for(const[t,e]of s)i.consumable.consume(t,{attributes:Object.keys(e)});s.size&&i.writer.setAttribute("sources",Array.from(s.values()),c),i.convertChildren(r,c)};return t=>{t.on("element:picture",n)}}(n)),e.for("downcast").add(function(t){const e=(e,n,o)=>{if(!o.consumable.consume(n.item,e.name))return;const i=o.writer,r=o.mapper.toViewElement(n.item),s=t.findViewImgElement(r),a=n.attributeNewValue;if(a&&a.length){const t=i.createContainerElement("picture",null,a.map((t=>i.createEmptyElement("source",t)))),e=[];let n=s.parent;for(;n&&n.is("attributeElement");){const t=n.parent;i.unwrap(i.createRangeOn(s),n),e.unshift(n),n=t}i.insert(i.createPositionBefore(s),t),i.move(i.createRangeOn(s),i.createPositionAt(t,"end"));for(const n of e)i.wrap(i.createRangeOn(t),n)}else if(s.parent.is("element","picture")){const t=s.parent;i.move(i.createRangeOn(s),i.createPositionBefore(t)),i.remove(t)}};return t=>{t.on("attribute:sources:imageBlock",e),t.on("attribute:sources:imageInline",e)}}(n))}_setupImageUploadEditingIntegration(){const t=this.editor;if(!t.plugins.has("ImageUploadEditing"))return;const e=t.plugins.get("ImageUploadEditing");this.listenTo(e,"uploadComplete",((e,{imageElement:n,data:o})=>{const i=o.sources;i&&t.model.change((t=>{t.setAttributes({sources:i},n)}))}))}},class extends Pr{static get requires(){return[lw,uw]}static get pluginName(){return"Strikethrough"}},class extends Pr{static get requires(){return[mw,pw]}static get pluginName(){return"Subscript"}},class extends Pr{static get requires(){return[kw,ww]}static get pluginName(){return"Superscript"}},class extends Pr{static get requires(){return[GT,QT,nI,lI,aI,oI,r_]}static get pluginName(){return"Table"}},class extends Pr{static get pluginName(){return"TableCaption"}static get requires(){return[LP,HP]}},class extends Pr{static get pluginName(){return"TableCellProperties"}static get requires(){return[pP,eP]}},class extends Pr{static get requires(){return[$P,iP]}static get pluginName(){return"TableColumnResize"}},class extends Pr{static get pluginName(){return"TableProperties"}static get requires(){return[EP,FP]}},class extends Pr{static get requires(){return[a_]}static get pluginName(){return"TableToolbar"}afterInit(){const t=this.editor,e=t.t,n=t.plugins.get(a_),o=t.config.get("table.contentToolbar"),i=t.config.get("table.tableToolbar");o&&n.register("tableContent",{ariaLabel:e("Table toolbar"),items:o,getRelatedElement:pI}),i&&n.register("table",{ariaLabel:e("Table toolbar"),items:i,getRelatedElement:gI})}},class extends Pr{static get requires(){return[_w,vw]}static get pluginName(){return"Underline"}},G_,class extends Pr{static get requires(){return[QP,JP]}static get pluginName(){return"RemoveFormat"}},class extends Pr{static get pluginName(){return"SourceEditing"}static get requires(){return[Kh]}constructor(t){super(t),this.set("isSourceEditingMode",!1),this._elementReplacer=new Y,this._replacedRoots=new Map,this._dataFromRoots=new Map,t.config.define("sourceEditing.allowCollaborationFeatures",!1)}init(){this._checkCompatibility();const t=this.editor,e=t.locale.t;t.ui.componentFactory.add("sourceEditing",(()=>{const t=this._createButton(mm);return t.set({label:e("Source"),icon:'',tooltip:!0,class:"ck-source-editing-button"}),t})),t.ui.componentFactory.add("menuBar:sourceEditing",(()=>{const t=this._createButton($k);return t.set({label:e("Show source")}),t})),this._isAllowedToHandleSourceEditingMode()&&(this.on("change:isSourceEditingMode",((t,e,n)=>{n?(this._hideVisibleDialog(),this._showSourceEditing(),this._disableCommands()):(this._hideSourceEditing(),this._enableCommands())})),this.on("change:isEnabled",((t,e,n)=>this._handleReadOnlyMode(!n))),this.listenTo(t,"change:isReadOnly",((t,e,n)=>this._handleReadOnlyMode(n)))),t.data.on("get",(()=>{this.isSourceEditingMode&&this.updateEditorData()}),{priority:"high"})}updateEditorData(){const t=this.editor,e={};for(const[t,n]of this._replacedRoots){const o=this._dataFromRoots.get(t),i=n.dataset.value;o!==i&&(e[t]=i,this._dataFromRoots.set(t,i))}Object.keys(e).length&&t.data.set(e,{batchType:{isUndoable:!0},suppressErrorInCollaboration:!0})}_checkCompatibility(){const t=this.editor,e=t.config.get("sourceEditing.allowCollaborationFeatures");if(!e&&t.plugins.has("RealTimeCollaborativeEditing"))throw new w("source-editing-incompatible-with-real-time-collaboration",null);!e&&["CommentsEditing","TrackChangesEditing","RevisionHistory"].some((e=>t.plugins.has(e)))&&console.warn("You initialized the editor with the source editing feature and at least one of the collaboration features. Please be advised that the source editing feature may not work, and be careful when editing document source that contains markers created by the collaboration features."),t.plugins.has("RestrictedEditingModeEditing")&&console.warn("You initialized the editor with the source editing feature and restricted editing feature. Please be advised that the source editing feature may not work, and be careful when editing document source that contains markers created by the restricted editing feature.")}_showSourceEditing(){const t=this.editor,e=t.editing.view,n=t.model;n.change((t=>{t.setSelection(null),t.removeSelectionAttribute(n.document.selection.getAttributeKeys())}));for(const[n,o]of e.domRoots){const i=iV(t.data.get({rootName:n})),r=kt(o.ownerDocument,"textarea",{rows:"1","aria-label":"Source code editing area"}),s=kt(o.ownerDocument,"div",{class:"ck-source-editing-area","data-value":i},[r]);r.value=i,r.setSelectionRange(0,0),r.addEventListener("input",(()=>{s.dataset.value=r.value,t.ui.update()})),e.change((t=>{const o=e.document.getRoot(n);t.addClass("ck-hidden",o)})),t.ui.setEditableElement("sourceEditing:"+n,r),this._replacedRoots.set(n,s),this._elementReplacer.replace(o,s),this._dataFromRoots.set(n,i)}this._focusSourceEditing()}_hideSourceEditing(){const t=this.editor.editing.view;this.updateEditorData(),t.change((e=>{for(const[n]of this._replacedRoots)e.removeClass("ck-hidden",t.document.getRoot(n))})),this._elementReplacer.restore(),this._replacedRoots.clear(),this._dataFromRoots.clear(),t.focus()}_focusSourceEditing(){const t=this.editor,[e]=this._replacedRoots.values(),n=e.querySelector("textarea");t.editing.view.document.isFocused=!1,n.focus()}_disableCommands(){const t=this.editor;for(const e of t.commands.commands())e.forceDisabled(oV);t.plugins.has("CommentsArchiveUI")&&t.plugins.get("CommentsArchiveUI").forceDisabled(oV)}_enableCommands(){const t=this.editor;for(const e of t.commands.commands())e.clearForceDisabled(oV);t.plugins.has("CommentsArchiveUI")&&t.plugins.get("CommentsArchiveUI").clearForceDisabled(oV)}_handleReadOnlyMode(t){if(this.isSourceEditingMode)for(const[,e]of this._replacedRoots)e.querySelector("textarea").readOnly=t}_isAllowedToHandleSourceEditingMode(){const t=this.editor.ui.view.editable;return t&&!t.hasExternalElement}_hideVisibleDialog(){if(this.editor.plugins.has("Dialog")){const t=this.editor.plugins.get("Dialog");t.isOpen&&t.hide()}}_createButton(t){const e=this.editor,n=new t(e.locale);return n.set({withText:!0}),n.bind("isOn").to(this,"isSourceEditingMode"),n.bind("isEnabled").to(this,"isEnabled",e,"isReadOnly",e.plugins.get(Kh),"hasAny",((t,e,n)=>!!t&&(!e&&!n))),this.listenTo(n,"execute",(()=>{this.isSourceEditingMode=!this.isSourceEditingMode})),n}},class extends Pr{static get pluginName(){return"AppendITopClasses"}init(){const t=this.editor;t.editing.view.change((e=>{const n=t.editing.view.document.getRoot();null!==n&&e.addClass("ibo-is-html-content",n)}))}},class extends Pr{static get pluginName(){return"KeyboardShortcut"}init(){const t=this.editor;t.keystrokes.set("Ctrl+Enter",((e,n)=>{if(null!==t.ui.element){const e=t.ui.element.closest("form");if(null!==e){const t=new Event("submit");e.dispatchEvent(t)}}}))}},class extends Pr{static get pluginName(){return"MentionsMarkup"}init(){const t=this.editor;t.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{href:!0,"data-role":!0,"data-object-class":!0,"data-object-id":!0}},model:{key:"mention",value:e=>t.plugins.get("Mention").toMentionAttribute(e,{link:e.getAttribute("href"),id:e.getAttribute("data-object-id"),class_name:e.getAttribute("data-object-class"),mention:"object-mention"})},converterPriority:"high"}),t.conversion.for("downcast").attributeToElement({model:"mention",view:(t,{writer:e})=>{if(t)return e.createAttributeElement("a",{"data-role":"object-mention","data-object-class":t.class_name,"data-object-id":t.id,href:t.link},{priority:20,id:t.uid})},converterPriority:"high"})}},class extends Pr{static get pluginName(){return"TriggerUpdateOnReady"}init(){const t=this.editor;t.ui.on("ready",(()=>{if(null!==t.ui.element){const e=new Event("update");t.ui.element.dispatchEvent(e)}for(const t of document.getElementsByClassName("ck-body-wrapper"))t.classList.add("ck-reset_all-excluded")}))}},class extends Pr{static get pluginName(){return"Maximize"}init(){const t=this.editor;let e;t.ui.componentFactory.add("maximize",(()=>{const n=new mm;return n.set({icon:rV,isToggleable:!0}),this.listenTo(n,"execute",(()=>{var o,i,r;if(null!==t.ui.element){let s=document.getElementsByClassName("ck-powered-by-balloon");const a=t.config.get("maximize.fullscreen");n.isOn?("native"===a?(document.exitFullscreen(),t.ui.element.classList.remove("fullscreen-mode")):(e.append(t.ui.element),t.ui.element.classList.remove("cke-maximized"),document.body.classList.remove("cke-maximized"),null===(o=s.item(0))||void 0===o||o.setAttribute("style","display: block")),n.icon=rV):("native"===a?(t.ui.element.requestFullscreen(),t.ui.element.classList.add("fullscreen-mode")):(e=null!==(i=t.ui.element.parentElement)&&void 0!==i?i:e,t.ui.element.remove(),document.body.append(t.ui.element),document.body.classList.add("cke-maximized"),t.ui.element.classList.add("cke-maximized"),null===(r=s.item(0))||void 0===r||r.setAttribute("style","display: none")),n.icon=''),n.isOn=!n.isOn}})),n}))}},class extends Pr{static get pluginName(){return"InsertHtmlContent"}init(){const t=this.editor;t.commands.add("insert-html",new cV(t))}},class extends Pr{constructor(t){super(t);const e=t.config.get("detectChanges.initialValue");if(!e||""===e)return;const n=new sV(t.data.viewDocument,e,t.getData());t.data.processor=n,t.model.document.once("change:data",(()=>{n.setTransformedInitialValue(t.getData())}))}init(){}static get pluginName(){return"DetectChanges"}},class extends Pr{static get pluginName(){return"UpdateInputOnChange"}init(){const t=this.editor;if(void 0!==t.sourceElement){const e=t.sourceElement;t.model.document.on("change:data",(n=>{e.value!==t.getData()&&(e.value=t.getData())}))}}},aV,class extends Pr{init(){const t=this.editor,e=["codeBlock","div","pre"];t.model.document.on("change:data",((n,o)=>{if(o.isLocal){const n=Array.from(t.model.document.differ.getChanges()),o=t.model.document.selection.getFirstPosition();n.forEach((n=>{var o;"insert"===n.type&&((o=n.position.nodeAfter)&&e.some((t=>o.is("element",t))))&&t.model.change((e=>{const o=n.position.getShiftedBy(n.length);t.execute("insertParagraph",{position:o})}))})),t.model.change((t=>{t.setSelection(o)}))}}))}}],uV.defaultConfig={toolbar:{items:["maximize","|","undo","redo","|","heading","|","alignment","|",{label:"Fonts",icon:"text",items:["fontfamily","fontSize","fontColor"]},"|","bold","italic","underline","highlight",{label:"More styles",items:["strikethrough","RemoveFormat"]},"|","horizontalLine","link","imageUpload","codeBlock","bulletedList","numberedList","insertTable","|","SourceEditing"],shouldNotGroupWhenFull:!0},language:"en",image:{toolbar:["resizeImage:25","resizeImage:50","resizeImage:original","|","toggleImageCaption"],resizeOptions:[{name:"resizeImage:original",value:null,icon:"original"},{name:"resizeImage:25",value:"25",icon:"small"},{name:"resizeImage:50",value:"50",icon:"medium"}]},table:{contentToolbar:["tableColumn","tableRow","mergeTableCells","|","tableCellProperties","tableProperties","|","toggleTableCaption"]},htmlSupport:{allow:[{name:/.*/,attributes:!0,classes:!0,styles:!0}]},link:{defaultProtocol:"http://"},highlight:{options:[{model:"yellowMarker",class:"marker-yellow",title:"Yellow Marker",color:"var(--ck-highlight-marker-yellow)",type:"marker"},{model:"greenMarker",class:"marker-green",title:"Green marker",color:"var(--ck-highlight-marker-green)",type:"marker"},{model:"pinkMarker",class:"marker-pink",title:"Pink marker",color:"var(--ck-highlight-marker-pink)",type:"marker"},{model:"blueMarker",class:"marker-blue",title:"Blue marker",color:"var(--ck-highlight-marker-blue)",type:"marker"}]},codeBlock:{languages:[{language:"plaintext",label:"Plain text"},{language:"abap",label:"ABAP"},{language:"apache",label:"Apache"},{language:"bash",label:"Bash"},{language:"cs",label:"C#"},{language:"cpp",label:"C++"},{language:"css",label:"CSS"},{language:"ciscocli",label:"Cisco CLI"},{language:"coffeescript",label:"CoffeeScript"},{language:"curl",label:"cURL"},{language:"diff",label:"Diff"},{language:"dnszonefile",label:"DNS Zone File"},{language:"html",label:"HTML"},{language:"http",label:"HTTP"},{language:"ini",label:"Ini"},{language:"json",label:"JSON"},{language:"java",label:"Java"},{language:"javascript",label:"JavaScript"},{language:"makefile",label:"Makefile"},{language:"markdown",label:"Markdown"},{language:"nginx",label:"Nginx"},{language:"objectivec",label:"Objective C"},{language:"php",label:"PHP"},{language:"perl",label:"Perl"},{language:"python",label:"Python"},{language:"ruby",label:"Ruby"},{language:"rust",label:"Rust"},{language:"scss",label:"SCSS"},{language:"sql",label:"SQL"},{language:"toml",label:"TOML"},{language:"twig",label:"TWIG"},{language:"typescript",label:"TypeScript"},{language:"vba",label:"VBA"},{language:"vbscript",label:"VBScript"},{language:"xml",label:"XML"},{language:"yaml",label:"YAML"}]}};const hV=uV})(),r=r.default})()));
+function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClassicEditor=e():t.ClassicEditor=e()}(self,(()=>(()=>{var t,e,n={5659:(t,e,n)=>{const o=n(8156),i={};for(const t of Object.keys(o))i[o[t]]=t;const r={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};t.exports=r;for(const t of Object.keys(r)){if(!("channels"in r[t]))throw new Error("missing channels property: "+t);if(!("labels"in r[t]))throw new Error("missing channel labels property: "+t);if(r[t].labels.length!==r[t].channels)throw new Error("channel and label counts mismatch: "+t);const{channels:e,labels:n}=r[t];delete r[t].channels,delete r[t].labels,Object.defineProperty(r[t],"channels",{value:e}),Object.defineProperty(r[t],"labels",{value:n})}r.rgb.hsl=function(t){const e=t[0]/255,n=t[1]/255,o=t[2]/255,i=Math.min(e,n,o),r=Math.max(e,n,o),s=r-i;let a,c;r===i?a=0:e===r?a=(n-o)/s:n===r?a=2+(o-e)/s:o===r&&(a=4+(e-n)/s),a=Math.min(60*a,360),a<0&&(a+=360);const l=(i+r)/2;return c=r===i?0:l<=.5?s/(r+i):s/(2-r-i),[a,100*c,100*l]},r.rgb.hsv=function(t){let e,n,o,i,r;const s=t[0]/255,a=t[1]/255,c=t[2]/255,l=Math.max(s,a,c),d=l-Math.min(s,a,c),u=function(t){return(l-t)/6/d+.5};return 0===d?(i=0,r=0):(r=d/l,e=u(s),n=u(a),o=u(c),s===l?i=o-n:a===l?i=1/3+e-o:c===l&&(i=2/3+n-e),i<0?i+=1:i>1&&(i-=1)),[360*i,100*r,100*l]},r.rgb.hwb=function(t){const e=t[0],n=t[1];let o=t[2];const i=r.rgb.hsl(t)[0],s=1/255*Math.min(e,Math.min(n,o));return o=1-1/255*Math.max(e,Math.max(n,o)),[i,100*s,100*o]},r.rgb.cmyk=function(t){const e=t[0]/255,n=t[1]/255,o=t[2]/255,i=Math.min(1-e,1-n,1-o);return[100*((1-e-i)/(1-i)||0),100*((1-n-i)/(1-i)||0),100*((1-o-i)/(1-i)||0),100*i]},r.rgb.keyword=function(t){const e=i[t];if(e)return e;let n,r=1/0;for(const e of Object.keys(o)){const i=o[e],c=(a=i,((s=t)[0]-a[0])**2+(s[1]-a[1])**2+(s[2]-a[2])**2);c<br> element)"),keystroke:"Shift+Enter"}]})}}class Rw extends Rr{refresh(){this.value=this._getValue(),this.isEnabled=this._checkEnabled()}execute(t={}){const e=this.editor.model,n=e.schema,o=e.document.selection,i=Array.from(o.getSelectedBlocks()),r=void 0===t.forceValue?!this.value:t.forceValue;e.change((t=>{if(r){const e=i.filter((t=>Fw(t)||Mw(n,t)));this._applyQuote(t,e)}else this._removeQuote(t,i.filter(Fw))}))}_getValue(){const t=$i(this.editor.model.document.selection.getSelectedBlocks());return!(!t||!Fw(t))}_checkEnabled(){if(this.value)return!0;const t=this.editor.model.document.selection,e=this.editor.model.schema,n=$i(t.getSelectedBlocks());return!!n&&Mw(e,n)}_removeQuote(t,e){zw(t,e).reverse().forEach((e=>{if(e.start.isAtStart&&e.end.isAtEnd)return void t.unwrap(e.start.parent);if(e.start.isAtStart){const n=t.createPositionBefore(e.start.parent);return void t.move(e,n)}e.end.isAtEnd||t.split(e.end);const n=t.createPositionAfter(e.end.parent);t.move(e,n)}))}_applyQuote(t,e){const n=[];zw(t,e).reverse().forEach((e=>{let o=Fw(e.start);o||(o=t.createElement("blockQuote"),t.wrap(e,o)),n.push(o)})),n.reverse().reduce(((e,n)=>e.nextSibling==n?(t.merge(t.createPositionAfter(e)),e):n))}}function Fw(t){return"blockQuote"==t.parent.name?t.parent:null}function zw(t,e){let n,o=0;const i=[];for(;o").replace(/\r?\n/g,"
").replace(/\t/g," ").replace(/^\s/," ").replace(/\s$/," ").replace(/\s\s/g," ")).includes("
")||r.includes("
"))&&(r=`
${r}
`),t=r),i=this.editor.data.htmlProcessor.toView(t)}var r;const s=new m(this,"inputTransformation");this.fire(s,{content:i,dataTransfer:o,targetRanges:e.targetRanges,method:e.method}),s.stop.called&&t.stop(),n.scrollToTheSelection()}),{priority:"low"}),this.listenTo(this,"inputTransformation",((t,n)=>{if(n.content.isEmpty)return;const o=this.editor.data.toModel(n.content,"$clipboardHolder");0!=o.childCount&&(t.stop(),e.change((()=>{this.fire("contentInsertion",{content:o,method:n.method,dataTransfer:n.dataTransfer,targetRanges:n.targetRanges})})))}),{priority:"low"}),this.listenTo(this,"contentInsertion",((t,e)=>{e.resultRange=i._pasteFragmentWithMarkers(e.content)}),{priority:"low"})}_setupCopyCut(){const t=this.editor,e=t.model.document,n=t.editing.view.document,o=(t,n)=>{const o=n.dataTransfer;n.preventDefault(),this._fireOutputTransformationEvent(o,e.selection,t.name)};this.listenTo(n,"copy",o,{priority:"low"}),this.listenTo(n,"cut",((e,n)=>{t.model.canEditAt(t.model.document.selection)?o(e,n):n.preventDefault()}),{priority:"low"}),this.listenTo(this,"outputTransformation",((e,o)=>{const i=t.data.toView(o.content);n.fire("clipboardOutput",{dataTransfer:o.dataTransfer,content:i,method:o.method})}),{priority:"low"}),this.listenTo(n,"clipboardOutput",((n,o)=>{o.content.isEmpty||(o.dataTransfer.setData("text/html",this.editor.data.htmlProcessor.toData(o.content)),o.dataTransfer.setData("text/plain",dA(o.content))),"cut"==o.method&&t.model.deleteContent(e.selection)}),{priority:"low"})}}class SA extends(S()){constructor(){super(...arguments),this._stack=[]}add(t,e){const n=this._stack,o=n[0];this._insertDescriptor(t);const i=n[0];o===i||TA(o,i)||this.fire("change:top",{oldDescriptor:o,newDescriptor:i,writer:e})}remove(t,e){const n=this._stack,o=n[0];this._removeDescriptor(t);const i=n[0];o===i||TA(o,i)||this.fire("change:top",{oldDescriptor:o,newDescriptor:i,writer:e})}_insertDescriptor(t){const e=this._stack,n=e.findIndex((e=>e.id===t.id));if(TA(t,e[n]))return;n>-1&&e.splice(n,1);let o=0;for(;e[o]&&IA(e[o],t);)o++;e.splice(o,0,t)}_removeDescriptor(t){const e=this._stack,n=e.findIndex((e=>e.id===t));n>-1&&e.splice(n,1)}}function TA(t,e){return t&&e&&t.priority==e.priority&&PA(t.classes)==PA(e.classes)}function IA(t,e){return t.priority>e.priority||!(t.priorityabc
\n //\n if (isAttribute && this._wrapAttributeElement(wrapElement, child)) {\n wrapPositions.push(new Position(parent, i));\n }\n //\n // Wrap the child if it is not an attribute element or if it is an attribute element that should be inside\n // `wrapElement` (due to priority).\n //\n //abc
-->abc
\n //abc
-->abc
\n else if (isText || !isAttribute || shouldABeOutsideB(wrapElement, child)) {\n // Clone attribute.\n const newAttribute = wrapElement._clone();\n // Wrap current node with new attribute.\n child._remove();\n newAttribute._appendChild(child);\n parent._insertChild(i, newAttribute);\n this._addToClonedElementsGroup(newAttribute);\n wrapPositions.push(new Position(parent, i));\n }\n //\n // If other nested attribute is found and it wasn't wrapped (see above), continue wrapping inside it.\n //\n // --> \n //\n else /* if ( isAttribute ) */ {\n this._wrapChildren(child, 0, child.childCount, wrapElement);\n }\n i++;\n }\n // Merge at each wrap.\n let offsetChange = 0;\n for (const position of wrapPositions) {\n position.offset -= offsetChange;\n // Do not merge with elements outside selected children.\n if (position.offset == startOffset) {\n continue;\n }\n const newPosition = this.mergeAttributes(position);\n // If nodes were merged - other merge offsets will change.\n if (!newPosition.isEqual(position)) {\n offsetChange++;\n endOffset--;\n }\n }\n return Range._createFromParentsAndOffsets(parent, startOffset, parent, endOffset);\n }\n /**\n * Unwraps children from provided `unwrapElement`. Only children contained in `parent` element between\n * `startOffset` and `endOffset` will be unwrapped.\n */\n _unwrapChildren(parent, startOffset, endOffset, unwrapElement) {\n let i = startOffset;\n const unwrapPositions = [];\n // Iterate over each element between provided offsets inside parent.\n // We don't use tree walker or range iterator because we will be removing and merging potentially multiple nodes,\n // so it could get messy. It is safer to it manually in this case.\n while (i < endOffset) {\n const child = parent.getChild(i);\n // Skip all text nodes. There should be no container element's here either.\n if (!child.is('attributeElement')) {\n i++;\n continue;\n }\n //\n // (In all examples, assume that `unwrapElement` is `` element.)\n //\n // If the child is similar to the given attribute element, unwrap it - it will be completely removed.\n //\n //abcxyz
-->abcxyz
\n //\n if (child.isSimilar(unwrapElement)) {\n const unwrapped = child.getChildren();\n const count = child.childCount;\n // Replace wrapper element with its children\n child._remove();\n parent._insertChild(i, unwrapped);\n this._removeFromClonedElementsGroup(child);\n // Save start and end position of moved items.\n unwrapPositions.push(new Position(parent, i), new Position(parent, i + count));\n // Skip elements that were unwrapped. Assuming there won't be another element to unwrap in child elements.\n i += count;\n endOffset += count - 1;\n continue;\n }\n //\n // If the child is not similar but is an attribute element, try partial unwrapping - remove the same attributes/styles/classes.\n // Partial unwrapping will happen only if the elements have the same name.\n //\n //abcxyz
-->xyz
\n //abcxyz
-->abcxyz
\n //\n if (this._unwrapAttributeElement(unwrapElement, child)) {\n unwrapPositions.push(new Position(parent, i), new Position(parent, i + 1));\n i++;\n continue;\n }\n //\n // If other nested attribute is found, look through it's children for elements to unwrap.\n //\n //abc
-->
abc
\n //\n this._unwrapChildren(child, 0, child.childCount, unwrapElement);\n i++;\n }\n // Merge at each unwrap.\n let offsetChange = 0;\n for (const position of unwrapPositions) {\n position.offset -= offsetChange;\n // Do not merge with elements outside selected children.\n if (position.offset == startOffset || position.offset == endOffset) {\n continue;\n }\n const newPosition = this.mergeAttributes(position);\n // If nodes were merged - other merge offsets will change.\n if (!newPosition.isEqual(position)) {\n offsetChange++;\n endOffset--;\n }\n }\n return Range._createFromParentsAndOffsets(parent, startOffset, parent, endOffset);\n }\n /**\n * Helper function for `view.writer.wrap`. Wraps range with provided attribute element.\n * This method will also merge newly added attribute element with its siblings whenever possible.\n *\n * Throws {@link module:utils/ckeditorerror~CKEditorError} `view-writer-wrap-invalid-attribute` when passed attribute element is not\n * an instance of {@link module:engine/view/attributeelement~AttributeElement AttributeElement}.\n *\n * @returns New range after wrapping, spanning over wrapping attribute element.\n */\n _wrapRange(range, attribute) {\n // Break attributes at range start and end.\n const { start: breakStart, end: breakEnd } = this._breakAttributesRange(range, true);\n const parentContainer = breakStart.parent;\n // Wrap all children with attribute.\n const newRange = this._wrapChildren(parentContainer, breakStart.offset, breakEnd.offset, attribute);\n // Merge attributes at the both ends and return a new range.\n const start = this.mergeAttributes(newRange.start);\n // If start position was merged - move end position back.\n if (!start.isEqual(newRange.start)) {\n newRange.end.offset--;\n }\n const end = this.mergeAttributes(newRange.end);\n return new Range(start, end);\n }\n /**\n * Helper function for {@link #wrap}. Wraps position with provided attribute element.\n * This method will also merge newly added attribute element with its siblings whenever possible.\n *\n * Throws {@link module:utils/ckeditorerror~CKEditorError} `view-writer-wrap-invalid-attribute` when passed attribute element is not\n * an instance of {@link module:engine/view/attributeelement~AttributeElement AttributeElement}.\n *\n * @returns New position after wrapping.\n */\n _wrapPosition(position, attribute) {\n // Return same position when trying to wrap with attribute similar to position parent.\n if (attribute.isSimilar(position.parent)) {\n return movePositionToTextNode(position.clone());\n }\n // When position is inside text node - break it and place new position between two text nodes.\n if (position.parent.is('$text')) {\n position = breakTextNode(position);\n }\n // Create fake element that will represent position, and will not be merged with other attributes.\n const fakeElement = this.createAttributeElement('_wrapPosition-fake-element');\n fakeElement._priority = Number.POSITIVE_INFINITY;\n fakeElement.isSimilar = () => false;\n // Insert fake element in position location.\n position.parent._insertChild(position.offset, fakeElement);\n // Range around inserted fake attribute element.\n const wrapRange = new Range(position, position.getShiftedBy(1));\n // Wrap fake element with attribute (it will also merge if possible).\n this.wrap(wrapRange, attribute);\n // Remove fake element and place new position there.\n const newPosition = new Position(fakeElement.parent, fakeElement.index);\n fakeElement._remove();\n // If position is placed between text nodes - merge them and return position inside.\n const nodeBefore = newPosition.nodeBefore;\n const nodeAfter = newPosition.nodeAfter;\n if (nodeBefore instanceof Text && nodeAfter instanceof Text) {\n return mergeTextNodes(nodeBefore, nodeAfter);\n }\n // If position is next to text node - move position inside.\n return movePositionToTextNode(newPosition);\n }\n /**\n * Wraps one {@link module:engine/view/attributeelement~AttributeElement AttributeElement} into another by\n * merging them if possible. When merging is possible - all attributes, styles and classes are moved from wrapper\n * element to element being wrapped.\n *\n * @param wrapper Wrapper AttributeElement.\n * @param toWrap AttributeElement to wrap using wrapper element.\n * @returns Returns `true` if elements are merged.\n */\n _wrapAttributeElement(wrapper, toWrap) {\n if (!canBeJoined(wrapper, toWrap)) {\n return false;\n }\n // Can't merge if name or priority differs.\n if (wrapper.name !== toWrap.name || wrapper.priority !== toWrap.priority) {\n return false;\n }\n // Check if attributes can be merged.\n for (const key of wrapper.getAttributeKeys()) {\n // Classes and styles should be checked separately.\n if (key === 'class' || key === 'style') {\n continue;\n }\n // If some attributes are different we cannot wrap.\n if (toWrap.hasAttribute(key) && toWrap.getAttribute(key) !== wrapper.getAttribute(key)) {\n return false;\n }\n }\n // Check if styles can be merged.\n for (const key of wrapper.getStyleNames()) {\n if (toWrap.hasStyle(key) && toWrap.getStyle(key) !== wrapper.getStyle(key)) {\n return false;\n }\n }\n // Move all attributes/classes/styles from wrapper to wrapped AttributeElement.\n for (const key of wrapper.getAttributeKeys()) {\n // Classes and styles should be checked separately.\n if (key === 'class' || key === 'style') {\n continue;\n }\n // Move only these attributes that are not present - other are similar.\n if (!toWrap.hasAttribute(key)) {\n this.setAttribute(key, wrapper.getAttribute(key), toWrap);\n }\n }\n for (const key of wrapper.getStyleNames()) {\n if (!toWrap.hasStyle(key)) {\n this.setStyle(key, wrapper.getStyle(key), toWrap);\n }\n }\n for (const key of wrapper.getClassNames()) {\n if (!toWrap.hasClass(key)) {\n this.addClass(key, toWrap);\n }\n }\n return true;\n }\n /**\n * Unwraps {@link module:engine/view/attributeelement~AttributeElement AttributeElement} from another by removing\n * corresponding attributes, classes and styles. All attributes, classes and styles from wrapper should be present\n * inside element being unwrapped.\n *\n * @param wrapper Wrapper AttributeElement.\n * @param toUnwrap AttributeElement to unwrap using wrapper element.\n * @returns Returns `true` if elements are unwrapped.\n **/\n _unwrapAttributeElement(wrapper, toUnwrap) {\n if (!canBeJoined(wrapper, toUnwrap)) {\n return false;\n }\n // Can't unwrap if name or priority differs.\n if (wrapper.name !== toUnwrap.name || wrapper.priority !== toUnwrap.priority) {\n return false;\n }\n // Check if AttributeElement has all wrapper attributes.\n for (const key of wrapper.getAttributeKeys()) {\n // Classes and styles should be checked separately.\n if (key === 'class' || key === 'style') {\n continue;\n }\n // If some attributes are missing or different we cannot unwrap.\n if (!toUnwrap.hasAttribute(key) || toUnwrap.getAttribute(key) !== wrapper.getAttribute(key)) {\n return false;\n }\n }\n // Check if AttributeElement has all wrapper classes.\n if (!toUnwrap.hasClass(...wrapper.getClassNames())) {\n return false;\n }\n // Check if AttributeElement has all wrapper styles.\n for (const key of wrapper.getStyleNames()) {\n // If some styles are missing or different we cannot unwrap.\n if (!toUnwrap.hasStyle(key) || toUnwrap.getStyle(key) !== wrapper.getStyle(key)) {\n return false;\n }\n }\n // Remove all wrapper's attributes from unwrapped element.\n for (const key of wrapper.getAttributeKeys()) {\n // Classes and styles should be checked separately.\n if (key === 'class' || key === 'style') {\n continue;\n }\n this.removeAttribute(key, toUnwrap);\n }\n // Remove all wrapper's classes from unwrapped element.\n this.removeClass(Array.from(wrapper.getClassNames()), toUnwrap);\n // Remove all wrapper's styles from unwrapped element.\n this.removeStyle(Array.from(wrapper.getStyleNames()), toUnwrap);\n return true;\n }\n /**\n * Helper function used by other `DowncastWriter` methods. Breaks attribute elements at the boundaries of given range.\n *\n * @param range Range which `start` and `end` positions will be used to break attributes.\n * @param forceSplitText If set to `true`, will break text nodes even if they are directly in container element.\n * This behavior will result in incorrect view state, but is needed by other view writing methods which then fixes view state.\n * @returns New range with located at break positions.\n */\n _breakAttributesRange(range, forceSplitText = false) {\n const rangeStart = range.start;\n const rangeEnd = range.end;\n validateRangeContainer(range, this.document);\n // Break at the collapsed position. Return new collapsed range.\n if (range.isCollapsed) {\n const position = this._breakAttributes(range.start, forceSplitText);\n return new Range(position, position);\n }\n const breakEnd = this._breakAttributes(rangeEnd, forceSplitText);\n const count = breakEnd.parent.childCount;\n const breakStart = this._breakAttributes(rangeStart, forceSplitText);\n // Calculate new break end offset.\n breakEnd.offset += breakEnd.parent.childCount - count;\n return new Range(breakStart, breakEnd);\n }\n /**\n * Helper function used by other `DowncastWriter` methods. Breaks attribute elements at given position.\n *\n * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-cannot-break-empty-element` when break position\n * is placed inside {@link module:engine/view/emptyelement~EmptyElement EmptyElement}.\n *\n * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-cannot-break-ui-element` when break position\n * is placed inside {@link module:engine/view/uielement~UIElement UIElement}.\n *\n * @param position Position where to break attributes.\n * @param forceSplitText If set to `true`, will break text nodes even if they are directly in container element.\n * This behavior will result in incorrect view state, but is needed by other view writing methods which then fixes view state.\n * @returns New position after breaking the attributes.\n */\n _breakAttributes(position, forceSplitText = false) {\n const positionOffset = position.offset;\n const positionParent = position.parent;\n // If position is placed inside EmptyElement - throw an exception as we cannot break inside.\n if (position.parent.is('emptyElement')) {\n /**\n * Cannot break an `EmptyElement` instance.\n *\n * This error is thrown if\n * {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes `DowncastWriter#breakAttributes()`}\n * was executed in an incorrect position.\n *\n * @error view-writer-cannot-break-empty-element\n */\n throw new CKEditorError('view-writer-cannot-break-empty-element', this.document);\n }\n // If position is placed inside UIElement - throw an exception as we cannot break inside.\n if (position.parent.is('uiElement')) {\n /**\n * Cannot break a `UIElement` instance.\n *\n * This error is thrown if\n * {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes `DowncastWriter#breakAttributes()`}\n * was executed in an incorrect position.\n *\n * @error view-writer-cannot-break-ui-element\n */\n throw new CKEditorError('view-writer-cannot-break-ui-element', this.document);\n }\n // If position is placed inside RawElement - throw an exception as we cannot break inside.\n if (position.parent.is('rawElement')) {\n /**\n * Cannot break a `RawElement` instance.\n *\n * This error is thrown if\n * {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes `DowncastWriter#breakAttributes()`}\n * was executed in an incorrect position.\n *\n * @error view-writer-cannot-break-raw-element\n */\n throw new CKEditorError('view-writer-cannot-break-raw-element', this.document);\n }\n // There are no attributes to break and text nodes breaking is not forced.\n if (!forceSplitText && positionParent.is('$text') && isContainerOrFragment(positionParent.parent)) {\n return position.clone();\n }\n // Position's parent is container, so no attributes to break.\n if (isContainerOrFragment(positionParent)) {\n return position.clone();\n }\n // Break text and start again in new position.\n if (positionParent.is('$text')) {\n return this._breakAttributes(breakTextNode(position), forceSplitText);\n }\n const length = positionParent.childCount;\n //
foobar{}
\n //foobar[]
\n //foobar[]
\n if (positionOffset == length) {\n const newPosition = new Position(positionParent.parent, positionParent.index + 1);\n return this._breakAttributes(newPosition, forceSplitText);\n }\n else {\n //foo{}bar
\n //foo[]bar
\n //foo{}bar
\n if (positionOffset === 0) {\n const newPosition = new Position(positionParent.parent, positionParent.index);\n return this._breakAttributes(newPosition, forceSplitText);\n }\n //foob{}ar
\n //foob[]ar
\n //foob[]ar
\n //foob[]ar
\n else {\n const offsetAfter = positionParent.index + 1;\n // Break element.\n const clonedNode = positionParent._clone();\n // Insert cloned node to position's parent node.\n positionParent.parent._insertChild(offsetAfter, clonedNode);\n this._addToClonedElementsGroup(clonedNode);\n // Get nodes to move.\n const count = positionParent.childCount - positionOffset;\n const nodesToMove = positionParent._removeChildren(positionOffset, count);\n // Move nodes to cloned node.\n clonedNode._appendChild(nodesToMove);\n // Create new position to work on.\n const newPosition = new Position(positionParent.parent, offsetAfter);\n return this._breakAttributes(newPosition, forceSplitText);\n }\n }\n }\n /**\n * Stores the information that an {@link module:engine/view/attributeelement~AttributeElement attribute element} was\n * added to the tree. Saves the reference to the group in the given element and updates the group, so other elements\n * from the group now keep a reference to the given attribute element.\n *\n * The clones group can be obtained using {@link module:engine/view/attributeelement~AttributeElement#getElementsWithSameId}.\n *\n * Does nothing if added element has no {@link module:engine/view/attributeelement~AttributeElement#id id}.\n *\n * @param element Attribute element to save.\n */\n _addToClonedElementsGroup(element) {\n // Add only if the element is in document tree.\n if (!element.root.is('rootElement')) {\n return;\n }\n // Traverse the element's children recursively to find other attribute elements that also might got inserted.\n // The loop is at the beginning so we can make fast returns later in the code.\n if (element.is('element')) {\n for (const child of element.getChildren()) {\n this._addToClonedElementsGroup(child);\n }\n }\n const id = element.id;\n if (!id) {\n return;\n }\n let group = this._cloneGroups.get(id);\n if (!group) {\n group = new Set();\n this._cloneGroups.set(id, group);\n }\n group.add(element);\n element._clonesGroup = group;\n }\n /**\n * Removes all the information about the given {@link module:engine/view/attributeelement~AttributeElement attribute element}\n * from its clones group.\n *\n * Keep in mind, that the element will still keep a reference to the group (but the group will not keep a reference to it).\n * This allows to reference the whole group even if the element was already removed from the tree.\n *\n * Does nothing if the element has no {@link module:engine/view/attributeelement~AttributeElement#id id}.\n *\n * @param element Attribute element to remove.\n */\n _removeFromClonedElementsGroup(element) {\n // Traverse the element's children recursively to find other attribute elements that also got removed.\n // The loop is at the beginning so we can make fast returns later in the code.\n if (element.is('element')) {\n for (const child of element.getChildren()) {\n this._removeFromClonedElementsGroup(child);\n }\n }\n const id = element.id;\n if (!id) {\n return;\n }\n const group = this._cloneGroups.get(id);\n if (!group) {\n return;\n }\n group.delete(element);\n // Not removing group from element on purpose!\n // If other parts of code have reference to this element, they will be able to get references to other elements from the group.\n }\n}\n// Helper function for `view.writer.wrap`. Checks if given element has any children that are not ui elements.\nfunction _hasNonUiChildren(parent) {\n return Array.from(parent.getChildren()).some(child => !child.is('uiElement'));\n}\n/**\n * The `attribute` passed to {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#wrap()`}\n * must be an instance of {@link module:engine/view/attributeelement~AttributeElement `AttributeElement`}.\n *\n * @error view-writer-wrap-invalid-attribute\n */\n/**\n * Returns first parent container of specified {@link module:engine/view/position~Position Position}.\n * Position's parent node is checked as first, then next parents are checked.\n * Note that {@link module:engine/view/documentfragment~DocumentFragment DocumentFragment} is treated like a container.\n *\n * @param position Position used as a start point to locate parent container.\n * @returns Parent container element or `undefined` if container is not found.\n */\nfunction getParentContainer(position) {\n let parent = position.parent;\n while (!isContainerOrFragment(parent)) {\n if (!parent) {\n return undefined;\n }\n parent = parent.parent;\n }\n return parent;\n}\n/**\n * Checks if first {@link module:engine/view/attributeelement~AttributeElement AttributeElement} provided to the function\n * can be wrapped outside second element. It is done by comparing elements'\n * {@link module:engine/view/attributeelement~AttributeElement#priority priorities}, if both have same priority\n * {@link module:engine/view/element~Element#getIdentity identities} are compared.\n */\nfunction shouldABeOutsideB(a, b) {\n if (a.priority < b.priority) {\n return true;\n }\n else if (a.priority > b.priority) {\n return false;\n }\n // When priorities are equal and names are different - use identities.\n return a.getIdentity() < b.getIdentity();\n}\n/**\n * Returns new position that is moved to near text node. Returns same position if there is no text node before of after\n * specified position.\n *\n * ```html\n *foo[]
->foo{}
\n *[]foo
->{}foo
\n * ```\n *\n * @returns Position located inside text node or same position if there is no text nodes\n * before or after position location.\n */\nfunction movePositionToTextNode(position) {\n const nodeBefore = position.nodeBefore;\n if (nodeBefore && nodeBefore.is('$text')) {\n return new Position(nodeBefore, nodeBefore.data.length);\n }\n const nodeAfter = position.nodeAfter;\n if (nodeAfter && nodeAfter.is('$text')) {\n return new Position(nodeAfter, 0);\n }\n return position;\n}\n/**\n * Breaks text node into two text nodes when possible.\n *\n * ```html\n *foo{}bar
->foo[]bar
\n *{}foobar
->[]foobar
\n *foobar{}
->foobar[]
\n * ```\n *\n * @param position Position that need to be placed inside text node.\n * @returns New position after breaking text node.\n */\nfunction breakTextNode(position) {\n if (position.offset == position.parent.data.length) {\n return new Position(position.parent.parent, position.parent.index + 1);\n }\n if (position.offset === 0) {\n return new Position(position.parent.parent, position.parent.index);\n }\n // Get part of the text that need to be moved.\n const textToMove = position.parent.data.slice(position.offset);\n // Leave rest of the text in position's parent.\n position.parent._data = position.parent.data.slice(0, position.offset);\n // Insert new text node after position's parent text node.\n position.parent.parent._insertChild(position.parent.index + 1, new Text(position.root.document, textToMove));\n // Return new position between two newly created text nodes.\n return new Position(position.parent.parent, position.parent.index + 1);\n}\n/**\n * Merges two text nodes into first node. Removes second node and returns merge position.\n *\n * @param t1 First text node to merge. Data from second text node will be moved at the end of this text node.\n * @param t2 Second text node to merge. This node will be removed after merging.\n * @returns Position after merging text nodes.\n */\nfunction mergeTextNodes(t1, t2) {\n // Merge text data into first text node and remove second one.\n const nodeBeforeLength = t1.data.length;\n t1._data += t2.data;\n t2._remove();\n return new Position(t1, nodeBeforeLength);\n}\nconst validNodesToInsert = [Text, AttributeElement, ContainerElement, EmptyElement, RawElement, UIElement];\n/**\n * Checks if provided nodes are valid to insert.\n *\n * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-insert-invalid-node` when nodes to insert\n * contains instances that are not supported ones (see error description for valid ones.\n */\nfunction validateNodesToInsert(nodes, errorContext) {\n for (const node of nodes) {\n if (!validNodesToInsert.some((validNode => node instanceof validNode))) { // eslint-disable-line no-use-before-define\n /**\n * One of the nodes to be inserted is of an invalid type.\n *\n * Nodes to be inserted with {@link module:engine/view/downcastwriter~DowncastWriter#insert `DowncastWriter#insert()`} should be\n * of the following types:\n *\n * * {@link module:engine/view/attributeelement~AttributeElement AttributeElement},\n * * {@link module:engine/view/containerelement~ContainerElement ContainerElement},\n * * {@link module:engine/view/emptyelement~EmptyElement EmptyElement},\n * * {@link module:engine/view/uielement~UIElement UIElement},\n * * {@link module:engine/view/rawelement~RawElement RawElement},\n * * {@link module:engine/view/text~Text Text}.\n *\n * @error view-writer-insert-invalid-node-type\n */\n throw new CKEditorError('view-writer-insert-invalid-node-type', errorContext);\n }\n if (!node.is('$text')) {\n validateNodesToInsert(node.getChildren(), errorContext);\n }\n }\n}\n/**\n * Checks if node is ContainerElement or DocumentFragment, because in most cases they should be treated the same way.\n *\n * @returns Returns `true` if node is instance of ContainerElement or DocumentFragment.\n */\nfunction isContainerOrFragment(node) {\n return node && (node.is('containerElement') || node.is('documentFragment'));\n}\n/**\n * Checks if {@link module:engine/view/range~Range#start range start} and {@link module:engine/view/range~Range#end range end} are placed\n * inside same {@link module:engine/view/containerelement~ContainerElement container element}.\n * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-invalid-range-container` when validation fails.\n */\nfunction validateRangeContainer(range, errorContext) {\n const startContainer = getParentContainer(range.start);\n const endContainer = getParentContainer(range.end);\n if (!startContainer || !endContainer || startContainer !== endContainer) {\n /**\n * The container of the given range is invalid.\n *\n * This may happen if {@link module:engine/view/range~Range#start range start} and\n * {@link module:engine/view/range~Range#end range end} positions are not placed inside the same container element or\n * a parent container for these positions cannot be found.\n *\n * Methods like {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#remove()`},\n * {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#clean()`},\n * {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#wrap()`},\n * {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#unwrap()`} need to be called\n * on a range that has its start and end positions located in the same container element. Both positions can be\n * nested within other elements (e.g. an attribute element) but the closest container ancestor must be the same.\n *\n * @error view-writer-invalid-range-container\n */\n throw new CKEditorError('view-writer-invalid-range-container', errorContext);\n }\n}\n/**\n * Checks if two attribute elements can be joined together. Elements can be joined together if, and only if\n * they do not have ids specified.\n */\nfunction canBeJoined(a, b) {\n return a.id === null && b.id === null;\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport { keyCodes, isText } from '@ckeditor/ckeditor5-utils';\n/**\n * Set of utilities related to handling block and inline fillers.\n *\n * Browsers do not allow to put caret in elements which does not have height. Because of it, we need to fill all\n * empty elements which should be selectable with elements or characters called \"fillers\". Unfortunately there is no one\n * universal filler, this is why two types are uses:\n *\n * * Block filler is an element which fill block elements, like ``. CKEditor uses `
` as a block filler during the editing,\n * as browsers do natively. So instead of an empty `
` there will be `
FooBarBazBax
\n * Expected DOM:\tBar123Baz456
\n * Input actions:\t[ insert, insert, delete, delete, equal, insert, delete ]\n * Output actions:\t[ insert, replace, delete, equal, replace ]\n * ```\n *\n * @param actions Actions array which is a result of the {@link module:utils/diff~diff} function.\n * @param actualDom Actual DOM children\n * @param expectedDom Expected DOM children.\n * @param comparator A comparator function that should return `true` if the given node should be reused\n * (either by the update of a text node data or an element children list for similar elements).\n * @returns Actions array modified with the `update` actions.\n */\n _findUpdateActions(actions, actualDom, expectedDom, comparator) {\n // If there is no both 'insert' and 'delete' actions, no need to check for replaced elements.\n if (actions.indexOf('insert') === -1 || actions.indexOf('delete') === -1) {\n return actions;\n }\n let newActions = [];\n let actualSlice = [];\n let expectedSlice = [];\n const counter = { equal: 0, insert: 0, delete: 0 };\n for (const action of actions) {\n if (action === 'insert') {\n expectedSlice.push(expectedDom[counter.equal + counter.insert]);\n }\n else if (action === 'delete') {\n actualSlice.push(actualDom[counter.equal + counter.delete]);\n }\n else { // equal\n newActions = newActions.concat(diff(actualSlice, expectedSlice, comparator)\n .map(action => action === 'equal' ? 'update' : action));\n newActions.push('equal');\n // Reset stored elements on 'equal'.\n actualSlice = [];\n expectedSlice = [];\n }\n counter[action]++;\n }\n return newActions.concat(diff(actualSlice, expectedSlice, comparator)\n .map(action => action === 'equal' ? 'update' : action));\n }\n /**\n * Marks text nodes to be synchronized.\n *\n * If a text node is passed, it will be marked. If an element is passed, all descendant text nodes inside it will be marked.\n *\n * @param viewNode View node to sync.\n */\n _markDescendantTextToSync(viewNode) {\n if (!viewNode) {\n return;\n }\n if (viewNode.is('$text')) {\n this.markedTexts.add(viewNode);\n }\n else if (viewNode.is('element')) {\n for (const child of viewNode.getChildren()) {\n this._markDescendantTextToSync(child);\n }\n }\n }\n /**\n * Checks if the selection needs to be updated and possibly updates it.\n */\n _updateSelection() {\n // Block updating DOM selection in (non-Android) Blink while the user is selecting to prevent accidental selection collapsing.\n // Note: Structural changes in DOM must trigger selection rendering, though. Nodes the selection was anchored\n // to, may disappear in DOM which would break the selection (e.g. in real-time collaboration scenarios).\n // https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723\n if (env.isBlink && !env.isAndroid && this.isSelecting && !this.markedChildren.size) {\n return;\n }\n // If there is no selection - remove DOM and fake selections.\n if (this.selection.rangeCount === 0) {\n this._removeDomSelection();\n this._removeFakeSelection();\n return;\n }\n const domRoot = this.domConverter.mapViewToDom(this.selection.editableElement);\n // Do nothing if there is no focus, or there is no DOM element corresponding to selection's editable element.\n if (!this.isFocused || !domRoot) {\n return;\n }\n // Render fake selection - create the fake selection container (if needed) and move DOM selection to it.\n if (this.selection.isFake) {\n this._updateFakeSelection(domRoot);\n }\n // There was a fake selection so remove it and update the DOM selection.\n // This is especially important on Android because otherwise IME will try to compose over the fake selection container.\n else if (this._fakeSelectionContainer && this._fakeSelectionContainer.isConnected) {\n this._removeFakeSelection();\n this._updateDomSelection(domRoot);\n }\n // Update the DOM selection in case of a plain selection change (no fake selection is involved).\n // On non-Android the whole rendering is disabled in composition mode (including DOM selection update),\n // but updating DOM selection should be also disabled on Android if in the middle of the composition\n // (to not interrupt it).\n else if (!(this.isComposing && env.isAndroid)) {\n this._updateDomSelection(domRoot);\n }\n }\n /**\n * Updates the fake selection.\n *\n * @param domRoot A valid DOM root where the fake selection container should be added.\n */\n _updateFakeSelection(domRoot) {\n const domDocument = domRoot.ownerDocument;\n if (!this._fakeSelectionContainer) {\n this._fakeSelectionContainer = createFakeSelectionContainer(domDocument);\n }\n const container = this._fakeSelectionContainer;\n // Bind fake selection container with the current selection *position*.\n this.domConverter.bindFakeSelection(container, this.selection);\n if (!this._fakeSelectionNeedsUpdate(domRoot)) {\n return;\n }\n if (!container.parentElement || container.parentElement != domRoot) {\n domRoot.appendChild(container);\n }\n container.textContent = this.selection.fakeSelectionLabel || '\\u00A0';\n const domSelection = domDocument.getSelection();\n const domRange = domDocument.createRange();\n domSelection.removeAllRanges();\n domRange.selectNodeContents(container);\n domSelection.addRange(domRange);\n }\n /**\n * Updates the DOM selection.\n *\n * @param domRoot A valid DOM root where the DOM selection should be rendered.\n */\n _updateDomSelection(domRoot) {\n const domSelection = domRoot.ownerDocument.defaultView.getSelection();\n // Let's check whether DOM selection needs updating at all.\n if (!this._domSelectionNeedsUpdate(domSelection)) {\n return;\n }\n // Multi-range selection is not available in most browsers, and, at least in Chrome, trying to\n // set such selection, that is not continuous, throws an error. Because of that, we will just use anchor\n // and focus of view selection.\n // Since we are not supporting multi-range selection, we also do not need to check if proper editable is\n // selected. If there is any editable selected, it is okay (editable is taken from selection anchor).\n const anchor = this.domConverter.viewPositionToDom(this.selection.anchor);\n const focus = this.domConverter.viewPositionToDom(this.selection.focus);\n // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {\n // @if CK_DEBUG_TYPING // \tconsole.info( '%c[Renderer]%c Update DOM selection:',\n // @if CK_DEBUG_TYPING // \t\t'color: green;font-weight: bold', '', anchor, focus\n // @if CK_DEBUG_TYPING // \t);\n // @if CK_DEBUG_TYPING // }\n domSelection.setBaseAndExtent(anchor.parent, anchor.offset, focus.parent, focus.offset);\n // Firefox–specific hack (https://github.com/ckeditor/ckeditor5-engine/issues/1439).\n if (env.isGecko) {\n fixGeckoSelectionAfterBr(focus, domSelection);\n }\n }\n /**\n * Checks whether a given DOM selection needs to be updated.\n *\n * @param domSelection The DOM selection to check.\n */\n _domSelectionNeedsUpdate(domSelection) {\n if (!this.domConverter.isDomSelectionCorrect(domSelection)) {\n // Current DOM selection is in incorrect position. We need to update it.\n return true;\n }\n const oldViewSelection = domSelection && this.domConverter.domSelectionToView(domSelection);\n if (oldViewSelection && this.selection.isEqual(oldViewSelection)) {\n return false;\n }\n // If selection is not collapsed, it does not need to be updated if it is similar.\n if (!this.selection.isCollapsed && this.selection.isSimilar(oldViewSelection)) {\n // Selection did not changed and is correct, do not update.\n return false;\n }\n // Selections are not similar.\n return true;\n }\n /**\n * Checks whether the fake selection needs to be updated.\n *\n * @param domRoot A valid DOM root where a new fake selection container should be added.\n */\n _fakeSelectionNeedsUpdate(domRoot) {\n const container = this._fakeSelectionContainer;\n const domSelection = domRoot.ownerDocument.getSelection();\n // Fake selection needs to be updated if there's no fake selection container, or the container currently sits\n // in a different root.\n if (!container || container.parentElement !== domRoot) {\n return true;\n }\n // Make sure that the selection actually is within the fake selection.\n if (domSelection.anchorNode !== container && !container.contains(domSelection.anchorNode)) {\n return true;\n }\n return container.textContent !== this.selection.fakeSelectionLabel;\n }\n /**\n * Removes the DOM selection.\n */\n _removeDomSelection() {\n for (const doc of this.domDocuments) {\n const domSelection = doc.getSelection();\n if (domSelection.rangeCount) {\n const activeDomElement = doc.activeElement;\n const viewElement = this.domConverter.mapDomToView(activeDomElement);\n if (activeDomElement && viewElement) {\n domSelection.removeAllRanges();\n }\n }\n }\n }\n /**\n * Removes the fake selection.\n */\n _removeFakeSelection() {\n const container = this._fakeSelectionContainer;\n if (container) {\n container.remove();\n }\n }\n /**\n * Checks if focus needs to be updated and possibly updates it.\n */\n _updateFocus() {\n if (this.isFocused) {\n const editable = this.selection.editableElement;\n if (editable) {\n this.domConverter.focus(editable);\n }\n }\n }\n}\n/**\n * Checks if provided element is editable.\n */\nfunction isEditable(element) {\n if (element.getAttribute('contenteditable') == 'false') {\n return false;\n }\n const parent = element.findAncestor(element => element.hasAttribute('contenteditable'));\n return !parent || parent.getAttribute('contenteditable') == 'true';\n}\n/**\n * Adds inline filler at a given position.\n *\n * The position can be given as an array of DOM nodes and an offset in that array,\n * or a DOM parent element and an offset in that element.\n *\n * @returns The DOM text node that contains an inline filler.\n */\nfunction addInlineFiller(domDocument, domParentOrArray, offset) {\n const childNodes = domParentOrArray instanceof Array ? domParentOrArray : domParentOrArray.childNodes;\n const nodeAfterFiller = childNodes[offset];\n if (isText(nodeAfterFiller)) {\n nodeAfterFiller.data = INLINE_FILLER + nodeAfterFiller.data;\n return nodeAfterFiller;\n }\n else {\n const fillerNode = domDocument.createTextNode(INLINE_FILLER);\n if (Array.isArray(domParentOrArray)) {\n childNodes.splice(offset, 0, fillerNode);\n }\n else {\n insertAt(domParentOrArray, offset, fillerNode);\n }\n return fillerNode;\n }\n}\n/**\n * Whether two DOM nodes should be considered as similar.\n * Nodes are considered similar if they have the same tag name.\n */\nfunction areSimilarElements(node1, node2) {\n return isNode(node1) && isNode(node2) &&\n !isText(node1) && !isText(node2) &&\n !isComment(node1) && !isComment(node2) &&\n node1.tagName.toLowerCase() === node2.tagName.toLowerCase();\n}\n/**\n * Whether two DOM nodes are text nodes.\n */\nfunction areTextNodes(node1, node2) {\n return isNode(node1) && isNode(node2) &&\n isText(node1) && isText(node2);\n}\n/**\n * Whether two dom nodes should be considered as the same.\n * Two nodes which are considered the same are:\n *\n * * Text nodes with the same text.\n * * Element nodes represented by the same object.\n * * Two block filler elements.\n *\n * @param blockFillerMode Block filler mode, see {@link module:engine/view/domconverter~DomConverter#blockFillerMode}.\n */\nfunction sameNodes(domConverter, actualDomChild, expectedDomChild) {\n // Elements.\n if (actualDomChild === expectedDomChild) {\n return true;\n }\n // Texts.\n else if (isText(actualDomChild) && isText(expectedDomChild)) {\n return actualDomChild.data === expectedDomChild.data;\n }\n // Block fillers.\n else if (domConverter.isBlockFiller(actualDomChild) &&\n domConverter.isBlockFiller(expectedDomChild)) {\n return true;\n }\n // Not matching types.\n return false;\n}\n/**\n * The following is a Firefox–specific hack (https://github.com/ckeditor/ckeditor5-engine/issues/1439).\n * When the native DOM selection is at the end of the block and preceded byfoo
[]