From 196f325fc0d55bd955e13d33424f71d65c334695 Mon Sep 17 00:00:00 2001
From: Neil Jenkins <neil@nmjenkins.com>
Date: Wed, 17 Jul 2024 16:21:35 +1000
Subject: [PATCH] Release v2.2.9

---
 CHANGELOG.md                   |  9 +++++++++
 dist/squire-raw.js             |  2 ++
 dist/squire-raw.mjs            |  2 ++
 dist/squire.js                 | 16 ++++++++--------
 dist/squire.js.map             |  4 ++--
 dist/squire.mjs                | 16 ++++++++--------
 dist/squire.mjs.map            |  4 ++--
 dist/types/Clipboard.d.ts      |  2 +-
 dist/types/Clipboard.d.ts.map  |  2 +-
 dist/types/Editor.d.ts.map     |  2 +-
 dist/types/node/Block.d.ts.map |  2 +-
 dist/types/node/Node.d.ts.map  |  2 +-
 package.json                   |  2 +-
 13 files changed, 39 insertions(+), 26 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 63f422f..64d6df8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file, starting fr
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [2.2.9] - 2024-07-17
+
+### Fixed
+
+-   Fix incorrect styles can be applied after splitting at the end of inline
+    formatting, blurring, and focusing again.
+-   Fix font info sometimes not returned even when selection within a single
+    text span.
+
 ## [2.2.8] - 2024-02-21
 
 ### Fixed
diff --git a/dist/squire-raw.js b/dist/squire-raw.js
index caf607b..c480580 100644
--- a/dist/squire-raw.js
+++ b/dist/squire-raw.js
@@ -2153,6 +2153,7 @@
       this.addEventListener("mousedown", this._disableRestoreSelection);
       this.addEventListener("touchstart", this._disableRestoreSelection);
       this.addEventListener("focus", this._restoreSelection);
+      this.addEventListener("blur", this._removeZWS);
       this._isShiftDown = false;
       this.addEventListener("cut", _onCut);
       this.addEventListener("copy", _onCopy);
@@ -3022,6 +3023,7 @@
       if (!range) {
         range = this.getSelection();
       }
+      moveRangeBoundariesDownTree(range);
       let seenAttributes = 0;
       let element = range.commonAncestorContainer;
       if (range.collapsed || element instanceof Text) {
diff --git a/dist/squire-raw.mjs b/dist/squire-raw.mjs
index 6af222b..2ccd16c 100644
--- a/dist/squire-raw.mjs
+++ b/dist/squire-raw.mjs
@@ -2150,6 +2150,7 @@ var Squire = class {
     this.addEventListener("mousedown", this._disableRestoreSelection);
     this.addEventListener("touchstart", this._disableRestoreSelection);
     this.addEventListener("focus", this._restoreSelection);
+    this.addEventListener("blur", this._removeZWS);
     this._isShiftDown = false;
     this.addEventListener("cut", _onCut);
     this.addEventListener("copy", _onCopy);
@@ -3019,6 +3020,7 @@ var Squire = class {
     if (!range) {
       range = this.getSelection();
     }
+    moveRangeBoundariesDownTree(range);
     let seenAttributes = 0;
     let element = range.commonAncestorContainer;
     if (range.collapsed || element instanceof Text) {
diff --git a/dist/squire.js b/dist/squire.js
index f449e41..4ef578c 100644
--- a/dist/squire.js
+++ b/dist/squire.js
@@ -1,11 +1,11 @@
-"use strict";(()=>{var it=()=>!0,T=class{constructor(t,e,n){this.root=t,this.currentNode=t,this.nodeType=e,this.filter=n||it}isAcceptableNode(t){let e=t.nodeType;return!!((e===Node.ELEMENT_NODE?1:e===Node.TEXT_NODE?4:0)&this.nodeType)&&this.filter(t)}nextNode(){let t=this.root,e=this.currentNode,n;for(;;){for(n=e.firstChild;!n&&e&&e!==t;)n=e.nextSibling,n||(e=e.parentNode);if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}previousNode(){let t=this.root,e=this.currentNode,n;for(;;){if(e===t)return null;if(n=e.previousSibling,n)for(;e=n.lastChild;)n=e;else n=e.parentNode;if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}previousPONode(){let t=this.root,e=this.currentNode,n;for(;;){for(n=e.lastChild;!n&&e&&e!==t;)n=e.previousSibling,n||(e=e.parentNode);if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}};var B="\u200B",J=navigator.userAgent,de=/Mac OS X/.test(J),fe=/Windows NT/.test(J),_e=/iP(?:ad|hone|od)/.test(J)||de&&!!navigator.maxTouchPoints,xt=/Android/.test(J),Fe=/Gecko\//.test(J),se=/Edge\//.test(J),st=!se&&/WebKit\//.test(J),k=de||_e?"Meta-":"Ctrl-",oe=st,He="onbeforeinput"in document&&"inputType"in new InputEvent("input"),I=/[^ \t\r\n]/;var at=/^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/,ct=new Set(["BR","HR","IFRAME","IMG","INPUT"]),dt=0,xe=1,Pe=2,Ue=3,ue=new WeakMap,qe=()=>{ue=new WeakMap},M=o=>ct.has(o.nodeName),be=o=>{switch(o.nodeType){case 3:return xe;case 1:case 11:if(ue.has(o))return ue.get(o);break;default:return dt}let t;return Array.from(o.childNodes).every(N)?at.test(o.nodeName)?t=xe:t=Pe:t=Ue,ue.set(o,t),t},N=o=>be(o)===xe,P=o=>be(o)===Pe,Q=o=>be(o)===Ue;var p=(o,t,e)=>{let n=document.createElement(o);if(t instanceof Array&&(e=t,t=null),t)for(let i in t){let s=t[i];s!==void 0&&n.setAttribute(i,s)}return e&&e.forEach(i=>n.appendChild(i)),n},Le=(o,t)=>M(o)||o.nodeType!==t.nodeType||o.nodeName!==t.nodeName?!1:o instanceof HTMLElement&&t instanceof HTMLElement?o.nodeName!=="A"&&o.className===t.className&&o.style.cssText===t.style.cssText:!0,he=(o,t,e)=>{if(o.nodeName!==t)return!1;for(let n in e)if(!("getAttribute"in o)||o.getAttribute(n)!==e[n])return!1;return!0},S=(o,t,e,n)=>{for(;o&&o!==t;){if(he(o,e,n))return o;o=o.parentNode}return null},me=(o,t)=>{let e=o.childNodes;for(;t&&o instanceof Element;)o=e[t-1],e=o.childNodes,t=e.length;return o},Re=(o,t)=>{let e=o;if(e instanceof Element){let n=e.childNodes;if(t<n.length)e=n[t];else{for(;e&&!e.nextSibling;)e=e.parentNode;e&&(e=e.nextSibling)}}return e},y=o=>o instanceof Element||o instanceof DocumentFragment?o.childNodes.length:o instanceof CharacterData?o.length:0,C=o=>{let t=document.createDocumentFragment(),e=o.firstChild;for(;e;)t.appendChild(e),e=o.firstChild;return t},E=o=>{let t=o.parentNode;return t&&t.removeChild(o),o},R=(o,t)=>{let e=o.parentNode;e&&e.replaceChild(t,o)};var ft=o=>o instanceof Element?o.nodeName==="BR":I.test(o.data),ee=(o,t)=>{let e=o.parentNode;for(;N(e);)e=e.parentNode;let n=new T(e,5,ft);return n.currentNode=o,!!n.nextNode()||t&&!n.previousNode()},re=(o,t)=>{let e=new T(o,4),n,i;for(;n=e.nextNode();)for(;(i=n.data.indexOf(B))>-1&&(!t||n.parentNode!==t);)if(n.length===1){let s=n,r=s.parentNode;for(;r&&(r.removeChild(s),e.currentNode=r,!(!N(r)||y(r)));)s=r,r=s.parentNode;break}else n.deleteData(i,1)};var ut=0,ht=1,mt=2,pt=3,K=(o,t,e)=>{let n=document.createRange();if(n.selectNode(t),e){let i=o.compareBoundaryPoints(pt,n)>-1,s=o.compareBoundaryPoints(ht,n)<1;return!i&&!s}else{let i=o.compareBoundaryPoints(ut,n)<1,s=o.compareBoundaryPoints(mt,n)>-1;return i&&s}},_=o=>{let{startContainer:t,startOffset:e,endContainer:n,endOffset:i}=o;for(;!(t instanceof Text);){let s=t.childNodes[e];if(!s||M(s)){if(e&&(s=t.childNodes[e-1],s instanceof Text)){let r=s,l;for(;!r.length&&(l=r.previousSibling)&&l instanceof Text;)r.remove(),r=l;t=r,e=r.data.length}break}t=s,e=0}if(i)for(;!(n instanceof Text);){let s=n.childNodes[i-1];if(!s||M(s)){if(s&&s.nodeName==="BR"&&!ee(s,!1)){i-=1;continue}break}n=s,i=y(n)}else for(;!(n instanceof Text);){let s=n.firstChild;if(!s||M(s))break;n=s}o.setStart(t,e),o.setEnd(n,i)},q=(o,t,e,n)=>{let i=o.startContainer,s=o.startOffset,r=o.endContainer,l=o.endOffset,a;for(t||(t=o.commonAncestorContainer),e||(e=t);!s&&i!==t&&i!==n;)a=i.parentNode,s=Array.from(a.childNodes).indexOf(i),i=a;for(;!(r===e||r===n||(r.nodeType!==3&&r.childNodes[l]&&r.childNodes[l].nodeName==="BR"&&!ee(r.childNodes[l],!1)&&(l+=1),l!==y(r)));)a=r.parentNode,l=Array.from(a.childNodes).indexOf(r)+1,r=a;o.setStart(i,s),o.setEnd(r,l)},Oe=(o,t,e)=>{let n=S(o.endContainer,e,t);if(n&&(n=n.parentNode)){let i=o.cloneRange();q(i,n,n,e),i.endContainer===n&&(o.setStart(i.endContainer,i.endOffset),o.setEnd(i.endContainer,i.endOffset))}return o};var x=o=>{let t=null;if(o instanceof Text)return o;if(N(o)){let e=o.firstChild;if(oe)for(;e&&e instanceof Text&&!e.data;)o.removeChild(e),e=o.firstChild;e||(oe?t=document.createTextNode(B):t=document.createTextNode(""))}else if((o instanceof Element||o instanceof DocumentFragment)&&!o.querySelector("BR")){t=p("BR");let e=o,n;for(;(n=e.lastElementChild)&&!N(n);)e=n;o=e}if(t)try{o.appendChild(t)}catch(e){}return o},D=(o,t)=>{let e=null;return Array.from(o.childNodes).forEach(n=>{let i=n.nodeName==="BR";!i&&N(n)?(e||(e=p("DIV")),e.appendChild(n)):(i||e)&&(e||(e=p("DIV")),x(e),i?o.replaceChild(e,n):o.insertBefore(e,n),e=null),Q(n)&&D(n,t)}),e&&o.appendChild(x(e)),o},w=(o,t,e,n)=>{if(o instanceof Text&&o!==e){if(typeof t!="number")throw new Error("Offset must be a number to split text node!");if(!o.parentNode)throw new Error("Cannot split text node with no parent!");return w(o.parentNode,o.splitText(t),e,n)}let i=typeof t=="number"?t<o.childNodes.length?o.childNodes[t]:null:t,s=o.parentNode;if(!s||o===e||!(o instanceof Element))return i;let r=o.cloneNode(!1);for(;i;){let l=i.nextSibling;r.appendChild(i),i=l}return o instanceof HTMLOListElement&&S(o,n,"BLOCKQUOTE")&&(r.start=(+o.start||1)+o.childNodes.length-1),x(o),x(r),s.insertBefore(r,o.nextSibling),w(s,r,e,n)},We=(o,t)=>{let e=o.childNodes,n=e.length,i=[];for(;n--;){let s=e[n],r=n?e[n-1]:null;if(r&&N(s)&&Le(s,r))t.startContainer===s&&(t.startContainer=r,t.startOffset+=y(r)),t.endContainer===s&&(t.endContainer=r,t.endOffset+=y(r)),t.startContainer===o&&(t.startOffset>n?t.startOffset-=1:t.startOffset===n&&(t.startContainer=r,t.startOffset=y(r))),t.endContainer===o&&(t.endOffset>n?t.endOffset-=1:t.endOffset===n&&(t.endContainer=r,t.endOffset=y(r))),E(s),s instanceof Text?r.appendData(s.data):i.push(C(s));else if(s instanceof Element){let l;for(;l=i.pop();)s.appendChild(l);We(s,t)}}},te=(o,t)=>{let e=o instanceof Text?o.parentNode:o;if(e instanceof Element){let n={startContainer:t.startContainer,startOffset:t.startOffset,endContainer:t.endContainer,endOffset:t.endOffset};We(e,n),t.setStart(n.startContainer,n.startOffset),t.setEnd(n.endContainer,n.endOffset)}},Y=(o,t,e,n)=>{let i=t,s,r;for(;(s=i.parentNode)&&s!==n&&s instanceof Element&&s.childNodes.length===1;)i=s;E(i),r=o.childNodes.length;let l=o.lastChild;l&&l.nodeName==="BR"&&(o.removeChild(l),r-=1),o.appendChild(C(t)),e.setStart(o,r),e.collapse(!0),te(o,e)},F=(o,t)=>{let e=o.previousSibling,n=o.firstChild,i=o.nodeName==="LI";if(!(i&&(!n||!/^[OU]L$/.test(n.nodeName)))){if(e&&Le(e,o)){if(!Q(e))if(i){let r=p("DIV");r.appendChild(C(e)),e.appendChild(r)}else return;E(o);let s=!Q(o);e.appendChild(C(o)),s&&D(e,t),n&&F(n,t)}else if(i){let s=p("DIV");o.insertBefore(s,n),x(s)}}};var Ke={"font-weight":{regexp:/^bold|^700/i,replace(){return p("B")}},"font-style":{regexp:/^italic/i,replace(){return p("I")}},"font-family":{regexp:I,replace(o,t){return p("SPAN",{class:o.fontFamily,style:"font-family:"+t})}},"font-size":{regexp:I,replace(o,t){return p("SPAN",{class:o.fontSize,style:"font-size:"+t})}},"text-decoration":{regexp:/^underline/i,replace(){return p("U")}}},Nt=(o,t,e)=>{let n=o.style,i,s;for(let r in Ke){let l=Ke[r],a=n.getPropertyValue(r);if(a&&l.regexp.test(a)){let d=l.replace(e.classNames,a);if(d.nodeName===o.nodeName&&d.className===o.className)continue;s||(s=d),i&&i.appendChild(d),i=d,o.style.removeProperty(r)}}return s&&i&&(i.appendChild(C(o)),o.style.cssText?o.appendChild(s):R(o,s)),i||o},pe=o=>(t,e)=>{let n=p(o),i=t.attributes;for(let s=0,r=i.length;s<r;s+=1){let l=i[s];n.setAttribute(l.name,l.value)}return e.replaceChild(n,t),n.appendChild(C(t)),n},St={1:"10",2:"13",3:"16",4:"18",5:"24",6:"32",7:"48"},gt={STRONG:pe("B"),EM:pe("I"),INS:pe("U"),STRIKE:pe("S"),SPAN:Nt,FONT:(o,t,e)=>{let n=o,i=n.face,s=n.size,r=n.color,l=e.classNames,a,d,c,f,u;return i&&(a=p("SPAN",{class:l.fontFamily,style:"font-family:"+i}),u=a,f=a),s&&(d=p("SPAN",{class:l.fontSize,style:"font-size:"+St[s]+"px"}),u||(u=d),f&&f.appendChild(d),f=d),r&&/^#?([\dA-F]{3}){1,2}$/i.test(r)&&(r.charAt(0)!=="#"&&(r="#"+r),c=p("SPAN",{class:l.color,style:"color:"+r}),u||(u=c),f&&f.appendChild(c),f=c),(!u||!f)&&(u=f=p("SPAN")),t.replaceChild(u,n),f.appendChild(C(n)),f},TT:(o,t,e)=>{let n=p("SPAN",{class:e.classNames.fontFamily,style:'font-family:menlo,consolas,"courier new",monospace'});return t.replaceChild(n,o),n.appendChild(C(o)),n}},Et=/^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/,Tt=/^(?:HEAD|META|STYLE)/,Ne=(o,t,e)=>{let n=o.childNodes,i=o;for(;N(i);)i=i.parentNode;let s=new T(i,5);for(let r=0,l=n.length;r<l;r+=1){let a=n[r],d=a.nodeName,c=gt[d];if(a instanceof HTMLElement){let f=a.childNodes.length;if(c)a=c(a,o,t);else if(Tt.test(d)){o.removeChild(a),r-=1,l-=1;continue}else if(!Et.test(d)&&!N(a)){r-=1,l+=f-1,o.replaceChild(C(a),a);continue}f&&Ne(a,t,e||d==="PRE")}else{if(a instanceof Text){let f=a.data,u=!I.test(f.charAt(0)),m=!I.test(f.charAt(f.length-1));if(e||!u&&!m)continue;if(u){s.currentNode=a;let h;for(;(h=s.previousPONode())&&!(h.nodeName==="IMG"||h instanceof Text&&I.test(h.data));)if(!N(h)){h=null;break}f=f.replace(/^[ \t\r\n]+/g,h?" ":"")}if(m){s.currentNode=a;let h;for(;(h=s.nextNode())&&!(h.nodeName==="IMG"||h instanceof Text&&I.test(h.data));)if(!N(h)){h=null;break}f=f.replace(/[ \t\r\n]+$/g,h?" ":"")}if(f){a.data=f;continue}}o.removeChild(a),r-=1,l-=1}}return o},Se=o=>{let t=o.childNodes,e=t.length;for(;e--;){let n=t[e];n instanceof Element&&!M(n)?(Se(n),N(n)&&!n.firstChild&&o.removeChild(n)):n instanceof Text&&!n.data&&o.removeChild(n)}},le=(o,t,e)=>{let n=o.querySelectorAll("BR"),i=[],s=n.length;for(let r=0;r<s;r+=1)i[r]=ee(n[r],e);for(;s--;){let r=n[s],l=r.parentNode;l&&(i[s]?N(l)||D(l,t):E(r))}},ke=o=>o.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;").split('"').join("&quot;");var ae=(o,t)=>{let e=new T(t,1,P);return e.currentNode=o,e},z=(o,t)=>{let e=ae(o,t).previousNode();return e!==t?e:null},W=(o,t)=>{let e=ae(o,t).nextNode();return e!==t?e:null},ce=o=>!o.textContent&&!o.querySelector("IMG");var L=(o,t)=>{let e=o.startContainer,n;if(N(e))n=z(e,t);else if(e!==t&&e instanceof HTMLElement&&P(e))n=e;else{let i=me(e,o.startOffset);n=W(i,t)}return n&&K(o,n,!0)?n:null},G=(o,t)=>{let e=o.endContainer,n;if(N(e))n=z(e,t);else if(e!==t&&e instanceof HTMLElement&&P(e))n=e;else{let i=Re(e,o.endOffset);if(!i||!t.contains(i)){i=t;let s;for(;s=i.lastChild;)i=s}n=z(i,t)}return n&&K(o,n,!0)?n:null},ze=o=>o instanceof Text?I.test(o.data):o.nodeName==="IMG",X=(o,t)=>{let e=o.startContainer,n=o.startOffset,i;if(e instanceof Text){let l=e.data;for(let a=n;a>0;a-=1)if(l.charAt(a-1)!==B)return!1;i=e}else if(i=Re(e,n),i&&!t.contains(i)&&(i=null),!i&&(i=me(e,n),i instanceof Text&&i.length))return!1;let s=L(o,t);if(!s)return!1;let r=new T(s,5,ze);return r.currentNode=i,!r.previousNode()},Z=(o,t)=>{let e=o.endContainer,n=o.endOffset,i;if(e instanceof Text){let l=e.data,a=l.length;for(let d=n;d<a;d+=1)if(l.charAt(d)!==B)return!1;i=e}else i=me(e,n);let s=G(o,t);if(!s)return!1;let r=new T(s,5,ze);return r.currentNode=i,!r.nextNode()},ye=(o,t)=>{let e=L(o,t),n=G(o,t),i;e&&n&&(i=e.parentNode,o.setStart(i,Array.from(i.childNodes).indexOf(e)),i=n.parentNode,o.setEnd(i,Array.from(i.childNodes).indexOf(n)+1))};function $(o,t,e,n){let i=document.createRange();return i.setStart(o,t),e&&typeof n=="number"?i.setEnd(e,n):i.setEnd(o,t),i}var j=(o,t)=>{let{startContainer:e,startOffset:n,endContainer:i,endOffset:s}=o,r;if(e instanceof Text){let a=e.parentNode;if(r=a.childNodes,n===e.length)n=Array.from(r).indexOf(e)+1,o.collapsed&&(i=a,s=n);else{if(n){let d=e.splitText(n);i===e?(s-=n,i=d):i===a&&(s+=1),e=d}n=Array.from(r).indexOf(e)}e=a}else r=e.childNodes;let l=r.length;n===l?e.appendChild(t):e.insertBefore(t,r[n]),e===i&&(s+=r.length-l),o.setStart(e,n),o.setEnd(i,s)},Be=(o,t,e)=>{let n=document.createDocumentFragment();if(o.collapsed)return n;t||(t=o.commonAncestorContainer),t instanceof Text&&(t=t.parentNode);let i=o.startContainer,s=o.startOffset,r=w(o.endContainer,o.endOffset,t,e),l=0,a=w(i,s,t,e);for(;a&&a!==r;){let d=a.nextSibling;n.appendChild(a),a=d}return i instanceof Text&&r instanceof Text&&(i.appendData(r.data),E(r),r=i,l=s),o.setStart(i,s),r?o.setEnd(r,l):o.setEnd(t,t.childNodes.length),x(t),n},Ge=(o,t,e)=>{o.currentNode=e;let n;for(;n=o[t]();){if(n instanceof Text||M(n))return n;if(!N(n))return null}return null},H=(o,t)=>{let e=L(o,t),n=G(o,t),i=e!==n;e&&n&&(_(o),q(o,e,n,t));let s=Be(o,null,t);_(o),i&&(n=G(o,t),e&&n&&e!==n&&Y(e,n,o,t)),e&&x(e);let r=t.firstChild;(!r||r.nodeName==="BR")&&(x(t),t.firstChild&&o.selectNodeContents(t.firstChild)),o.collapse(!0);let l=o.startContainer,a=o.startOffset,d=new T(t,5),c=l,f=a;(!(c instanceof Text)||f===c.data.length)&&(c=Ge(d,"nextNode",c),f=0);let u=l,m=a-1;(!(u instanceof Text)||m===-1)&&(u=Ge(d,"previousPONode",c||(l instanceof Text?l:l.childNodes[a]||l)),u instanceof Text&&(m=u.data.length));let h=null,g=0;return c instanceof Text&&c.data.charAt(f)===" "&&X(o,t)?(h=c,g=f):u instanceof Text&&u.data.charAt(m)===" "&&(c instanceof Text&&c.data.charAt(f)===" "||Z(o,t))&&(h=u,g=m),h&&h.replaceData(g,1,"\xA0"),o.setStart(l,a),o.collapse(!0),s},Ze=(o,t,e)=>{let n=t.firstChild&&N(t.firstChild),i;for(D(t,e),i=t;i=W(i,e);)x(i);o.collapsed||H(o,e),_(o),o.collapse(!1);let s=S(o.endContainer,e,"BLOCKQUOTE")||e,r=L(o,e),l=null,a=W(t,t),d=!n&&!!r&&ce(r);if(r&&a&&!d&&!S(a,t,"PRE")&&!S(a,t,"TABLE")){q(o,r,r,e),o.collapse(!0);let c=o.endContainer,f=o.endOffset;if(le(r,e,!1),N(c)){let u=w(c,f,z(c,e)||e,e);c=u.parentNode,f=Array.from(c.childNodes).indexOf(u)}if(f!==y(c))for(l=document.createDocumentFragment();i=c.childNodes[f];)l.appendChild(i);Y(c,a,o,e),f=Array.from(c.parentNode.childNodes).indexOf(c)+1,c=c.parentNode,o.setEnd(c,f)}if(y(t)){d&&r&&(o.setEndBefore(r),o.collapse(!1),E(r)),q(o,s,s,e);let c=w(o.endContainer,o.endOffset,s,e),f=c?c.previousSibling:s.lastChild;s.insertBefore(t,c),c?o.setEndBefore(c):o.setEnd(s,y(s)),r=G(o,e),_(o);let u=o.endContainer,m=o.endOffset;c&&Q(c)&&F(c,e),c=f&&f.nextSibling,c&&Q(c)&&F(c,e),o.setEnd(u,m)}if(l&&r){let c=o.cloneRange();x(l),Y(r,l,c,e),o.setEnd(c.endContainer,c.endOffset)}_(o)};var ge=o=>{if(o.collapsed)return"";let t=o.startContainer,e=o.endContainer,n=new T(o.commonAncestorContainer,5,a=>K(o,a,!0));n.currentNode=t;let i=t,s="",r=!1,l;for((!(i instanceof Element)&&!(i instanceof Text)||!n.filter(i))&&(i=n.nextNode());i;)i instanceof Text?(l=i.data,l&&/\S/.test(l)&&(i===e&&(l=l.slice(0,o.endOffset)),i===t&&(l=l.slice(o.startOffset)),s+=l,r=!0)):(i.nodeName==="BR"||r&&!N(i))&&(s+=`
-`,r=!1),i=n.nextNode();return s=s.replace(/ /g," "),s};var De=Array.prototype.indexOf,je=(o,t,e,n,i,s,r)=>{let l=o.clipboardData;if(se||!l)return!1;let a=s?"":ge(t),d=L(t,e),c=G(t,e),f=e;d===c&&(d!=null&&d.contains(t.commonAncestorContainer))&&(f=d);let u;n?u=H(t,e):(t=t.cloneRange(),_(t),q(t,f,f,e),u=t.cloneContents());let m=t.commonAncestorContainer;for(m instanceof Text&&(m=m.parentNode);m&&m!==f;){let g=m.cloneNode(!1);g.appendChild(u),u=g,m=m.parentNode}let h;if(u.childNodes.length===1&&u.childNodes[0]instanceof Text)a=u.childNodes[0].data.replace(/ /g," "),r=!0;else{let g=p("DIV");g.appendChild(u),h=g.innerHTML,i&&(h=i(h))}return s&&h!==void 0&&(a=s(h)),fe&&(a=a.replace(/\r?\n/g,`\r
-`)),!r&&h&&a!==h&&l.setData("text/html",h),l.setData("text/plain",a),o.preventDefault(),!0},Qe=function(o){let t=this.getSelection(),e=this._root;if(t.collapsed){o.preventDefault();return}this.saveUndoState(t),je(o,t,e,!0,this._config.willCutCopy,this._config.toPlainText,!1)||setTimeout(()=>{try{this._ensureBottomLine()}catch(i){this._config.didError(i)}},0),this.setSelection(t)},Xe=function(o){je(o,this.getSelection(),this._root,!1,this._config.willCutCopy,this._config.toPlainText,!1)},Ae=function(o){this._isShiftDown=o.shiftKey},$e=function(o){let t=o.clipboardData,e=t==null?void 0:t.items,n=this._isShiftDown,i=!1,s=!1,r=null,l=null;if(e){let b=e.length;for(;b--;){let O=e[b],A=O.type;A==="text/html"?l=O:A==="text/plain"||A==="text/uri-list"?r=O:A==="text/rtf"?i=!0:/^image\/.*/.test(A)&&(s=!0)}if(s&&!(i&&l)){o.preventDefault(),this.fireEvent("pasteImage",{clipboardData:t});return}if(!se){o.preventDefault(),l&&(!n||!r)?l.getAsString(O=>{this.insertHTML(O,!0)}):r&&r.getAsString(O=>{let A=!1,Ie=this.getSelection();if(!Ie.collapsed&&I.test(Ie.toString())){let Me=this.linkRegExp.exec(O);A=!!Me&&Me[0].length===O.length}A?this.makeLink(O):this.insertPlainText(O,!0)});return}}let a=t==null?void 0:t.types;if(!se&&a&&(De.call(a,"text/html")>-1||!Fe&&De.call(a,"text/plain")>-1&&De.call(a,"text/rtf")<0)){o.preventDefault();let b;!n&&(b=t.getData("text/html"))?this.insertHTML(b,!0):((b=t.getData("text/plain"))||(b=t.getData("text/uri-list")))&&this.insertPlainText(b,!0);return}let d=document.body,c=this.getSelection(),f=c.startContainer,u=c.startOffset,m=c.endContainer,h=c.endOffset,g=p("DIV",{contenteditable:"true",style:"position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;"});d.appendChild(g),c.selectNodeContents(g),this.setSelection(c),setTimeout(()=>{try{let b="",O=g,A;for(;g=O;)O=g.nextSibling,E(g),A=g.firstChild,A&&A===g.lastChild&&A instanceof HTMLDivElement&&(g=A),b+=g.innerHTML;this.setSelection($(f,u,m,h)),b&&this.insertHTML(b,!0)}catch(b){this._config.didError(b)}},0)},Ve=function(o){if(!o.dataTransfer)return;let t=o.dataTransfer.types,e=t.length,n=!1,i=!1;for(;e--;)switch(t[e]){case"text/plain":n=!0;break;case"text/html":i=!0;break;default:return}(i||n&&this.saveUndoState)&&this.saveUndoState()};var we=(o,t,e)=>{t.preventDefault(),o.splitBlock(t.shiftKey,e)};var ne=(o,t)=>{try{t||(t=o.getSelection());let e=t.startContainer;e instanceof Text&&(e=e.parentNode);let n=e;for(;N(n)&&(!n.textContent||n.textContent===B);)e=n,n=e.parentNode;e!==n&&(t.setStart(n,Array.from(n.childNodes).indexOf(e)),t.collapse(!0),n.removeChild(e),P(n)||(n=z(n,o._root)||o._root),x(n),_(t)),e===o._root&&(e=e.firstChild)&&e.nodeName==="BR"&&E(e),o._ensureBottomLine(),o.setSelection(t),o._updatePath(t,!0)}catch(e){o._config.didError(e)}},Ee=(o,t)=>{let e;for(;(e=o.parentNode)&&!(e===t||e.isContentEditable);)o=e;E(o)},Te=(o,t,e)=>{if(S(t,o._root,"A"))return;let n=t.data||"",i=Math.max(n.lastIndexOf(" ",e-1),n.lastIndexOf("\xA0",e-1))+1,s=n.slice(i,e),r=o.linkRegExp.exec(s);if(r){let l=o.getSelection();o._docWasChanged(),o._recordUndoState(l),o._getRangeAndRemoveBookmark(l);let a=i+r.index,d=a+r[0].length,c=l.startContainer===t,f=l.startOffset-d;a&&(t=t.splitText(a));let u=o._config.tagAttributes.a,m=p("A",Object.assign({href:r[1]?/^(?:ht|f)tps?:/i.test(r[1])?r[1]:"http://"+r[1]:"mailto:"+r[0]},u));m.textContent=n.slice(a,d),t.parentNode.insertBefore(m,t),t.data=n.slice(d),c&&(l.setStart(t,f),l.setEnd(t,f)),o.setSelection(l)}};var Ye=(o,t,e)=>{let n=o._root;if(o._removeZWS(),o.saveUndoState(e),!e.collapsed)t.preventDefault(),H(e,n),ne(o,e);else if(X(e,n)){t.preventDefault();let i=L(e,n);if(!i)return;let s=i;D(s.parentNode,n);let r=z(s,n);if(r){if(!r.isContentEditable){Ee(r,n);return}for(Y(r,s,e,n),s=r.parentNode;s!==n&&!s.nextSibling;)s=s.parentNode;s!==n&&(s=s.nextSibling)&&F(s,n),o.setSelection(e)}else if(s){if(S(s,n,"UL")||S(s,n,"OL")){o.decreaseListLevel(e);return}else if(S(s,n,"BLOCKQUOTE")){o.removeQuote(e);return}o.setSelection(e),o._updatePath(e,!0)}}else{_(e);let i=e.startContainer,s=e.startOffset,r=i.parentNode;i instanceof Text&&r instanceof HTMLAnchorElement&&s&&r.href.includes(i.data)?(i.deleteData(s-1,1),o.setSelection(e),o.removeLink(),t.preventDefault()):(o.setSelection(e),setTimeout(()=>{ne(o)},0))}};var Je=(o,t,e)=>{let n=o._root,i,s,r,l,a,d;if(o._removeZWS(),o.saveUndoState(e),!e.collapsed)t.preventDefault(),H(e,n),ne(o,e);else if(Z(e,n)){if(t.preventDefault(),i=L(e,n),!i)return;if(D(i.parentNode,n),s=W(i,n),s){if(!s.isContentEditable){Ee(s,n);return}for(Y(i,s,e,n),s=i.parentNode;s!==n&&!s.nextSibling;)s=s.parentNode;s!==n&&(s=s.nextSibling)&&F(s,n),o.setSelection(e),o._updatePath(e,!0)}}else{if(r=e.cloneRange(),q(e,n,n,n),l=e.endContainer,a=e.endOffset,l instanceof Element&&(d=l.childNodes[a],d&&d.nodeName==="IMG")){t.preventDefault(),E(d),_(e),ne(o,e);return}o.setSelection(r),setTimeout(()=>{ne(o)},0)}};var et=(o,t,e)=>{let n=o._root;if(o._removeZWS(),e.collapsed&&X(e,n)){let i=L(e,n),s;for(;s=i.parentNode;){if(s.nodeName==="UL"||s.nodeName==="OL"){t.preventDefault(),o.increaseListLevel(e);break}i=s}}},tt=(o,t,e)=>{let n=o._root;if(o._removeZWS(),e.collapsed&&X(e,n)){let i=e.startContainer;(S(i,n,"UL")||S(i,n,"OL"))&&(t.preventDefault(),o.decreaseListLevel(e))}};var nt=(o,t,e)=>{var s;let n,i=o._root;if(o._recordUndoState(e),o._getRangeAndRemoveBookmark(e),!e.collapsed)H(e,i),o._ensureBottomLine(),o.setSelection(e),o._updatePath(e,!0);else if(Z(e,i)){let r=L(e,i);if(r&&r.nodeName!=="PRE"){let l=(s=r.textContent)==null?void 0:s.trimEnd().replace(B,"");if(l==="*"||l==="1."){t.preventDefault(),o.insertPlainText(" ",!1),o._docWasChanged(),o.saveUndoState(e);let a=new T(r,4),d;for(;d=a.nextNode();)E(d);l==="*"?o.makeUnorderedList():o.makeOrderedList();return}}}if(n=e.endContainer,e.endOffset===y(n))do if(n.nodeName==="A"){e.setStartAfter(n);break}while(!n.nextSibling&&(n=n.parentNode)&&n!==i);if(o._config.addLinks){let r=e.cloneRange();_(r);let l=r.startContainer,a=r.startOffset;setTimeout(()=>{Te(o,l,a)},0)}o.setSelection(e)};var ot=function(o){if(o.defaultPrevented||o.isComposing)return;let t=o.key,e="",n=o.code;/^Digit\d$/.test(n)&&(t=n.slice(-1)),t!=="Backspace"&&t!=="Delete"&&(o.altKey&&(e+="Alt-"),o.ctrlKey&&(e+="Ctrl-"),o.metaKey&&(e+="Meta-"),o.shiftKey&&(e+="Shift-")),fe&&o.shiftKey&&t==="Delete"&&(e+="Shift-"),t=e+t;let i=this.getSelection();this._keyHandlers[t]?this._keyHandlers[t](this,o,i):!i.collapsed&&!o.ctrlKey&&!o.metaKey&&t.length===1&&(this.saveUndoState(i),H(i,this._root),this._ensureBottomLine(),this.setSelection(i),this._updatePath(i,!0))},v={Backspace:Ye,Delete:Je,Tab:et,"Shift-Tab":tt," ":nt,ArrowLeft(o){o._removeZWS()},ArrowRight(o,t,e){o._removeZWS();let n=o.getRoot();if(Z(e,n)){_(e);let i=e.endContainer;do if(i.nodeName==="CODE"){let s=i.nextSibling;if(!(s instanceof Text)){let r=document.createTextNode("\xA0");i.parentNode.insertBefore(r,s),s=r}e.setStart(s,1),o.setSelection(e),t.preventDefault();break}while(!i.nextSibling&&(i=i.parentNode)&&i!==n)}}};He||(v.Enter=we,v["Shift-Enter"]=we);!de&&!_e&&(v.PageUp=o=>{o.moveCursorToStart()},v.PageDown=o=>{o.moveCursorToEnd()});var ie=(o,t)=>(t=t||null,(e,n)=>{n.preventDefault();let i=e.getSelection();e.hasFormat(o,null,i)?e.changeFormat(null,{tag:o},i):e.changeFormat({tag:o},t,i)});v[k+"b"]=ie("B");v[k+"i"]=ie("I");v[k+"u"]=ie("U");v[k+"Shift-7"]=ie("S");v[k+"Shift-5"]=ie("SUB",{tag:"SUP"});v[k+"Shift-6"]=ie("SUP",{tag:"SUB"});v[k+"Shift-8"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)UL/.test(e)?o.removeList():o.makeUnorderedList()};v[k+"Shift-9"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)OL/.test(e)?o.removeList():o.makeOrderedList()};v[k+"["]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)BLOCKQUOTE/.test(e)||!/(?:^|>)[OU]L/.test(e)?o.decreaseQuoteLevel():o.decreaseListLevel()};v[k+"]"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)BLOCKQUOTE/.test(e)||!/(?:^|>)[OU]L/.test(e)?o.increaseQuoteLevel():o.increaseListLevel()};v[k+"d"]=(o,t)=>{t.preventDefault(),o.toggleCode()};v[k+"z"]=(o,t)=>{t.preventDefault(),o.undo()};v[k+"y"]=v[k+"Shift-z"]=v[k+"Shift-Z"]=(o,t)=>{t.preventDefault(),o.redo()};var Ce=class{constructor(t,e){this.customEvents=new Set(["pathChange","select","input","pasteImage","undoStateChange"]);this.startSelectionId="squire-selection-start";this.endSelectionId="squire-selection-end";this.linkRegExp=/\b(?:((?:(?:ht|f)tps?:\/\/|www\d{0,3}[.]|[a-z0-9][a-z0-9.\-]*[.][a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:[^\s?&`!()\[\]{};:'".,<>«»“”‘’]|\([^\s()<>]+\)))|([\w\-.%+]+@(?:[\w\-]+\.)+[a-z]{2,}\b(?:[?][^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+(?:&[^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+)*)?))/i;this.tagAfterSplit={DT:"DD",DD:"DT",LI:"LI",PRE:"PRE"};this._root=t,this._config=this._makeConfig(e),this._isFocused=!1,this._lastSelection=$(t,0),this._willRestoreSelection=!1,this._mayHaveZWS=!1,this._lastAnchorNode=null,this._lastFocusNode=null,this._path="",this._events=new Map,this._undoIndex=-1,this._undoStack=[],this._undoStackLength=0,this._isInUndoState=!1,this._ignoreChange=!1,this._ignoreAllChanges=!1,this.addEventListener("selectionchange",this._updatePathOnEvent),this.addEventListener("blur",this._enableRestoreSelection),this.addEventListener("mousedown",this._disableRestoreSelection),this.addEventListener("touchstart",this._disableRestoreSelection),this.addEventListener("focus",this._restoreSelection),this._isShiftDown=!1,this.addEventListener("cut",Qe),this.addEventListener("copy",Xe),this.addEventListener("paste",$e),this.addEventListener("drop",Ve),this.addEventListener("keydown",Ae),this.addEventListener("keyup",Ae),this.addEventListener("keydown",ot),this._keyHandlers=Object.create(v);let n=new MutationObserver(()=>this._docWasChanged());n.observe(t,{childList:!0,attributes:!0,characterData:!0,subtree:!0}),this._mutation=n,t.setAttribute("contenteditable","true"),this.addEventListener("beforeinput",this._beforeInput),this.setHTML("")}destroy(){this._events.forEach((t,e)=>{this.removeEventListener(e)}),this._mutation.disconnect(),this._undoIndex=-1,this._undoStack=[],this._undoStackLength=0}_makeConfig(t){let e={blockTag:"DIV",blockAttributes:null,tagAttributes:{},classNames:{color:"color",fontFamily:"font",fontSize:"size",highlight:"highlight"},undo:{documentSizeThreshold:-1,undoLimit:-1},addLinks:!0,willCutCopy:null,toPlainText:null,sanitizeToDOMFragment:n=>{let i=DOMPurify.sanitize(n,{ALLOW_UNKNOWN_PROTOCOLS:!0,WHOLE_DOCUMENT:!1,RETURN_DOM:!0,RETURN_DOM_FRAGMENT:!0,FORCE_BODY:!1});return i?document.importNode(i,!0):document.createDocumentFragment()},didError:n=>console.log(n)};return t&&(Object.assign(e,t),e.blockTag=e.blockTag.toUpperCase()),e}setKeyHandler(t,e){return this._keyHandlers[t]=e,this}_beforeInput(t){switch(t.inputType){case"insertLineBreak":t.preventDefault(),this.splitBlock(!0);break;case"insertParagraph":t.preventDefault(),this.splitBlock(!1);break;case"insertOrderedList":t.preventDefault(),this.makeOrderedList();break;case"insertUnoderedList":t.preventDefault(),this.makeUnorderedList();break;case"historyUndo":t.preventDefault(),this.undo();break;case"historyRedo":t.preventDefault(),this.redo();break;case"formatBold":t.preventDefault(),this.bold();break;case"formaItalic":t.preventDefault(),this.italic();break;case"formatUnderline":t.preventDefault(),this.underline();break;case"formatStrikeThrough":t.preventDefault(),this.strikethrough();break;case"formatSuperscript":t.preventDefault(),this.superscript();break;case"formatSubscript":t.preventDefault(),this.subscript();break;case"formatJustifyFull":case"formatJustifyCenter":case"formatJustifyRight":case"formatJustifyLeft":{t.preventDefault();let e=t.inputType.slice(13).toLowerCase();e==="full"&&(e="justify"),this.setTextAlignment(e);break}case"formatRemove":t.preventDefault(),this.removeAllFormatting();break;case"formatSetBlockTextDirection":{t.preventDefault();let e=t.data;e==="null"&&(e=null),this.setTextDirection(e);break}case"formatBackColor":t.preventDefault(),this.setHighlightColor(t.data);break;case"formatFontColor":t.preventDefault(),this.setTextColor(t.data);break;case"formatFontName":t.preventDefault(),this.setFontFace(t.data);break}}handleEvent(t){this.fireEvent(t.type,t)}fireEvent(t,e){let n=this._events.get(t);if(/^(?:focus|blur)/.test(t)){let i=this._root===document.activeElement;if(t==="focus"){if(!i||this._isFocused)return this;this._isFocused=!0}else{if(i||!this._isFocused)return this;this._isFocused=!1}}if(n){let i=e instanceof Event?e:new CustomEvent(t,{detail:e});n=n.slice();for(let s of n)try{"handleEvent"in s?s.handleEvent(i):s.call(this,i)}catch(r){this._config.didError(r)}}return this}addEventListener(t,e){let n=this._events.get(t),i=this._root;return n||(n=[],this._events.set(t,n),this.customEvents.has(t)||(t==="selectionchange"&&(i=document),i.addEventListener(t,this,!0))),n.push(e),this}removeEventListener(t,e){let n=this._events.get(t),i=this._root;if(n){if(e){let s=n.length;for(;s--;)n[s]===e&&n.splice(s,1)}else n.length=0;n.length||(this._events.delete(t),this.customEvents.has(t)||(t==="selectionchange"&&(i=document),i.removeEventListener(t,this,!0)))}return this}focus(){return this._root.focus({preventScroll:!0}),this}blur(){return this._root.blur(),this}_enableRestoreSelection(){this._willRestoreSelection=!0}_disableRestoreSelection(){this._willRestoreSelection=!1}_restoreSelection(){this._willRestoreSelection&&this.setSelection(this._lastSelection)}_removeZWS(){this._mayHaveZWS&&(re(this._root),this._mayHaveZWS=!1)}_saveRangeToBookmark(t){let e=p("INPUT",{id:this.startSelectionId,type:"hidden"}),n=p("INPUT",{id:this.endSelectionId,type:"hidden"}),i;j(t,e),t.collapse(!1),j(t,n),e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING&&(e.id=this.endSelectionId,n.id=this.startSelectionId,i=e,e=n,n=i),t.setStartAfter(e),t.setEndBefore(n)}_getRangeAndRemoveBookmark(t){let e=this._root,n=e.querySelector("#"+this.startSelectionId),i=e.querySelector("#"+this.endSelectionId);if(n&&i){let s=n.parentNode,r=i.parentNode,l=Array.from(s.childNodes).indexOf(n),a=Array.from(r.childNodes).indexOf(i);s===r&&(a-=1),n.remove(),i.remove(),t||(t=document.createRange()),t.setStart(s,l),t.setEnd(r,a),te(s,t),s!==r&&te(r,t),t.collapsed&&(s=t.startContainer,s instanceof Text&&(r=s.childNodes[t.startOffset],(!r||!(r instanceof Text))&&(r=s.childNodes[t.startOffset-1]),r&&r instanceof Text&&(t.setStart(r,0),t.collapse(!0))))}return t||null}getSelection(){let t=window.getSelection(),e=this._root,n=null;if(this._isFocused&&t&&t.rangeCount){n=t.getRangeAt(0).cloneRange();let i=n.startContainer,s=n.endContainer;i&&M(i)&&n.setStartBefore(i),s&&M(s)&&n.setEndBefore(s)}return n&&e.contains(n.commonAncestorContainer)?this._lastSelection=n:(n=this._lastSelection,document.contains(n.commonAncestorContainer)||(n=null)),n||(n=$(e.firstElementChild||e,0)),n}setSelection(t){if(this._lastSelection=t,!this._isFocused)this._enableRestoreSelection();else{let e=window.getSelection();e&&("setBaseAndExtent"in Selection.prototype?e.setBaseAndExtent(t.startContainer,t.startOffset,t.endContainer,t.endOffset):(e.removeAllRanges(),e.addRange(t)))}return this}_moveCursorTo(t){let e=this._root,n=$(e,t?0:e.childNodes.length);return _(n),this.setSelection(n),this}moveCursorToStart(){return this._moveCursorTo(!0)}moveCursorToEnd(){return this._moveCursorTo(!1)}getCursorPosition(){let t=this.getSelection(),e=t.getBoundingClientRect();if(e&&!e.top){this._ignoreChange=!0;let n=p("SPAN");n.textContent=B,j(t,n),e=n.getBoundingClientRect();let i=n.parentNode;i.removeChild(n),te(i,t)}return e}getPath(){return this._path}_updatePathOnEvent(){this._isFocused&&this._updatePath(this.getSelection())}_updatePath(t,e){let n=t.startContainer,i=t.endContainer,s;(e||n!==this._lastAnchorNode||i!==this._lastFocusNode)&&(this._lastAnchorNode=n,this._lastFocusNode=i,s=n&&i?n===i?this._getPath(i):"(selection)":"",this._path!==s&&(this._path=s,this.fireEvent("pathChange",{path:s}))),this.fireEvent(t.collapsed?"cursor":"select",{range:t})}_getPath(t){let e=this._root,n=this._config,i="";if(t&&t!==e){let s=t.parentNode;if(i=s?this._getPath(s):"",t instanceof HTMLElement){let r=t.id,l=t.classList,a=Array.from(l).sort(),d=t.dir,c=n.classNames;i+=(i?">":"")+t.nodeName,r&&(i+="#"+r),a.length&&(i+=".",i+=a.join(".")),d&&(i+="[dir="+d+"]"),l.contains(c.highlight)&&(i+="[backgroundColor="+t.style.backgroundColor.replace(/ /g,"")+"]"),l.contains(c.color)&&(i+="[color="+t.style.color.replace(/ /g,"")+"]"),l.contains(c.fontFamily)&&(i+="[fontFamily="+t.style.fontFamily.replace(/ /g,"")+"]"),l.contains(c.fontSize)&&(i+="[fontSize="+t.style.fontSize+"]")}}return i}modifyDocument(t){let e=this._mutation;return e&&(e.takeRecords().length&&this._docWasChanged(),e.disconnect()),this._ignoreAllChanges=!0,t(),this._ignoreAllChanges=!1,e&&(e.observe(this._root,{childList:!0,attributes:!0,characterData:!0,subtree:!0}),this._ignoreChange=!1),this}_docWasChanged(){if(qe(),this._mayHaveZWS=!0,!this._ignoreAllChanges){if(this._ignoreChange){this._ignoreChange=!1;return}this._isInUndoState&&(this._isInUndoState=!1,this.fireEvent("undoStateChange",{canUndo:!0,canRedo:!1})),this.fireEvent("input")}}_recordUndoState(t,e){let n=this._isInUndoState;if(!n||e){let i=this._undoIndex+1,s=this._undoStack,r=this._config.undo,l=r.documentSizeThreshold,a=r.undoLimit;if(i<this._undoStackLength&&(s.length=this._undoStackLength=i),t&&this._saveRangeToBookmark(t),n)return this;let d=this._getRawHTML();e&&(i-=1),l>-1&&d.length*2>l&&a>-1&&i>a&&(s.splice(0,i-a),i=a,this._undoStackLength=a),s[i]=d,this._undoIndex=i,this._undoStackLength+=1,this._isInUndoState=!0}return this}saveUndoState(t){return t||(t=this.getSelection()),this._recordUndoState(t,this._isInUndoState),this._getRangeAndRemoveBookmark(t),this}undo(){if(this._undoIndex!==0||!this._isInUndoState){this._recordUndoState(this.getSelection(),!1),this._undoIndex-=1,this._setRawHTML(this._undoStack[this._undoIndex]);let t=this._getRangeAndRemoveBookmark();t&&this.setSelection(t),this._isInUndoState=!0,this.fireEvent("undoStateChange",{canUndo:this._undoIndex!==0,canRedo:!0}),this.fireEvent("input")}return this.focus()}redo(){let t=this._undoIndex,e=this._undoStackLength;if(t+1<e&&this._isInUndoState){this._undoIndex+=1,this._setRawHTML(this._undoStack[this._undoIndex]);let n=this._getRangeAndRemoveBookmark();n&&this.setSelection(n),this.fireEvent("undoStateChange",{canUndo:!0,canRedo:t+2<e}),this.fireEvent("input")}return this.focus()}getRoot(){return this._root}_getRawHTML(){return this._root.innerHTML}_setRawHTML(t){let e=this._root;e.innerHTML=t;let n=e,i=n.firstChild;if(!i||i.nodeName==="BR"){let s=this.createDefaultBlock();i?n.replaceChild(s,i):n.appendChild(s)}else for(;n=W(n,e);)x(n);return this._ignoreChange=!0,this}getHTML(t){let e;t&&(e=this.getSelection(),this._saveRangeToBookmark(e));let n=this._getRawHTML().replace(/\u200B/g,"");return t&&this._getRangeAndRemoveBookmark(e),n}setHTML(t){let e=this._config.sanitizeToDOMFragment(t,this),n=this._root;Ne(e,this._config),le(e,n,!1),D(e,n);let i=e,s=i.firstChild;if(!s||s.nodeName==="BR"){let l=this.createDefaultBlock();s?i.replaceChild(l,s):i.appendChild(l)}else for(;i=W(i,n);)x(i);for(this._ignoreChange=!0;s=n.lastChild;)n.removeChild(s);n.appendChild(e),this._undoIndex=-1,this._undoStack.length=0,this._undoStackLength=0,this._isInUndoState=!1;let r=this._getRangeAndRemoveBookmark()||$(n.firstElementChild||n,0);return this.saveUndoState(r),this.setSelection(r),this._updatePath(r,!0),this}insertHTML(t,e){let n=this._config,i=n.sanitizeToDOMFragment(t,this),s=this.getSelection();this.saveUndoState(s);try{let r=this._root;n.addLinks&&this.addDetectedLinks(i,i),Ne(i,this._config),le(i,r,!1),Se(i),i.normalize();let l=i;for(;l=W(l,i);)x(l);let a=!0;if(e){let d=new CustomEvent("willPaste",{cancelable:!0,detail:{fragment:i}});this.fireEvent("willPaste",d),i=d.detail.fragment,a=!d.defaultPrevented}a&&(Ze(s,i,r),s.collapse(!1),Oe(s,"A",r),this._ensureBottomLine()),this.setSelection(s),this._updatePath(s,!0),e&&this.focus()}catch(r){this._config.didError(r)}return this}insertElement(t,e){if(e||(e=this.getSelection()),e.collapse(!0),N(t))j(e,t),e.setStartAfter(t);else{let n=this._root,i=L(e,n),s=i||n,r=null;for(;s!==n&&!s.nextSibling;)s=s.parentNode;if(s!==n){let a=s.parentNode;r=w(a,s.nextSibling,n,n)}i&&ce(i)&&E(i),n.insertBefore(t,r);let l=this.createDefaultBlock();n.insertBefore(l,r),e.setStart(l,0),e.setEnd(l,0),_(e)}return this.focus(),this.setSelection(e),this._updatePath(e),this}insertImage(t,e){let n=p("IMG",Object.assign({src:t},e));return this.insertElement(n),n}insertPlainText(t,e){let n=this.getSelection();if(n.collapsed&&S(n.startContainer,this._root,"PRE")){let c=n.startContainer,f=n.startOffset,u;if(!c||!(c instanceof Text)){let h=document.createTextNode("");c.insertBefore(h,c.childNodes[f]),u=h,f=0}else u=c;let m=!0;if(e){let h=new CustomEvent("willPaste",{cancelable:!0,detail:{text:t}});this.fireEvent("willPaste",h),t=h.detail.text,m=!h.defaultPrevented}return m&&(u.insertData(f,t),n.setStart(u,f+t.length),n.collapse(!0)),this.setSelection(n),this}let i=t.split(`
-`),s=this._config,r=s.blockTag,l=s.blockAttributes,a="</"+r+">",d="<"+r;for(let c in l)d+=" "+c+'="'+ke(l[c])+'"';d+=">";for(let c=0,f=i.length;c<f;c+=1){let u=i[c];u=ke(u).replace(/ (?=(?: |$))/g,"&nbsp;"),c&&(u=d+(u||"<BR>")+a),i[c]=u}return this.insertHTML(i.join(""),e)}getSelectedText(t){return ge(t||this.getSelection())}getFontInfo(t){let e={color:void 0,backgroundColor:void 0,fontFamily:void 0,fontSize:void 0};t||(t=this.getSelection());let n=0,i=t.commonAncestorContainer;if(t.collapsed||i instanceof Text)for(i instanceof Text&&(i=i.parentNode);n<4&&i;){let s=i.style;if(s){let r=s.color;!e.color&&r&&(e.color=r,n+=1);let l=s.backgroundColor;!e.backgroundColor&&l&&(e.backgroundColor=l,n+=1);let a=s.fontFamily;!e.fontFamily&&a&&(e.fontFamily=a,n+=1);let d=s.fontSize;!e.fontSize&&d&&(e.fontSize=d,n+=1)}i=i.parentNode}return e}hasFormat(t,e,n){t=t.toUpperCase(),e||(e={}),n||(n=this.getSelection()),!n.collapsed&&n.startContainer instanceof Text&&n.startOffset===n.startContainer.length&&n.startContainer.nextSibling&&n.setStartBefore(n.startContainer.nextSibling),!n.collapsed&&n.endContainer instanceof Text&&n.endOffset===0&&n.endContainer.previousSibling&&n.setEndAfter(n.endContainer.previousSibling);let i=this._root,s=n.commonAncestorContainer;if(S(s,i,t,e))return!0;if(s instanceof Text)return!1;let r=new T(s,4,d=>K(n,d,!0)),l=!1,a;for(;a=r.nextNode();){if(!S(a,i,t,e))return!1;l=!0}return l}changeFormat(t,e,n,i){return n||(n=this.getSelection()),this.saveUndoState(n),e&&(n=this._removeFormat(e.tag.toUpperCase(),e.attributes||{},n,i)),t&&(n=this._addFormat(t.tag.toUpperCase(),t.attributes||{},n)),this.setSelection(n),this._updatePath(n,!0),this.focus()}_addFormat(t,e,n){let i=this._root;if(n.collapsed){let s=x(p(t,e));j(n,s);let r=s.firstChild||s,l=r instanceof Text?r.length:0;n.setStart(r,l),n.collapse(!0);let a=s;for(;N(a);)a=a.parentNode;re(a,s)}else{let s=new T(n.commonAncestorContainer,5,c=>(c instanceof Text||c.nodeName==="BR"||c.nodeName==="IMG")&&K(n,c,!0)),{startContainer:r,startOffset:l,endContainer:a,endOffset:d}=n;if(s.currentNode=r,!(r instanceof Element)&&!(r instanceof Text)||!s.filter(r)){let c=s.nextNode();if(!c)return n;r=c,l=0}do{let c=s.currentNode;if(!S(c,i,t,e)){c===a&&c.length>d&&c.splitText(d),c===r&&l&&(c=c.splitText(l),a===r?(a=c,d-=l):a===r.parentNode&&(d+=1),r=c,l=0);let u=p(t,e);R(c,u),u.appendChild(c)}}while(s.nextNode());n=$(r,l,a,d)}return n}_removeFormat(t,e,n,i){this._saveRangeToBookmark(n);let s;n.collapsed&&(oe?s=document.createTextNode(B):s=document.createTextNode(""),j(n,s));let r=n.commonAncestorContainer;for(;N(r);)r=r.parentNode;let l=n.startContainer,a=n.startOffset,d=n.endContainer,c=n.endOffset,f=[],u=(h,g)=>{if(K(n,h,!1))return;let b,O;if(!K(n,h,!0)){!(h instanceof HTMLInputElement)&&(!(h instanceof Text)||h.data)&&f.push([g,h]);return}if(h instanceof Text)h===d&&c!==h.length&&f.push([g,h.splitText(c)]),h===l&&a&&(h.splitText(a),f.push([g,h]));else for(b=h.firstChild;b;b=O)O=b.nextSibling,u(b,g)},m=Array.from(r.getElementsByTagName(t)).filter(h=>K(n,h,!0)&&he(h,t,e));if(i||m.forEach(h=>{u(h,h)}),f.forEach(([h,g])=>{h=h.cloneNode(!1),R(g,h),h.appendChild(g)}),m.forEach(h=>{R(h,C(h))}),oe&&s){s=s.parentNode;let h=s;for(;h&&N(h);)h=h.parentNode;h&&re(h,s)}return this._getRangeAndRemoveBookmark(n),s&&n.collapse(!1),te(r,n),n}bold(){return this.changeFormat({tag:"B"})}removeBold(){return this.changeFormat(null,{tag:"B"})}italic(){return this.changeFormat({tag:"I"})}removeItalic(){return this.changeFormat(null,{tag:"I"})}underline(){return this.changeFormat({tag:"U"})}removeUnderline(){return this.changeFormat(null,{tag:"U"})}strikethrough(){return this.changeFormat({tag:"S"})}removeStrikethrough(){return this.changeFormat(null,{tag:"S"})}subscript(){return this.changeFormat({tag:"SUB"},{tag:"SUP"})}removeSubscript(){return this.changeFormat(null,{tag:"SUB"})}superscript(){return this.changeFormat({tag:"SUP"},{tag:"SUB"})}removeSuperscript(){return this.changeFormat(null,{tag:"SUP"})}makeLink(t,e){let n=this.getSelection();if(n.collapsed){let i=t.indexOf(":")+1;if(i)for(;t[i]==="/";)i+=1;j(n,document.createTextNode(t.slice(i)))}return e=Object.assign({href:t},this._config.tagAttributes.a,e),this.changeFormat({tag:"A",attributes:e},{tag:"A"},n)}removeLink(){return this.changeFormat(null,{tag:"A"},this.getSelection(),!0)}addDetectedLinks(t,e){let n=new T(t,4,l=>!S(l,e||this._root,"A")),i=this.linkRegExp,s=this._config.tagAttributes.a,r;for(;r=n.nextNode();){let l=r.parentNode,a=r.data,d;for(;d=i.exec(a);){let c=d.index,f=c+d[0].length;c&&l.insertBefore(document.createTextNode(a.slice(0,c)),r);let u=p("A",Object.assign({href:d[1]?/^(?:ht|f)tps?:/i.test(d[1])?d[1]:"http://"+d[1]:"mailto:"+d[0]},s));u.textContent=a.slice(c,f),l.insertBefore(u,r),r.data=a=a.slice(f)}}return this}setFontFace(t){let e=this._config.classNames.fontFamily;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"font-family: "+t+", sans-serif;"}}:null,{tag:"SPAN",attributes:{class:e}})}setFontSize(t){let e=this._config.classNames.fontSize;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"font-size: "+(typeof t=="number"?t+"px":t)}}:null,{tag:"SPAN",attributes:{class:e}})}setTextColor(t){let e=this._config.classNames.color;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"color:"+t}}:null,{tag:"SPAN",attributes:{class:e}})}setHighlightColor(t){let e=this._config.classNames.highlight;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"background-color:"+t}}:null,{tag:"SPAN",attributes:{class:e}})}_ensureBottomLine(){let t=this._root,e=t.lastElementChild;(!e||e.nodeName!==this._config.blockTag||!P(e))&&t.appendChild(this.createDefaultBlock())}createDefaultBlock(t){let e=this._config;return x(p(e.blockTag,e.blockAttributes,t))}splitBlock(t,e){e||(e=this.getSelection());let n=this._root,i,s,r,l;if(this._recordUndoState(e),this._removeZWS(),this._getRangeAndRemoveBookmark(e),e.collapsed||H(e,n),this._config.addLinks){_(e);let u=e.startContainer,m=e.startOffset;setTimeout(()=>{Te(this,u,m)},0)}if(i=L(e,n),i&&(s=S(i,n,"PRE"))){_(e),r=e.startContainer;let u=e.startOffset;return r instanceof Text||(r=document.createTextNode(""),s.insertBefore(r,s.firstChild)),!t&&r instanceof Text&&(r.data.charAt(u-1)===`
+"use strict";(()=>{var it=()=>!0,C=class{constructor(t,e,n){this.root=t,this.currentNode=t,this.nodeType=e,this.filter=n||it}isAcceptableNode(t){let e=t.nodeType;return!!((e===Node.ELEMENT_NODE?1:e===Node.TEXT_NODE?4:0)&this.nodeType)&&this.filter(t)}nextNode(){let t=this.root,e=this.currentNode,n;for(;;){for(n=e.firstChild;!n&&e&&e!==t;)n=e.nextSibling,n||(e=e.parentNode);if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}previousNode(){let t=this.root,e=this.currentNode,n;for(;;){if(e===t)return null;if(n=e.previousSibling,n)for(;e=n.lastChild;)n=e;else n=e.parentNode;if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}previousPONode(){let t=this.root,e=this.currentNode,n;for(;;){for(n=e.lastChild;!n&&e&&e!==t;)n=e.previousSibling,n||(e=e.parentNode);if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}};var B="\u200B",J=navigator.userAgent,de=/Mac OS X/.test(J),fe=/Windows NT/.test(J),_e=/iP(?:ad|hone|od)/.test(J)||de&&!!navigator.maxTouchPoints,xt=/Android/.test(J),Fe=/Gecko\//.test(J),se=/Edge\//.test(J),st=!se&&/WebKit\//.test(J),k=de||_e?"Meta-":"Ctrl-",oe=st,He="onbeforeinput"in document&&"inputType"in new InputEvent("input"),I=/[^ \t\r\n]/;var at=/^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/,ct=new Set(["BR","HR","IFRAME","IMG","INPUT"]),dt=0,xe=1,Pe=2,Ue=3,ue=new WeakMap,qe=()=>{ue=new WeakMap},M=o=>ct.has(o.nodeName),be=o=>{switch(o.nodeType){case 3:return xe;case 1:case 11:if(ue.has(o))return ue.get(o);break;default:return dt}let t;return Array.from(o.childNodes).every(N)?at.test(o.nodeName)?t=xe:t=Pe:t=Ue,ue.set(o,t),t},N=o=>be(o)===xe,P=o=>be(o)===Pe,Q=o=>be(o)===Ue;var p=(o,t,e)=>{let n=document.createElement(o);if(t instanceof Array&&(e=t,t=null),t)for(let i in t){let s=t[i];s!==void 0&&n.setAttribute(i,s)}return e&&e.forEach(i=>n.appendChild(i)),n},Le=(o,t)=>M(o)||o.nodeType!==t.nodeType||o.nodeName!==t.nodeName?!1:o instanceof HTMLElement&&t instanceof HTMLElement?o.nodeName!=="A"&&o.className===t.className&&o.style.cssText===t.style.cssText:!0,he=(o,t,e)=>{if(o.nodeName!==t)return!1;for(let n in e)if(!("getAttribute"in o)||o.getAttribute(n)!==e[n])return!1;return!0},S=(o,t,e,n)=>{for(;o&&o!==t;){if(he(o,e,n))return o;o=o.parentNode}return null},me=(o,t)=>{let e=o.childNodes;for(;t&&o instanceof Element;)o=e[t-1],e=o.childNodes,t=e.length;return o},Re=(o,t)=>{let e=o;if(e instanceof Element){let n=e.childNodes;if(t<n.length)e=n[t];else{for(;e&&!e.nextSibling;)e=e.parentNode;e&&(e=e.nextSibling)}}return e},y=o=>o instanceof Element||o instanceof DocumentFragment?o.childNodes.length:o instanceof CharacterData?o.length:0,_=o=>{let t=document.createDocumentFragment(),e=o.firstChild;for(;e;)t.appendChild(e),e=o.firstChild;return t},E=o=>{let t=o.parentNode;return t&&t.removeChild(o),o},R=(o,t)=>{let e=o.parentNode;e&&e.replaceChild(t,o)};var ft=o=>o instanceof Element?o.nodeName==="BR":I.test(o.data),ee=(o,t)=>{let e=o.parentNode;for(;N(e);)e=e.parentNode;let n=new C(e,5,ft);return n.currentNode=o,!!n.nextNode()||t&&!n.previousNode()},re=(o,t)=>{let e=new C(o,4),n,i;for(;n=e.nextNode();)for(;(i=n.data.indexOf(B))>-1&&(!t||n.parentNode!==t);)if(n.length===1){let s=n,r=s.parentNode;for(;r&&(r.removeChild(s),e.currentNode=r,!(!N(r)||y(r)));)s=r,r=s.parentNode;break}else n.deleteData(i,1)};var ut=0,ht=1,mt=2,pt=3,K=(o,t,e)=>{let n=document.createRange();if(n.selectNode(t),e){let i=o.compareBoundaryPoints(pt,n)>-1,s=o.compareBoundaryPoints(ht,n)<1;return!i&&!s}else{let i=o.compareBoundaryPoints(ut,n)<1,s=o.compareBoundaryPoints(mt,n)>-1;return i&&s}},T=o=>{let{startContainer:t,startOffset:e,endContainer:n,endOffset:i}=o;for(;!(t instanceof Text);){let s=t.childNodes[e];if(!s||M(s)){if(e&&(s=t.childNodes[e-1],s instanceof Text)){let r=s,l;for(;!r.length&&(l=r.previousSibling)&&l instanceof Text;)r.remove(),r=l;t=r,e=r.data.length}break}t=s,e=0}if(i)for(;!(n instanceof Text);){let s=n.childNodes[i-1];if(!s||M(s)){if(s&&s.nodeName==="BR"&&!ee(s,!1)){i-=1;continue}break}n=s,i=y(n)}else for(;!(n instanceof Text);){let s=n.firstChild;if(!s||M(s))break;n=s}o.setStart(t,e),o.setEnd(n,i)},q=(o,t,e,n)=>{let i=o.startContainer,s=o.startOffset,r=o.endContainer,l=o.endOffset,a;for(t||(t=o.commonAncestorContainer),e||(e=t);!s&&i!==t&&i!==n;)a=i.parentNode,s=Array.from(a.childNodes).indexOf(i),i=a;for(;!(r===e||r===n||(r.nodeType!==3&&r.childNodes[l]&&r.childNodes[l].nodeName==="BR"&&!ee(r.childNodes[l],!1)&&(l+=1),l!==y(r)));)a=r.parentNode,l=Array.from(a.childNodes).indexOf(r)+1,r=a;o.setStart(i,s),o.setEnd(r,l)},Oe=(o,t,e)=>{let n=S(o.endContainer,e,t);if(n&&(n=n.parentNode)){let i=o.cloneRange();q(i,n,n,e),i.endContainer===n&&(o.setStart(i.endContainer,i.endOffset),o.setEnd(i.endContainer,i.endOffset))}return o};var x=o=>{let t=null;if(o instanceof Text)return o;if(N(o)){let e=o.firstChild;if(oe)for(;e&&e instanceof Text&&!e.data;)o.removeChild(e),e=o.firstChild;e||(oe?t=document.createTextNode(B):t=document.createTextNode(""))}else if((o instanceof Element||o instanceof DocumentFragment)&&!o.querySelector("BR")){t=p("BR");let e=o,n;for(;(n=e.lastElementChild)&&!N(n);)e=n;o=e}if(t)try{o.appendChild(t)}catch(e){}return o},D=(o,t)=>{let e=null;return Array.from(o.childNodes).forEach(n=>{let i=n.nodeName==="BR";!i&&N(n)?(e||(e=p("DIV")),e.appendChild(n)):(i||e)&&(e||(e=p("DIV")),x(e),i?o.replaceChild(e,n):o.insertBefore(e,n),e=null),Q(n)&&D(n,t)}),e&&o.appendChild(x(e)),o},w=(o,t,e,n)=>{if(o instanceof Text&&o!==e){if(typeof t!="number")throw new Error("Offset must be a number to split text node!");if(!o.parentNode)throw new Error("Cannot split text node with no parent!");return w(o.parentNode,o.splitText(t),e,n)}let i=typeof t=="number"?t<o.childNodes.length?o.childNodes[t]:null:t,s=o.parentNode;if(!s||o===e||!(o instanceof Element))return i;let r=o.cloneNode(!1);for(;i;){let l=i.nextSibling;r.appendChild(i),i=l}return o instanceof HTMLOListElement&&S(o,n,"BLOCKQUOTE")&&(r.start=(+o.start||1)+o.childNodes.length-1),x(o),x(r),s.insertBefore(r,o.nextSibling),w(s,r,e,n)},We=(o,t)=>{let e=o.childNodes,n=e.length,i=[];for(;n--;){let s=e[n],r=n?e[n-1]:null;if(r&&N(s)&&Le(s,r))t.startContainer===s&&(t.startContainer=r,t.startOffset+=y(r)),t.endContainer===s&&(t.endContainer=r,t.endOffset+=y(r)),t.startContainer===o&&(t.startOffset>n?t.startOffset-=1:t.startOffset===n&&(t.startContainer=r,t.startOffset=y(r))),t.endContainer===o&&(t.endOffset>n?t.endOffset-=1:t.endOffset===n&&(t.endContainer=r,t.endOffset=y(r))),E(s),s instanceof Text?r.appendData(s.data):i.push(_(s));else if(s instanceof Element){let l;for(;l=i.pop();)s.appendChild(l);We(s,t)}}},te=(o,t)=>{let e=o instanceof Text?o.parentNode:o;if(e instanceof Element){let n={startContainer:t.startContainer,startOffset:t.startOffset,endContainer:t.endContainer,endOffset:t.endOffset};We(e,n),t.setStart(n.startContainer,n.startOffset),t.setEnd(n.endContainer,n.endOffset)}},Y=(o,t,e,n)=>{let i=t,s,r;for(;(s=i.parentNode)&&s!==n&&s instanceof Element&&s.childNodes.length===1;)i=s;E(i),r=o.childNodes.length;let l=o.lastChild;l&&l.nodeName==="BR"&&(o.removeChild(l),r-=1),o.appendChild(_(t)),e.setStart(o,r),e.collapse(!0),te(o,e)},F=(o,t)=>{let e=o.previousSibling,n=o.firstChild,i=o.nodeName==="LI";if(!(i&&(!n||!/^[OU]L$/.test(n.nodeName)))){if(e&&Le(e,o)){if(!Q(e))if(i){let r=p("DIV");r.appendChild(_(e)),e.appendChild(r)}else return;E(o);let s=!Q(o);e.appendChild(_(o)),s&&D(e,t),n&&F(n,t)}else if(i){let s=p("DIV");o.insertBefore(s,n),x(s)}}};var Ke={"font-weight":{regexp:/^bold|^700/i,replace(){return p("B")}},"font-style":{regexp:/^italic/i,replace(){return p("I")}},"font-family":{regexp:I,replace(o,t){return p("SPAN",{class:o.fontFamily,style:"font-family:"+t})}},"font-size":{regexp:I,replace(o,t){return p("SPAN",{class:o.fontSize,style:"font-size:"+t})}},"text-decoration":{regexp:/^underline/i,replace(){return p("U")}}},Nt=(o,t,e)=>{let n=o.style,i,s;for(let r in Ke){let l=Ke[r],a=n.getPropertyValue(r);if(a&&l.regexp.test(a)){let d=l.replace(e.classNames,a);if(d.nodeName===o.nodeName&&d.className===o.className)continue;s||(s=d),i&&i.appendChild(d),i=d,o.style.removeProperty(r)}}return s&&i&&(i.appendChild(_(o)),o.style.cssText?o.appendChild(s):R(o,s)),i||o},pe=o=>(t,e)=>{let n=p(o),i=t.attributes;for(let s=0,r=i.length;s<r;s+=1){let l=i[s];n.setAttribute(l.name,l.value)}return e.replaceChild(n,t),n.appendChild(_(t)),n},St={1:"10",2:"13",3:"16",4:"18",5:"24",6:"32",7:"48"},gt={STRONG:pe("B"),EM:pe("I"),INS:pe("U"),STRIKE:pe("S"),SPAN:Nt,FONT:(o,t,e)=>{let n=o,i=n.face,s=n.size,r=n.color,l=e.classNames,a,d,c,f,u;return i&&(a=p("SPAN",{class:l.fontFamily,style:"font-family:"+i}),u=a,f=a),s&&(d=p("SPAN",{class:l.fontSize,style:"font-size:"+St[s]+"px"}),u||(u=d),f&&f.appendChild(d),f=d),r&&/^#?([\dA-F]{3}){1,2}$/i.test(r)&&(r.charAt(0)!=="#"&&(r="#"+r),c=p("SPAN",{class:l.color,style:"color:"+r}),u||(u=c),f&&f.appendChild(c),f=c),(!u||!f)&&(u=f=p("SPAN")),t.replaceChild(u,n),f.appendChild(_(n)),f},TT:(o,t,e)=>{let n=p("SPAN",{class:e.classNames.fontFamily,style:'font-family:menlo,consolas,"courier new",monospace'});return t.replaceChild(n,o),n.appendChild(_(o)),n}},Et=/^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/,Tt=/^(?:HEAD|META|STYLE)/,Ne=(o,t,e)=>{let n=o.childNodes,i=o;for(;N(i);)i=i.parentNode;let s=new C(i,5);for(let r=0,l=n.length;r<l;r+=1){let a=n[r],d=a.nodeName,c=gt[d];if(a instanceof HTMLElement){let f=a.childNodes.length;if(c)a=c(a,o,t);else if(Tt.test(d)){o.removeChild(a),r-=1,l-=1;continue}else if(!Et.test(d)&&!N(a)){r-=1,l+=f-1,o.replaceChild(_(a),a);continue}f&&Ne(a,t,e||d==="PRE")}else{if(a instanceof Text){let f=a.data,u=!I.test(f.charAt(0)),m=!I.test(f.charAt(f.length-1));if(e||!u&&!m)continue;if(u){s.currentNode=a;let h;for(;(h=s.previousPONode())&&!(h.nodeName==="IMG"||h instanceof Text&&I.test(h.data));)if(!N(h)){h=null;break}f=f.replace(/^[ \t\r\n]+/g,h?" ":"")}if(m){s.currentNode=a;let h;for(;(h=s.nextNode())&&!(h.nodeName==="IMG"||h instanceof Text&&I.test(h.data));)if(!N(h)){h=null;break}f=f.replace(/[ \t\r\n]+$/g,h?" ":"")}if(f){a.data=f;continue}}o.removeChild(a),r-=1,l-=1}}return o},Se=o=>{let t=o.childNodes,e=t.length;for(;e--;){let n=t[e];n instanceof Element&&!M(n)?(Se(n),N(n)&&!n.firstChild&&o.removeChild(n)):n instanceof Text&&!n.data&&o.removeChild(n)}},le=(o,t,e)=>{let n=o.querySelectorAll("BR"),i=[],s=n.length;for(let r=0;r<s;r+=1)i[r]=ee(n[r],e);for(;s--;){let r=n[s],l=r.parentNode;l&&(i[s]?N(l)||D(l,t):E(r))}},ke=o=>o.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;").split('"').join("&quot;");var ae=(o,t)=>{let e=new C(t,1,P);return e.currentNode=o,e},z=(o,t)=>{let e=ae(o,t).previousNode();return e!==t?e:null},W=(o,t)=>{let e=ae(o,t).nextNode();return e!==t?e:null},ce=o=>!o.textContent&&!o.querySelector("IMG");var L=(o,t)=>{let e=o.startContainer,n;if(N(e))n=z(e,t);else if(e!==t&&e instanceof HTMLElement&&P(e))n=e;else{let i=me(e,o.startOffset);n=W(i,t)}return n&&K(o,n,!0)?n:null},G=(o,t)=>{let e=o.endContainer,n;if(N(e))n=z(e,t);else if(e!==t&&e instanceof HTMLElement&&P(e))n=e;else{let i=Re(e,o.endOffset);if(!i||!t.contains(i)){i=t;let s;for(;s=i.lastChild;)i=s}n=z(i,t)}return n&&K(o,n,!0)?n:null},ze=o=>o instanceof Text?I.test(o.data):o.nodeName==="IMG",X=(o,t)=>{let e=o.startContainer,n=o.startOffset,i;if(e instanceof Text){let l=e.data;for(let a=n;a>0;a-=1)if(l.charAt(a-1)!==B)return!1;i=e}else if(i=Re(e,n),i&&!t.contains(i)&&(i=null),!i&&(i=me(e,n),i instanceof Text&&i.length))return!1;let s=L(o,t);if(!s)return!1;let r=new C(s,5,ze);return r.currentNode=i,!r.previousNode()},Z=(o,t)=>{let e=o.endContainer,n=o.endOffset,i;if(e instanceof Text){let l=e.data,a=l.length;for(let d=n;d<a;d+=1)if(l.charAt(d)!==B)return!1;i=e}else i=me(e,n);let s=G(o,t);if(!s)return!1;let r=new C(s,5,ze);return r.currentNode=i,!r.nextNode()},ye=(o,t)=>{let e=L(o,t),n=G(o,t),i;e&&n&&(i=e.parentNode,o.setStart(i,Array.from(i.childNodes).indexOf(e)),i=n.parentNode,o.setEnd(i,Array.from(i.childNodes).indexOf(n)+1))};function $(o,t,e,n){let i=document.createRange();return i.setStart(o,t),e&&typeof n=="number"?i.setEnd(e,n):i.setEnd(o,t),i}var j=(o,t)=>{let{startContainer:e,startOffset:n,endContainer:i,endOffset:s}=o,r;if(e instanceof Text){let a=e.parentNode;if(r=a.childNodes,n===e.length)n=Array.from(r).indexOf(e)+1,o.collapsed&&(i=a,s=n);else{if(n){let d=e.splitText(n);i===e?(s-=n,i=d):i===a&&(s+=1),e=d}n=Array.from(r).indexOf(e)}e=a}else r=e.childNodes;let l=r.length;n===l?e.appendChild(t):e.insertBefore(t,r[n]),e===i&&(s+=r.length-l),o.setStart(e,n),o.setEnd(i,s)},Be=(o,t,e)=>{let n=document.createDocumentFragment();if(o.collapsed)return n;t||(t=o.commonAncestorContainer),t instanceof Text&&(t=t.parentNode);let i=o.startContainer,s=o.startOffset,r=w(o.endContainer,o.endOffset,t,e),l=0,a=w(i,s,t,e);for(;a&&a!==r;){let d=a.nextSibling;n.appendChild(a),a=d}return i instanceof Text&&r instanceof Text&&(i.appendData(r.data),E(r),r=i,l=s),o.setStart(i,s),r?o.setEnd(r,l):o.setEnd(t,t.childNodes.length),x(t),n},Ge=(o,t,e)=>{o.currentNode=e;let n;for(;n=o[t]();){if(n instanceof Text||M(n))return n;if(!N(n))return null}return null},H=(o,t)=>{let e=L(o,t),n=G(o,t),i=e!==n;e&&n&&(T(o),q(o,e,n,t));let s=Be(o,null,t);T(o),i&&(n=G(o,t),e&&n&&e!==n&&Y(e,n,o,t)),e&&x(e);let r=t.firstChild;(!r||r.nodeName==="BR")&&(x(t),t.firstChild&&o.selectNodeContents(t.firstChild)),o.collapse(!0);let l=o.startContainer,a=o.startOffset,d=new C(t,5),c=l,f=a;(!(c instanceof Text)||f===c.data.length)&&(c=Ge(d,"nextNode",c),f=0);let u=l,m=a-1;(!(u instanceof Text)||m===-1)&&(u=Ge(d,"previousPONode",c||(l instanceof Text?l:l.childNodes[a]||l)),u instanceof Text&&(m=u.data.length));let h=null,g=0;return c instanceof Text&&c.data.charAt(f)===" "&&X(o,t)?(h=c,g=f):u instanceof Text&&u.data.charAt(m)===" "&&(c instanceof Text&&c.data.charAt(f)===" "||Z(o,t))&&(h=u,g=m),h&&h.replaceData(g,1,"\xA0"),o.setStart(l,a),o.collapse(!0),s},Ze=(o,t,e)=>{let n=t.firstChild&&N(t.firstChild),i;for(D(t,e),i=t;i=W(i,e);)x(i);o.collapsed||H(o,e),T(o),o.collapse(!1);let s=S(o.endContainer,e,"BLOCKQUOTE")||e,r=L(o,e),l=null,a=W(t,t),d=!n&&!!r&&ce(r);if(r&&a&&!d&&!S(a,t,"PRE")&&!S(a,t,"TABLE")){q(o,r,r,e),o.collapse(!0);let c=o.endContainer,f=o.endOffset;if(le(r,e,!1),N(c)){let u=w(c,f,z(c,e)||e,e);c=u.parentNode,f=Array.from(c.childNodes).indexOf(u)}if(f!==y(c))for(l=document.createDocumentFragment();i=c.childNodes[f];)l.appendChild(i);Y(c,a,o,e),f=Array.from(c.parentNode.childNodes).indexOf(c)+1,c=c.parentNode,o.setEnd(c,f)}if(y(t)){d&&r&&(o.setEndBefore(r),o.collapse(!1),E(r)),q(o,s,s,e);let c=w(o.endContainer,o.endOffset,s,e),f=c?c.previousSibling:s.lastChild;s.insertBefore(t,c),c?o.setEndBefore(c):o.setEnd(s,y(s)),r=G(o,e),T(o);let u=o.endContainer,m=o.endOffset;c&&Q(c)&&F(c,e),c=f&&f.nextSibling,c&&Q(c)&&F(c,e),o.setEnd(u,m)}if(l&&r){let c=o.cloneRange();x(l),Y(r,l,c,e),o.setEnd(c.endContainer,c.endOffset)}T(o)};var ge=o=>{if(o.collapsed)return"";let t=o.startContainer,e=o.endContainer,n=new C(o.commonAncestorContainer,5,a=>K(o,a,!0));n.currentNode=t;let i=t,s="",r=!1,l;for((!(i instanceof Element)&&!(i instanceof Text)||!n.filter(i))&&(i=n.nextNode());i;)i instanceof Text?(l=i.data,l&&/\S/.test(l)&&(i===e&&(l=l.slice(0,o.endOffset)),i===t&&(l=l.slice(o.startOffset)),s+=l,r=!0)):(i.nodeName==="BR"||r&&!N(i))&&(s+=`
+`,r=!1),i=n.nextNode();return s=s.replace(/ /g," "),s};var De=Array.prototype.indexOf,je=(o,t,e,n,i,s,r)=>{let l=o.clipboardData;if(se||!l)return!1;let a=s?"":ge(t),d=L(t,e),c=G(t,e),f=e;d===c&&(d!=null&&d.contains(t.commonAncestorContainer))&&(f=d);let u;n?u=H(t,e):(t=t.cloneRange(),T(t),q(t,f,f,e),u=t.cloneContents());let m=t.commonAncestorContainer;for(m instanceof Text&&(m=m.parentNode);m&&m!==f;){let g=m.cloneNode(!1);g.appendChild(u),u=g,m=m.parentNode}let h;if(u.childNodes.length===1&&u.childNodes[0]instanceof Text)a=u.childNodes[0].data.replace(/ /g," "),r=!0;else{let g=p("DIV");g.appendChild(u),h=g.innerHTML,i&&(h=i(h))}return s&&h!==void 0&&(a=s(h)),fe&&(a=a.replace(/\r?\n/g,`\r
+`)),!r&&h&&a!==h&&l.setData("text/html",h),l.setData("text/plain",a),o.preventDefault(),!0},Qe=function(o){let t=this.getSelection(),e=this._root;if(t.collapsed){o.preventDefault();return}this.saveUndoState(t),je(o,t,e,!0,this._config.willCutCopy,this._config.toPlainText,!1)||setTimeout(()=>{try{this._ensureBottomLine()}catch(i){this._config.didError(i)}},0),this.setSelection(t)},Xe=function(o){je(o,this.getSelection(),this._root,!1,this._config.willCutCopy,this._config.toPlainText,!1)},Ae=function(o){this._isShiftDown=o.shiftKey},$e=function(o){let t=o.clipboardData,e=t==null?void 0:t.items,n=this._isShiftDown,i=!1,s=!1,r=null,l=null;if(e){let b=e.length;for(;b--;){let O=e[b],A=O.type;A==="text/html"?l=O:A==="text/plain"||A==="text/uri-list"?r=O:A==="text/rtf"?i=!0:/^image\/.*/.test(A)&&(s=!0)}if(s&&!(i&&l)){o.preventDefault(),this.fireEvent("pasteImage",{clipboardData:t});return}if(!se){o.preventDefault(),l&&(!n||!r)?l.getAsString(O=>{this.insertHTML(O,!0)}):r&&r.getAsString(O=>{let A=!1,Ie=this.getSelection();if(!Ie.collapsed&&I.test(Ie.toString())){let Me=this.linkRegExp.exec(O);A=!!Me&&Me[0].length===O.length}A?this.makeLink(O):this.insertPlainText(O,!0)});return}}let a=t==null?void 0:t.types;if(!se&&a&&(De.call(a,"text/html")>-1||!Fe&&De.call(a,"text/plain")>-1&&De.call(a,"text/rtf")<0)){o.preventDefault();let b;!n&&(b=t.getData("text/html"))?this.insertHTML(b,!0):((b=t.getData("text/plain"))||(b=t.getData("text/uri-list")))&&this.insertPlainText(b,!0);return}let d=document.body,c=this.getSelection(),f=c.startContainer,u=c.startOffset,m=c.endContainer,h=c.endOffset,g=p("DIV",{contenteditable:"true",style:"position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;"});d.appendChild(g),c.selectNodeContents(g),this.setSelection(c),setTimeout(()=>{try{let b="",O=g,A;for(;g=O;)O=g.nextSibling,E(g),A=g.firstChild,A&&A===g.lastChild&&A instanceof HTMLDivElement&&(g=A),b+=g.innerHTML;this.setSelection($(f,u,m,h)),b&&this.insertHTML(b,!0)}catch(b){this._config.didError(b)}},0)},Ve=function(o){if(!o.dataTransfer)return;let t=o.dataTransfer.types,e=t.length,n=!1,i=!1;for(;e--;)switch(t[e]){case"text/plain":n=!0;break;case"text/html":i=!0;break;default:return}(i||n&&this.saveUndoState)&&this.saveUndoState()};var we=(o,t,e)=>{t.preventDefault(),o.splitBlock(t.shiftKey,e)};var ne=(o,t)=>{try{t||(t=o.getSelection());let e=t.startContainer;e instanceof Text&&(e=e.parentNode);let n=e;for(;N(n)&&(!n.textContent||n.textContent===B);)e=n,n=e.parentNode;e!==n&&(t.setStart(n,Array.from(n.childNodes).indexOf(e)),t.collapse(!0),n.removeChild(e),P(n)||(n=z(n,o._root)||o._root),x(n),T(t)),e===o._root&&(e=e.firstChild)&&e.nodeName==="BR"&&E(e),o._ensureBottomLine(),o.setSelection(t),o._updatePath(t,!0)}catch(e){o._config.didError(e)}},Ee=(o,t)=>{let e;for(;(e=o.parentNode)&&!(e===t||e.isContentEditable);)o=e;E(o)},Te=(o,t,e)=>{if(S(t,o._root,"A"))return;let n=t.data||"",i=Math.max(n.lastIndexOf(" ",e-1),n.lastIndexOf("\xA0",e-1))+1,s=n.slice(i,e),r=o.linkRegExp.exec(s);if(r){let l=o.getSelection();o._docWasChanged(),o._recordUndoState(l),o._getRangeAndRemoveBookmark(l);let a=i+r.index,d=a+r[0].length,c=l.startContainer===t,f=l.startOffset-d;a&&(t=t.splitText(a));let u=o._config.tagAttributes.a,m=p("A",Object.assign({href:r[1]?/^(?:ht|f)tps?:/i.test(r[1])?r[1]:"http://"+r[1]:"mailto:"+r[0]},u));m.textContent=n.slice(a,d),t.parentNode.insertBefore(m,t),t.data=n.slice(d),c&&(l.setStart(t,f),l.setEnd(t,f)),o.setSelection(l)}};var Ye=(o,t,e)=>{let n=o._root;if(o._removeZWS(),o.saveUndoState(e),!e.collapsed)t.preventDefault(),H(e,n),ne(o,e);else if(X(e,n)){t.preventDefault();let i=L(e,n);if(!i)return;let s=i;D(s.parentNode,n);let r=z(s,n);if(r){if(!r.isContentEditable){Ee(r,n);return}for(Y(r,s,e,n),s=r.parentNode;s!==n&&!s.nextSibling;)s=s.parentNode;s!==n&&(s=s.nextSibling)&&F(s,n),o.setSelection(e)}else if(s){if(S(s,n,"UL")||S(s,n,"OL")){o.decreaseListLevel(e);return}else if(S(s,n,"BLOCKQUOTE")){o.removeQuote(e);return}o.setSelection(e),o._updatePath(e,!0)}}else{T(e);let i=e.startContainer,s=e.startOffset,r=i.parentNode;i instanceof Text&&r instanceof HTMLAnchorElement&&s&&r.href.includes(i.data)?(i.deleteData(s-1,1),o.setSelection(e),o.removeLink(),t.preventDefault()):(o.setSelection(e),setTimeout(()=>{ne(o)},0))}};var Je=(o,t,e)=>{let n=o._root,i,s,r,l,a,d;if(o._removeZWS(),o.saveUndoState(e),!e.collapsed)t.preventDefault(),H(e,n),ne(o,e);else if(Z(e,n)){if(t.preventDefault(),i=L(e,n),!i)return;if(D(i.parentNode,n),s=W(i,n),s){if(!s.isContentEditable){Ee(s,n);return}for(Y(i,s,e,n),s=i.parentNode;s!==n&&!s.nextSibling;)s=s.parentNode;s!==n&&(s=s.nextSibling)&&F(s,n),o.setSelection(e),o._updatePath(e,!0)}}else{if(r=e.cloneRange(),q(e,n,n,n),l=e.endContainer,a=e.endOffset,l instanceof Element&&(d=l.childNodes[a],d&&d.nodeName==="IMG")){t.preventDefault(),E(d),T(e),ne(o,e);return}o.setSelection(r),setTimeout(()=>{ne(o)},0)}};var et=(o,t,e)=>{let n=o._root;if(o._removeZWS(),e.collapsed&&X(e,n)){let i=L(e,n),s;for(;s=i.parentNode;){if(s.nodeName==="UL"||s.nodeName==="OL"){t.preventDefault(),o.increaseListLevel(e);break}i=s}}},tt=(o,t,e)=>{let n=o._root;if(o._removeZWS(),e.collapsed&&X(e,n)){let i=e.startContainer;(S(i,n,"UL")||S(i,n,"OL"))&&(t.preventDefault(),o.decreaseListLevel(e))}};var nt=(o,t,e)=>{var s;let n,i=o._root;if(o._recordUndoState(e),o._getRangeAndRemoveBookmark(e),!e.collapsed)H(e,i),o._ensureBottomLine(),o.setSelection(e),o._updatePath(e,!0);else if(Z(e,i)){let r=L(e,i);if(r&&r.nodeName!=="PRE"){let l=(s=r.textContent)==null?void 0:s.trimEnd().replace(B,"");if(l==="*"||l==="1."){t.preventDefault(),o.insertPlainText(" ",!1),o._docWasChanged(),o.saveUndoState(e);let a=new C(r,4),d;for(;d=a.nextNode();)E(d);l==="*"?o.makeUnorderedList():o.makeOrderedList();return}}}if(n=e.endContainer,e.endOffset===y(n))do if(n.nodeName==="A"){e.setStartAfter(n);break}while(!n.nextSibling&&(n=n.parentNode)&&n!==i);if(o._config.addLinks){let r=e.cloneRange();T(r);let l=r.startContainer,a=r.startOffset;setTimeout(()=>{Te(o,l,a)},0)}o.setSelection(e)};var ot=function(o){if(o.defaultPrevented||o.isComposing)return;let t=o.key,e="",n=o.code;/^Digit\d$/.test(n)&&(t=n.slice(-1)),t!=="Backspace"&&t!=="Delete"&&(o.altKey&&(e+="Alt-"),o.ctrlKey&&(e+="Ctrl-"),o.metaKey&&(e+="Meta-"),o.shiftKey&&(e+="Shift-")),fe&&o.shiftKey&&t==="Delete"&&(e+="Shift-"),t=e+t;let i=this.getSelection();this._keyHandlers[t]?this._keyHandlers[t](this,o,i):!i.collapsed&&!o.ctrlKey&&!o.metaKey&&t.length===1&&(this.saveUndoState(i),H(i,this._root),this._ensureBottomLine(),this.setSelection(i),this._updatePath(i,!0))},v={Backspace:Ye,Delete:Je,Tab:et,"Shift-Tab":tt," ":nt,ArrowLeft(o){o._removeZWS()},ArrowRight(o,t,e){o._removeZWS();let n=o.getRoot();if(Z(e,n)){T(e);let i=e.endContainer;do if(i.nodeName==="CODE"){let s=i.nextSibling;if(!(s instanceof Text)){let r=document.createTextNode("\xA0");i.parentNode.insertBefore(r,s),s=r}e.setStart(s,1),o.setSelection(e),t.preventDefault();break}while(!i.nextSibling&&(i=i.parentNode)&&i!==n)}}};He||(v.Enter=we,v["Shift-Enter"]=we);!de&&!_e&&(v.PageUp=o=>{o.moveCursorToStart()},v.PageDown=o=>{o.moveCursorToEnd()});var ie=(o,t)=>(t=t||null,(e,n)=>{n.preventDefault();let i=e.getSelection();e.hasFormat(o,null,i)?e.changeFormat(null,{tag:o},i):e.changeFormat({tag:o},t,i)});v[k+"b"]=ie("B");v[k+"i"]=ie("I");v[k+"u"]=ie("U");v[k+"Shift-7"]=ie("S");v[k+"Shift-5"]=ie("SUB",{tag:"SUP"});v[k+"Shift-6"]=ie("SUP",{tag:"SUB"});v[k+"Shift-8"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)UL/.test(e)?o.removeList():o.makeUnorderedList()};v[k+"Shift-9"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)OL/.test(e)?o.removeList():o.makeOrderedList()};v[k+"["]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)BLOCKQUOTE/.test(e)||!/(?:^|>)[OU]L/.test(e)?o.decreaseQuoteLevel():o.decreaseListLevel()};v[k+"]"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)BLOCKQUOTE/.test(e)||!/(?:^|>)[OU]L/.test(e)?o.increaseQuoteLevel():o.increaseListLevel()};v[k+"d"]=(o,t)=>{t.preventDefault(),o.toggleCode()};v[k+"z"]=(o,t)=>{t.preventDefault(),o.undo()};v[k+"y"]=v[k+"Shift-z"]=v[k+"Shift-Z"]=(o,t)=>{t.preventDefault(),o.redo()};var Ce=class{constructor(t,e){this.customEvents=new Set(["pathChange","select","input","pasteImage","undoStateChange"]);this.startSelectionId="squire-selection-start";this.endSelectionId="squire-selection-end";this.linkRegExp=/\b(?:((?:(?:ht|f)tps?:\/\/|www\d{0,3}[.]|[a-z0-9][a-z0-9.\-]*[.][a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:[^\s?&`!()\[\]{};:'".,<>«»“”‘’]|\([^\s()<>]+\)))|([\w\-.%+]+@(?:[\w\-]+\.)+[a-z]{2,}\b(?:[?][^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+(?:&[^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+)*)?))/i;this.tagAfterSplit={DT:"DD",DD:"DT",LI:"LI",PRE:"PRE"};this._root=t,this._config=this._makeConfig(e),this._isFocused=!1,this._lastSelection=$(t,0),this._willRestoreSelection=!1,this._mayHaveZWS=!1,this._lastAnchorNode=null,this._lastFocusNode=null,this._path="",this._events=new Map,this._undoIndex=-1,this._undoStack=[],this._undoStackLength=0,this._isInUndoState=!1,this._ignoreChange=!1,this._ignoreAllChanges=!1,this.addEventListener("selectionchange",this._updatePathOnEvent),this.addEventListener("blur",this._enableRestoreSelection),this.addEventListener("mousedown",this._disableRestoreSelection),this.addEventListener("touchstart",this._disableRestoreSelection),this.addEventListener("focus",this._restoreSelection),this.addEventListener("blur",this._removeZWS),this._isShiftDown=!1,this.addEventListener("cut",Qe),this.addEventListener("copy",Xe),this.addEventListener("paste",$e),this.addEventListener("drop",Ve),this.addEventListener("keydown",Ae),this.addEventListener("keyup",Ae),this.addEventListener("keydown",ot),this._keyHandlers=Object.create(v);let n=new MutationObserver(()=>this._docWasChanged());n.observe(t,{childList:!0,attributes:!0,characterData:!0,subtree:!0}),this._mutation=n,t.setAttribute("contenteditable","true"),this.addEventListener("beforeinput",this._beforeInput),this.setHTML("")}destroy(){this._events.forEach((t,e)=>{this.removeEventListener(e)}),this._mutation.disconnect(),this._undoIndex=-1,this._undoStack=[],this._undoStackLength=0}_makeConfig(t){let e={blockTag:"DIV",blockAttributes:null,tagAttributes:{},classNames:{color:"color",fontFamily:"font",fontSize:"size",highlight:"highlight"},undo:{documentSizeThreshold:-1,undoLimit:-1},addLinks:!0,willCutCopy:null,toPlainText:null,sanitizeToDOMFragment:n=>{let i=DOMPurify.sanitize(n,{ALLOW_UNKNOWN_PROTOCOLS:!0,WHOLE_DOCUMENT:!1,RETURN_DOM:!0,RETURN_DOM_FRAGMENT:!0,FORCE_BODY:!1});return i?document.importNode(i,!0):document.createDocumentFragment()},didError:n=>console.log(n)};return t&&(Object.assign(e,t),e.blockTag=e.blockTag.toUpperCase()),e}setKeyHandler(t,e){return this._keyHandlers[t]=e,this}_beforeInput(t){switch(t.inputType){case"insertLineBreak":t.preventDefault(),this.splitBlock(!0);break;case"insertParagraph":t.preventDefault(),this.splitBlock(!1);break;case"insertOrderedList":t.preventDefault(),this.makeOrderedList();break;case"insertUnoderedList":t.preventDefault(),this.makeUnorderedList();break;case"historyUndo":t.preventDefault(),this.undo();break;case"historyRedo":t.preventDefault(),this.redo();break;case"formatBold":t.preventDefault(),this.bold();break;case"formaItalic":t.preventDefault(),this.italic();break;case"formatUnderline":t.preventDefault(),this.underline();break;case"formatStrikeThrough":t.preventDefault(),this.strikethrough();break;case"formatSuperscript":t.preventDefault(),this.superscript();break;case"formatSubscript":t.preventDefault(),this.subscript();break;case"formatJustifyFull":case"formatJustifyCenter":case"formatJustifyRight":case"formatJustifyLeft":{t.preventDefault();let e=t.inputType.slice(13).toLowerCase();e==="full"&&(e="justify"),this.setTextAlignment(e);break}case"formatRemove":t.preventDefault(),this.removeAllFormatting();break;case"formatSetBlockTextDirection":{t.preventDefault();let e=t.data;e==="null"&&(e=null),this.setTextDirection(e);break}case"formatBackColor":t.preventDefault(),this.setHighlightColor(t.data);break;case"formatFontColor":t.preventDefault(),this.setTextColor(t.data);break;case"formatFontName":t.preventDefault(),this.setFontFace(t.data);break}}handleEvent(t){this.fireEvent(t.type,t)}fireEvent(t,e){let n=this._events.get(t);if(/^(?:focus|blur)/.test(t)){let i=this._root===document.activeElement;if(t==="focus"){if(!i||this._isFocused)return this;this._isFocused=!0}else{if(i||!this._isFocused)return this;this._isFocused=!1}}if(n){let i=e instanceof Event?e:new CustomEvent(t,{detail:e});n=n.slice();for(let s of n)try{"handleEvent"in s?s.handleEvent(i):s.call(this,i)}catch(r){this._config.didError(r)}}return this}addEventListener(t,e){let n=this._events.get(t),i=this._root;return n||(n=[],this._events.set(t,n),this.customEvents.has(t)||(t==="selectionchange"&&(i=document),i.addEventListener(t,this,!0))),n.push(e),this}removeEventListener(t,e){let n=this._events.get(t),i=this._root;if(n){if(e){let s=n.length;for(;s--;)n[s]===e&&n.splice(s,1)}else n.length=0;n.length||(this._events.delete(t),this.customEvents.has(t)||(t==="selectionchange"&&(i=document),i.removeEventListener(t,this,!0)))}return this}focus(){return this._root.focus({preventScroll:!0}),this}blur(){return this._root.blur(),this}_enableRestoreSelection(){this._willRestoreSelection=!0}_disableRestoreSelection(){this._willRestoreSelection=!1}_restoreSelection(){this._willRestoreSelection&&this.setSelection(this._lastSelection)}_removeZWS(){this._mayHaveZWS&&(re(this._root),this._mayHaveZWS=!1)}_saveRangeToBookmark(t){let e=p("INPUT",{id:this.startSelectionId,type:"hidden"}),n=p("INPUT",{id:this.endSelectionId,type:"hidden"}),i;j(t,e),t.collapse(!1),j(t,n),e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING&&(e.id=this.endSelectionId,n.id=this.startSelectionId,i=e,e=n,n=i),t.setStartAfter(e),t.setEndBefore(n)}_getRangeAndRemoveBookmark(t){let e=this._root,n=e.querySelector("#"+this.startSelectionId),i=e.querySelector("#"+this.endSelectionId);if(n&&i){let s=n.parentNode,r=i.parentNode,l=Array.from(s.childNodes).indexOf(n),a=Array.from(r.childNodes).indexOf(i);s===r&&(a-=1),n.remove(),i.remove(),t||(t=document.createRange()),t.setStart(s,l),t.setEnd(r,a),te(s,t),s!==r&&te(r,t),t.collapsed&&(s=t.startContainer,s instanceof Text&&(r=s.childNodes[t.startOffset],(!r||!(r instanceof Text))&&(r=s.childNodes[t.startOffset-1]),r&&r instanceof Text&&(t.setStart(r,0),t.collapse(!0))))}return t||null}getSelection(){let t=window.getSelection(),e=this._root,n=null;if(this._isFocused&&t&&t.rangeCount){n=t.getRangeAt(0).cloneRange();let i=n.startContainer,s=n.endContainer;i&&M(i)&&n.setStartBefore(i),s&&M(s)&&n.setEndBefore(s)}return n&&e.contains(n.commonAncestorContainer)?this._lastSelection=n:(n=this._lastSelection,document.contains(n.commonAncestorContainer)||(n=null)),n||(n=$(e.firstElementChild||e,0)),n}setSelection(t){if(this._lastSelection=t,!this._isFocused)this._enableRestoreSelection();else{let e=window.getSelection();e&&("setBaseAndExtent"in Selection.prototype?e.setBaseAndExtent(t.startContainer,t.startOffset,t.endContainer,t.endOffset):(e.removeAllRanges(),e.addRange(t)))}return this}_moveCursorTo(t){let e=this._root,n=$(e,t?0:e.childNodes.length);return T(n),this.setSelection(n),this}moveCursorToStart(){return this._moveCursorTo(!0)}moveCursorToEnd(){return this._moveCursorTo(!1)}getCursorPosition(){let t=this.getSelection(),e=t.getBoundingClientRect();if(e&&!e.top){this._ignoreChange=!0;let n=p("SPAN");n.textContent=B,j(t,n),e=n.getBoundingClientRect();let i=n.parentNode;i.removeChild(n),te(i,t)}return e}getPath(){return this._path}_updatePathOnEvent(){this._isFocused&&this._updatePath(this.getSelection())}_updatePath(t,e){let n=t.startContainer,i=t.endContainer,s;(e||n!==this._lastAnchorNode||i!==this._lastFocusNode)&&(this._lastAnchorNode=n,this._lastFocusNode=i,s=n&&i?n===i?this._getPath(i):"(selection)":"",this._path!==s&&(this._path=s,this.fireEvent("pathChange",{path:s}))),this.fireEvent(t.collapsed?"cursor":"select",{range:t})}_getPath(t){let e=this._root,n=this._config,i="";if(t&&t!==e){let s=t.parentNode;if(i=s?this._getPath(s):"",t instanceof HTMLElement){let r=t.id,l=t.classList,a=Array.from(l).sort(),d=t.dir,c=n.classNames;i+=(i?">":"")+t.nodeName,r&&(i+="#"+r),a.length&&(i+=".",i+=a.join(".")),d&&(i+="[dir="+d+"]"),l.contains(c.highlight)&&(i+="[backgroundColor="+t.style.backgroundColor.replace(/ /g,"")+"]"),l.contains(c.color)&&(i+="[color="+t.style.color.replace(/ /g,"")+"]"),l.contains(c.fontFamily)&&(i+="[fontFamily="+t.style.fontFamily.replace(/ /g,"")+"]"),l.contains(c.fontSize)&&(i+="[fontSize="+t.style.fontSize+"]")}}return i}modifyDocument(t){let e=this._mutation;return e&&(e.takeRecords().length&&this._docWasChanged(),e.disconnect()),this._ignoreAllChanges=!0,t(),this._ignoreAllChanges=!1,e&&(e.observe(this._root,{childList:!0,attributes:!0,characterData:!0,subtree:!0}),this._ignoreChange=!1),this}_docWasChanged(){if(qe(),this._mayHaveZWS=!0,!this._ignoreAllChanges){if(this._ignoreChange){this._ignoreChange=!1;return}this._isInUndoState&&(this._isInUndoState=!1,this.fireEvent("undoStateChange",{canUndo:!0,canRedo:!1})),this.fireEvent("input")}}_recordUndoState(t,e){let n=this._isInUndoState;if(!n||e){let i=this._undoIndex+1,s=this._undoStack,r=this._config.undo,l=r.documentSizeThreshold,a=r.undoLimit;if(i<this._undoStackLength&&(s.length=this._undoStackLength=i),t&&this._saveRangeToBookmark(t),n)return this;let d=this._getRawHTML();e&&(i-=1),l>-1&&d.length*2>l&&a>-1&&i>a&&(s.splice(0,i-a),i=a,this._undoStackLength=a),s[i]=d,this._undoIndex=i,this._undoStackLength+=1,this._isInUndoState=!0}return this}saveUndoState(t){return t||(t=this.getSelection()),this._recordUndoState(t,this._isInUndoState),this._getRangeAndRemoveBookmark(t),this}undo(){if(this._undoIndex!==0||!this._isInUndoState){this._recordUndoState(this.getSelection(),!1),this._undoIndex-=1,this._setRawHTML(this._undoStack[this._undoIndex]);let t=this._getRangeAndRemoveBookmark();t&&this.setSelection(t),this._isInUndoState=!0,this.fireEvent("undoStateChange",{canUndo:this._undoIndex!==0,canRedo:!0}),this.fireEvent("input")}return this.focus()}redo(){let t=this._undoIndex,e=this._undoStackLength;if(t+1<e&&this._isInUndoState){this._undoIndex+=1,this._setRawHTML(this._undoStack[this._undoIndex]);let n=this._getRangeAndRemoveBookmark();n&&this.setSelection(n),this.fireEvent("undoStateChange",{canUndo:!0,canRedo:t+2<e}),this.fireEvent("input")}return this.focus()}getRoot(){return this._root}_getRawHTML(){return this._root.innerHTML}_setRawHTML(t){let e=this._root;e.innerHTML=t;let n=e,i=n.firstChild;if(!i||i.nodeName==="BR"){let s=this.createDefaultBlock();i?n.replaceChild(s,i):n.appendChild(s)}else for(;n=W(n,e);)x(n);return this._ignoreChange=!0,this}getHTML(t){let e;t&&(e=this.getSelection(),this._saveRangeToBookmark(e));let n=this._getRawHTML().replace(/\u200B/g,"");return t&&this._getRangeAndRemoveBookmark(e),n}setHTML(t){let e=this._config.sanitizeToDOMFragment(t,this),n=this._root;Ne(e,this._config),le(e,n,!1),D(e,n);let i=e,s=i.firstChild;if(!s||s.nodeName==="BR"){let l=this.createDefaultBlock();s?i.replaceChild(l,s):i.appendChild(l)}else for(;i=W(i,n);)x(i);for(this._ignoreChange=!0;s=n.lastChild;)n.removeChild(s);n.appendChild(e),this._undoIndex=-1,this._undoStack.length=0,this._undoStackLength=0,this._isInUndoState=!1;let r=this._getRangeAndRemoveBookmark()||$(n.firstElementChild||n,0);return this.saveUndoState(r),this.setSelection(r),this._updatePath(r,!0),this}insertHTML(t,e){let n=this._config,i=n.sanitizeToDOMFragment(t,this),s=this.getSelection();this.saveUndoState(s);try{let r=this._root;n.addLinks&&this.addDetectedLinks(i,i),Ne(i,this._config),le(i,r,!1),Se(i),i.normalize();let l=i;for(;l=W(l,i);)x(l);let a=!0;if(e){let d=new CustomEvent("willPaste",{cancelable:!0,detail:{fragment:i}});this.fireEvent("willPaste",d),i=d.detail.fragment,a=!d.defaultPrevented}a&&(Ze(s,i,r),s.collapse(!1),Oe(s,"A",r),this._ensureBottomLine()),this.setSelection(s),this._updatePath(s,!0),e&&this.focus()}catch(r){this._config.didError(r)}return this}insertElement(t,e){if(e||(e=this.getSelection()),e.collapse(!0),N(t))j(e,t),e.setStartAfter(t);else{let n=this._root,i=L(e,n),s=i||n,r=null;for(;s!==n&&!s.nextSibling;)s=s.parentNode;if(s!==n){let a=s.parentNode;r=w(a,s.nextSibling,n,n)}i&&ce(i)&&E(i),n.insertBefore(t,r);let l=this.createDefaultBlock();n.insertBefore(l,r),e.setStart(l,0),e.setEnd(l,0),T(e)}return this.focus(),this.setSelection(e),this._updatePath(e),this}insertImage(t,e){let n=p("IMG",Object.assign({src:t},e));return this.insertElement(n),n}insertPlainText(t,e){let n=this.getSelection();if(n.collapsed&&S(n.startContainer,this._root,"PRE")){let c=n.startContainer,f=n.startOffset,u;if(!c||!(c instanceof Text)){let h=document.createTextNode("");c.insertBefore(h,c.childNodes[f]),u=h,f=0}else u=c;let m=!0;if(e){let h=new CustomEvent("willPaste",{cancelable:!0,detail:{text:t}});this.fireEvent("willPaste",h),t=h.detail.text,m=!h.defaultPrevented}return m&&(u.insertData(f,t),n.setStart(u,f+t.length),n.collapse(!0)),this.setSelection(n),this}let i=t.split(`
+`),s=this._config,r=s.blockTag,l=s.blockAttributes,a="</"+r+">",d="<"+r;for(let c in l)d+=" "+c+'="'+ke(l[c])+'"';d+=">";for(let c=0,f=i.length;c<f;c+=1){let u=i[c];u=ke(u).replace(/ (?=(?: |$))/g,"&nbsp;"),c&&(u=d+(u||"<BR>")+a),i[c]=u}return this.insertHTML(i.join(""),e)}getSelectedText(t){return ge(t||this.getSelection())}getFontInfo(t){let e={color:void 0,backgroundColor:void 0,fontFamily:void 0,fontSize:void 0};t||(t=this.getSelection()),T(t);let n=0,i=t.commonAncestorContainer;if(t.collapsed||i instanceof Text)for(i instanceof Text&&(i=i.parentNode);n<4&&i;){let s=i.style;if(s){let r=s.color;!e.color&&r&&(e.color=r,n+=1);let l=s.backgroundColor;!e.backgroundColor&&l&&(e.backgroundColor=l,n+=1);let a=s.fontFamily;!e.fontFamily&&a&&(e.fontFamily=a,n+=1);let d=s.fontSize;!e.fontSize&&d&&(e.fontSize=d,n+=1)}i=i.parentNode}return e}hasFormat(t,e,n){t=t.toUpperCase(),e||(e={}),n||(n=this.getSelection()),!n.collapsed&&n.startContainer instanceof Text&&n.startOffset===n.startContainer.length&&n.startContainer.nextSibling&&n.setStartBefore(n.startContainer.nextSibling),!n.collapsed&&n.endContainer instanceof Text&&n.endOffset===0&&n.endContainer.previousSibling&&n.setEndAfter(n.endContainer.previousSibling);let i=this._root,s=n.commonAncestorContainer;if(S(s,i,t,e))return!0;if(s instanceof Text)return!1;let r=new C(s,4,d=>K(n,d,!0)),l=!1,a;for(;a=r.nextNode();){if(!S(a,i,t,e))return!1;l=!0}return l}changeFormat(t,e,n,i){return n||(n=this.getSelection()),this.saveUndoState(n),e&&(n=this._removeFormat(e.tag.toUpperCase(),e.attributes||{},n,i)),t&&(n=this._addFormat(t.tag.toUpperCase(),t.attributes||{},n)),this.setSelection(n),this._updatePath(n,!0),this.focus()}_addFormat(t,e,n){let i=this._root;if(n.collapsed){let s=x(p(t,e));j(n,s);let r=s.firstChild||s,l=r instanceof Text?r.length:0;n.setStart(r,l),n.collapse(!0);let a=s;for(;N(a);)a=a.parentNode;re(a,s)}else{let s=new C(n.commonAncestorContainer,5,c=>(c instanceof Text||c.nodeName==="BR"||c.nodeName==="IMG")&&K(n,c,!0)),{startContainer:r,startOffset:l,endContainer:a,endOffset:d}=n;if(s.currentNode=r,!(r instanceof Element)&&!(r instanceof Text)||!s.filter(r)){let c=s.nextNode();if(!c)return n;r=c,l=0}do{let c=s.currentNode;if(!S(c,i,t,e)){c===a&&c.length>d&&c.splitText(d),c===r&&l&&(c=c.splitText(l),a===r?(a=c,d-=l):a===r.parentNode&&(d+=1),r=c,l=0);let u=p(t,e);R(c,u),u.appendChild(c)}}while(s.nextNode());n=$(r,l,a,d)}return n}_removeFormat(t,e,n,i){this._saveRangeToBookmark(n);let s;n.collapsed&&(oe?s=document.createTextNode(B):s=document.createTextNode(""),j(n,s));let r=n.commonAncestorContainer;for(;N(r);)r=r.parentNode;let l=n.startContainer,a=n.startOffset,d=n.endContainer,c=n.endOffset,f=[],u=(h,g)=>{if(K(n,h,!1))return;let b,O;if(!K(n,h,!0)){!(h instanceof HTMLInputElement)&&(!(h instanceof Text)||h.data)&&f.push([g,h]);return}if(h instanceof Text)h===d&&c!==h.length&&f.push([g,h.splitText(c)]),h===l&&a&&(h.splitText(a),f.push([g,h]));else for(b=h.firstChild;b;b=O)O=b.nextSibling,u(b,g)},m=Array.from(r.getElementsByTagName(t)).filter(h=>K(n,h,!0)&&he(h,t,e));if(i||m.forEach(h=>{u(h,h)}),f.forEach(([h,g])=>{h=h.cloneNode(!1),R(g,h),h.appendChild(g)}),m.forEach(h=>{R(h,_(h))}),oe&&s){s=s.parentNode;let h=s;for(;h&&N(h);)h=h.parentNode;h&&re(h,s)}return this._getRangeAndRemoveBookmark(n),s&&n.collapse(!1),te(r,n),n}bold(){return this.changeFormat({tag:"B"})}removeBold(){return this.changeFormat(null,{tag:"B"})}italic(){return this.changeFormat({tag:"I"})}removeItalic(){return this.changeFormat(null,{tag:"I"})}underline(){return this.changeFormat({tag:"U"})}removeUnderline(){return this.changeFormat(null,{tag:"U"})}strikethrough(){return this.changeFormat({tag:"S"})}removeStrikethrough(){return this.changeFormat(null,{tag:"S"})}subscript(){return this.changeFormat({tag:"SUB"},{tag:"SUP"})}removeSubscript(){return this.changeFormat(null,{tag:"SUB"})}superscript(){return this.changeFormat({tag:"SUP"},{tag:"SUB"})}removeSuperscript(){return this.changeFormat(null,{tag:"SUP"})}makeLink(t,e){let n=this.getSelection();if(n.collapsed){let i=t.indexOf(":")+1;if(i)for(;t[i]==="/";)i+=1;j(n,document.createTextNode(t.slice(i)))}return e=Object.assign({href:t},this._config.tagAttributes.a,e),this.changeFormat({tag:"A",attributes:e},{tag:"A"},n)}removeLink(){return this.changeFormat(null,{tag:"A"},this.getSelection(),!0)}addDetectedLinks(t,e){let n=new C(t,4,l=>!S(l,e||this._root,"A")),i=this.linkRegExp,s=this._config.tagAttributes.a,r;for(;r=n.nextNode();){let l=r.parentNode,a=r.data,d;for(;d=i.exec(a);){let c=d.index,f=c+d[0].length;c&&l.insertBefore(document.createTextNode(a.slice(0,c)),r);let u=p("A",Object.assign({href:d[1]?/^(?:ht|f)tps?:/i.test(d[1])?d[1]:"http://"+d[1]:"mailto:"+d[0]},s));u.textContent=a.slice(c,f),l.insertBefore(u,r),r.data=a=a.slice(f)}}return this}setFontFace(t){let e=this._config.classNames.fontFamily;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"font-family: "+t+", sans-serif;"}}:null,{tag:"SPAN",attributes:{class:e}})}setFontSize(t){let e=this._config.classNames.fontSize;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"font-size: "+(typeof t=="number"?t+"px":t)}}:null,{tag:"SPAN",attributes:{class:e}})}setTextColor(t){let e=this._config.classNames.color;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"color:"+t}}:null,{tag:"SPAN",attributes:{class:e}})}setHighlightColor(t){let e=this._config.classNames.highlight;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"background-color:"+t}}:null,{tag:"SPAN",attributes:{class:e}})}_ensureBottomLine(){let t=this._root,e=t.lastElementChild;(!e||e.nodeName!==this._config.blockTag||!P(e))&&t.appendChild(this.createDefaultBlock())}createDefaultBlock(t){let e=this._config;return x(p(e.blockTag,e.blockAttributes,t))}splitBlock(t,e){e||(e=this.getSelection());let n=this._root,i,s,r,l;if(this._recordUndoState(e),this._removeZWS(),this._getRangeAndRemoveBookmark(e),e.collapsed||H(e,n),this._config.addLinks){T(e);let u=e.startContainer,m=e.startOffset;setTimeout(()=>{Te(this,u,m)},0)}if(i=L(e,n),i&&(s=S(i,n,"PRE"))){T(e),r=e.startContainer;let u=e.startOffset;return r instanceof Text||(r=document.createTextNode(""),s.insertBefore(r,s.firstChild)),!t&&r instanceof Text&&(r.data.charAt(u-1)===`
 `||X(e,n))&&(r.data.charAt(u)===`
 `||Z(e,n))?(r.deleteData(u&&u-1,u?2:1),l=w(r,u&&u-1,n,n),r=l.previousSibling,r.textContent||E(r),r=this.createDefaultBlock(),l.parentNode.insertBefore(r,l),l.textContent||E(l),e.setStart(r,0)):(r.insertData(u,`
-`),x(s),r.length===u+1?e.setStartAfter(r):e.setStart(r,u+1)),e.collapse(!0),this.setSelection(e),this._updatePath(e,!0),this._docWasChanged(),this}if(!i||t||/^T[HD]$/.test(i.nodeName))return Oe(e,"A",n),j(e,p("BR")),e.collapse(!1),this.setSelection(e),this._updatePath(e,!0),this;if((s=S(i,n,"LI"))&&(i=s),ce(i)){if(S(i,n,"UL")||S(i,n,"OL"))return this.decreaseListLevel(e),this;if(S(i,n,"BLOCKQUOTE"))return this.removeQuote(e),this}r=e.startContainer;let a=e.startOffset,d=this.tagAfterSplit[i.nodeName];l=w(r,a,i.parentNode,this._root);let c=this._config,f=null;for(d||(d=c.blockTag,f=c.blockAttributes),he(l,d,f)||(i=p(d,f),l.dir&&(i.dir=l.dir),R(l,i),i.appendChild(C(l)),l=i),re(i),Se(i),x(i);l instanceof Element;){let u=l.firstChild,m;if(l.nodeName==="A"&&(!l.textContent||l.textContent===B)){u=document.createTextNode(""),R(l,u),l=u;break}for(;u&&u instanceof Text&&!u.data&&(m=u.nextSibling,!(!m||m.nodeName==="BR"));)E(u),u=m;if(!u||u.nodeName==="BR"||u instanceof Text)break;l=u}return e=$(l,0),this.setSelection(e),this._updatePath(e,!0),this}forEachBlock(t,e,n){n||(n=this.getSelection()),e&&this.saveUndoState(n);let i=this._root,s=L(n,i),r=G(n,i);if(s&&r)do if(t(s)||s===r)break;while(s=W(s,i));return e&&(this.setSelection(n),this._updatePath(n,!0)),this}modifyBlocks(t,e){e||(e=this.getSelection()),this._recordUndoState(e,this._isInUndoState);let n=this._root;ye(e,n),q(e,n,n,n);let i=Be(e,n,n);if(!e.collapsed){let s=e.endContainer;if(s===n)e.collapse(!1);else{for(;s.parentNode!==n;)s=s.parentNode;e.setStartBefore(s),e.collapse(!0)}}return j(e,t.call(this,i)),e.endOffset<e.endContainer.childNodes.length&&F(e.endContainer.childNodes[e.endOffset],n),F(e.startContainer.childNodes[e.startOffset],n),this._getRangeAndRemoveBookmark(e),this.setSelection(e),this._updatePath(e,!0),this}setTextAlignment(t){return this.forEachBlock(e=>{let n=e.className.split(/\s+/).filter(i=>!!i&&!/^align/.test(i)).join(" ");t?(e.className=n+" align-"+t,e.style.textAlign=t):(e.className=n,e.style.textAlign="")},!0),this.focus()}setTextDirection(t){return this.forEachBlock(e=>{t?e.dir=t:e.removeAttribute("dir")},!0),this.focus()}_getListSelection(t,e){let n=t.commonAncestorContainer,i=t.startContainer,s=t.endContainer;for(;n&&n!==e&&!/^[OU]L$/.test(n.nodeName);)n=n.parentNode;if(!n||n===e)return null;for(i===n&&(i=i.childNodes[t.startOffset]),s===n&&(s=s.childNodes[t.endOffset]);i&&i.parentNode!==n;)i=i.parentNode;for(;s&&s.parentNode!==n;)s=s.parentNode;return[n,i,s]}increaseListLevel(t){t||(t=this.getSelection());let e=this._root,n=this._getListSelection(t,e);if(!n)return this.focus();let[i,s,r]=n;if(!s||s===i.firstChild)return this.focus();this._recordUndoState(t,this._isInUndoState);let l=i.nodeName,a=s.previousSibling,d,c;a.nodeName!==l&&(d=this._config.tagAttributes[l.toLowerCase()],a=p(l,d),i.insertBefore(a,s));do c=s===r?null:s.nextSibling,a.appendChild(s);while(s=c);return c=a.nextSibling,c&&F(c,e),this._getRangeAndRemoveBookmark(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}decreaseListLevel(t){t||(t=this.getSelection());let e=this._root,n=this._getListSelection(t,e);if(!n)return this.focus();let[i,s,r]=n;s||(s=i.firstChild),r||(r=i.lastChild),this._recordUndoState(t,this._isInUndoState);let l,a=null;if(s){let d=i.parentNode;if(a=r.nextSibling?w(i,r.nextSibling,d,e):i.nextSibling,d!==e&&d.nodeName==="LI"){for(d=d.parentNode;a;)l=a.nextSibling,r.appendChild(a),a=l;a=i.parentNode.nextSibling}let c=!/^[OU]L$/.test(d.nodeName);do l=s===r?null:s.nextSibling,i.removeChild(s),c&&s.nodeName==="LI"&&(s=this.createDefaultBlock([C(s)])),d.insertBefore(s,a);while(s=l)}return i.firstChild||E(i),a&&F(a,e),this._getRangeAndRemoveBookmark(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}_makeList(t,e){let n=ae(t,this._root),i=this._config.tagAttributes,s=i[e.toLowerCase()],r=i.li,l;for(;l=n.nextNode();)if(l.parentNode instanceof HTMLLIElement&&(l=l.parentNode,n.currentNode=l.lastChild),l instanceof HTMLLIElement){l=l.parentNode;let a=l.nodeName;a!==e&&/^[OU]L$/.test(a)&&R(l,p(e,s,[C(l)]))}else{let a=p("LI",r);l.dir&&(a.dir=l.dir);let d=l.previousSibling;d&&d.nodeName===e?(d.appendChild(a),E(l)):R(l,p(e,s,[a])),a.appendChild(C(l)),n.currentNode=a}return t}makeUnorderedList(){return this.modifyBlocks(t=>this._makeList(t,"UL")),this.focus()}makeOrderedList(){return this.modifyBlocks(t=>this._makeList(t,"OL")),this.focus()}removeList(){return this.modifyBlocks(t=>{let e=t.querySelectorAll("UL, OL"),n=t.querySelectorAll("LI"),i=this._root;for(let s=0,r=e.length;s<r;s+=1){let l=e[s],a=C(l);D(a,i),R(l,a)}for(let s=0,r=n.length;s<r;s+=1){let l=n[s];P(l)?R(l,this.createDefaultBlock([C(l)])):(D(l,i),R(l,C(l)))}return t}),this.focus()}increaseQuoteLevel(t){return this.modifyBlocks(e=>p("BLOCKQUOTE",this._config.tagAttributes.blockquote,[e]),t),this.focus()}decreaseQuoteLevel(t){return this.modifyBlocks(e=>(Array.from(e.querySelectorAll("blockquote")).filter(n=>!S(n.parentNode,e,"BLOCKQUOTE")).forEach(n=>{R(n,C(n))}),e),t),this.focus()}removeQuote(t){return this.modifyBlocks(()=>this.createDefaultBlock([p("INPUT",{id:this.startSelectionId,type:"hidden"}),p("INPUT",{id:this.endSelectionId,type:"hidden"})]),t),this.focus()}code(){let t=this.getSelection();return t.collapsed||Q(t.commonAncestorContainer)?(this.modifyBlocks(e=>{let n=this._root,i=document.createDocumentFragment(),s=ae(e,n),r;for(;r=s.nextNode();){let a=r.querySelectorAll("BR"),d=[],c=a.length;for(let f=0;f<c;f+=1)d[f]=ee(a[f],!1);for(;c--;){let f=a[c];d[c]?R(f,document.createTextNode(`
-`)):E(f)}for(a=r.querySelectorAll("CODE"),c=a.length;c--;)R(a[c],C(a[c]));i.childNodes.length&&i.appendChild(document.createTextNode(`
-`)),i.appendChild(C(r))}let l=new T(i,4);for(;r=l.nextNode();)r.data=r.data.replace(/ /g," ");return i.normalize(),x(p("PRE",this._config.tagAttributes.pre,[i]))},t),this.focus()):this.changeFormat({tag:"CODE",attributes:this._config.tagAttributes.code},null,t),this}removeCode(){let t=this.getSelection(),e=t.commonAncestorContainer;return S(e,this._root,"PRE")?(this.modifyBlocks(i=>{let s=this._root,r=i.querySelectorAll("PRE"),l=r.length;for(;l--;){let a=r[l],d=new T(a,4),c;for(;c=d.nextNode();){let f=c.data;f=f.replace(/ (?= )/g,"\xA0");let u=document.createDocumentFragment(),m;for(;(m=f.indexOf(`
-`))>-1;)u.appendChild(document.createTextNode(f.slice(0,m))),u.appendChild(p("BR")),f=f.slice(m+1);c.parentNode.insertBefore(u,c),c.data=f}D(a,s),R(a,C(a))}return i},t),this.focus()):this.changeFormat(null,{tag:"CODE"},t),this}toggleCode(){return this.hasFormat("PRE")||this.hasFormat("CODE")?this.removeCode():this.code(),this}_removeFormatting(t,e){for(let n=t.firstChild,i;n;n=i){if(i=n.nextSibling,N(n)){if(n instanceof Text||n.nodeName==="BR"||n.nodeName==="IMG"){e.appendChild(n);continue}}else if(P(n)){e.appendChild(this.createDefaultBlock([this._removeFormatting(n,document.createDocumentFragment())]));continue}this._removeFormatting(n,e)}return e}removeAllFormatting(t){if(t||(t=this.getSelection()),t.collapsed)return this.focus();let e=this._root,n=t.commonAncestorContainer;for(;n&&!P(n);)n=n.parentNode;if(n||(ye(t,e),n=e),n instanceof Text)return this.focus();this.saveUndoState(t),q(t,n,n,e);let i=t.startContainer,s=t.startOffset,r=t.endContainer,l=t.endOffset,a=document.createDocumentFragment(),d=document.createDocumentFragment(),c=w(r,l,n,e),f=w(i,s,n,e),u;for(;f!==c;)u=f.nextSibling,a.appendChild(f),f=u;if(this._removeFormatting(a,d),d.normalize(),f=d.firstChild,u=d.lastChild,f){n.insertBefore(d,c);let m=Array.from(n.childNodes);s=m.indexOf(f),l=u?m.indexOf(u)+1:0}else c&&(s=Array.from(n.childNodes).indexOf(c),l=s);return t.setStart(n,s),t.setEnd(n,l),te(n,t),_(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}};window.Squire=Ce;})();
+`),x(s),r.length===u+1?e.setStartAfter(r):e.setStart(r,u+1)),e.collapse(!0),this.setSelection(e),this._updatePath(e,!0),this._docWasChanged(),this}if(!i||t||/^T[HD]$/.test(i.nodeName))return Oe(e,"A",n),j(e,p("BR")),e.collapse(!1),this.setSelection(e),this._updatePath(e,!0),this;if((s=S(i,n,"LI"))&&(i=s),ce(i)){if(S(i,n,"UL")||S(i,n,"OL"))return this.decreaseListLevel(e),this;if(S(i,n,"BLOCKQUOTE"))return this.removeQuote(e),this}r=e.startContainer;let a=e.startOffset,d=this.tagAfterSplit[i.nodeName];l=w(r,a,i.parentNode,this._root);let c=this._config,f=null;for(d||(d=c.blockTag,f=c.blockAttributes),he(l,d,f)||(i=p(d,f),l.dir&&(i.dir=l.dir),R(l,i),i.appendChild(_(l)),l=i),re(i),Se(i),x(i);l instanceof Element;){let u=l.firstChild,m;if(l.nodeName==="A"&&(!l.textContent||l.textContent===B)){u=document.createTextNode(""),R(l,u),l=u;break}for(;u&&u instanceof Text&&!u.data&&(m=u.nextSibling,!(!m||m.nodeName==="BR"));)E(u),u=m;if(!u||u.nodeName==="BR"||u instanceof Text)break;l=u}return e=$(l,0),this.setSelection(e),this._updatePath(e,!0),this}forEachBlock(t,e,n){n||(n=this.getSelection()),e&&this.saveUndoState(n);let i=this._root,s=L(n,i),r=G(n,i);if(s&&r)do if(t(s)||s===r)break;while(s=W(s,i));return e&&(this.setSelection(n),this._updatePath(n,!0)),this}modifyBlocks(t,e){e||(e=this.getSelection()),this._recordUndoState(e,this._isInUndoState);let n=this._root;ye(e,n),q(e,n,n,n);let i=Be(e,n,n);if(!e.collapsed){let s=e.endContainer;if(s===n)e.collapse(!1);else{for(;s.parentNode!==n;)s=s.parentNode;e.setStartBefore(s),e.collapse(!0)}}return j(e,t.call(this,i)),e.endOffset<e.endContainer.childNodes.length&&F(e.endContainer.childNodes[e.endOffset],n),F(e.startContainer.childNodes[e.startOffset],n),this._getRangeAndRemoveBookmark(e),this.setSelection(e),this._updatePath(e,!0),this}setTextAlignment(t){return this.forEachBlock(e=>{let n=e.className.split(/\s+/).filter(i=>!!i&&!/^align/.test(i)).join(" ");t?(e.className=n+" align-"+t,e.style.textAlign=t):(e.className=n,e.style.textAlign="")},!0),this.focus()}setTextDirection(t){return this.forEachBlock(e=>{t?e.dir=t:e.removeAttribute("dir")},!0),this.focus()}_getListSelection(t,e){let n=t.commonAncestorContainer,i=t.startContainer,s=t.endContainer;for(;n&&n!==e&&!/^[OU]L$/.test(n.nodeName);)n=n.parentNode;if(!n||n===e)return null;for(i===n&&(i=i.childNodes[t.startOffset]),s===n&&(s=s.childNodes[t.endOffset]);i&&i.parentNode!==n;)i=i.parentNode;for(;s&&s.parentNode!==n;)s=s.parentNode;return[n,i,s]}increaseListLevel(t){t||(t=this.getSelection());let e=this._root,n=this._getListSelection(t,e);if(!n)return this.focus();let[i,s,r]=n;if(!s||s===i.firstChild)return this.focus();this._recordUndoState(t,this._isInUndoState);let l=i.nodeName,a=s.previousSibling,d,c;a.nodeName!==l&&(d=this._config.tagAttributes[l.toLowerCase()],a=p(l,d),i.insertBefore(a,s));do c=s===r?null:s.nextSibling,a.appendChild(s);while(s=c);return c=a.nextSibling,c&&F(c,e),this._getRangeAndRemoveBookmark(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}decreaseListLevel(t){t||(t=this.getSelection());let e=this._root,n=this._getListSelection(t,e);if(!n)return this.focus();let[i,s,r]=n;s||(s=i.firstChild),r||(r=i.lastChild),this._recordUndoState(t,this._isInUndoState);let l,a=null;if(s){let d=i.parentNode;if(a=r.nextSibling?w(i,r.nextSibling,d,e):i.nextSibling,d!==e&&d.nodeName==="LI"){for(d=d.parentNode;a;)l=a.nextSibling,r.appendChild(a),a=l;a=i.parentNode.nextSibling}let c=!/^[OU]L$/.test(d.nodeName);do l=s===r?null:s.nextSibling,i.removeChild(s),c&&s.nodeName==="LI"&&(s=this.createDefaultBlock([_(s)])),d.insertBefore(s,a);while(s=l)}return i.firstChild||E(i),a&&F(a,e),this._getRangeAndRemoveBookmark(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}_makeList(t,e){let n=ae(t,this._root),i=this._config.tagAttributes,s=i[e.toLowerCase()],r=i.li,l;for(;l=n.nextNode();)if(l.parentNode instanceof HTMLLIElement&&(l=l.parentNode,n.currentNode=l.lastChild),l instanceof HTMLLIElement){l=l.parentNode;let a=l.nodeName;a!==e&&/^[OU]L$/.test(a)&&R(l,p(e,s,[_(l)]))}else{let a=p("LI",r);l.dir&&(a.dir=l.dir);let d=l.previousSibling;d&&d.nodeName===e?(d.appendChild(a),E(l)):R(l,p(e,s,[a])),a.appendChild(_(l)),n.currentNode=a}return t}makeUnorderedList(){return this.modifyBlocks(t=>this._makeList(t,"UL")),this.focus()}makeOrderedList(){return this.modifyBlocks(t=>this._makeList(t,"OL")),this.focus()}removeList(){return this.modifyBlocks(t=>{let e=t.querySelectorAll("UL, OL"),n=t.querySelectorAll("LI"),i=this._root;for(let s=0,r=e.length;s<r;s+=1){let l=e[s],a=_(l);D(a,i),R(l,a)}for(let s=0,r=n.length;s<r;s+=1){let l=n[s];P(l)?R(l,this.createDefaultBlock([_(l)])):(D(l,i),R(l,_(l)))}return t}),this.focus()}increaseQuoteLevel(t){return this.modifyBlocks(e=>p("BLOCKQUOTE",this._config.tagAttributes.blockquote,[e]),t),this.focus()}decreaseQuoteLevel(t){return this.modifyBlocks(e=>(Array.from(e.querySelectorAll("blockquote")).filter(n=>!S(n.parentNode,e,"BLOCKQUOTE")).forEach(n=>{R(n,_(n))}),e),t),this.focus()}removeQuote(t){return this.modifyBlocks(()=>this.createDefaultBlock([p("INPUT",{id:this.startSelectionId,type:"hidden"}),p("INPUT",{id:this.endSelectionId,type:"hidden"})]),t),this.focus()}code(){let t=this.getSelection();return t.collapsed||Q(t.commonAncestorContainer)?(this.modifyBlocks(e=>{let n=this._root,i=document.createDocumentFragment(),s=ae(e,n),r;for(;r=s.nextNode();){let a=r.querySelectorAll("BR"),d=[],c=a.length;for(let f=0;f<c;f+=1)d[f]=ee(a[f],!1);for(;c--;){let f=a[c];d[c]?R(f,document.createTextNode(`
+`)):E(f)}for(a=r.querySelectorAll("CODE"),c=a.length;c--;)R(a[c],_(a[c]));i.childNodes.length&&i.appendChild(document.createTextNode(`
+`)),i.appendChild(_(r))}let l=new C(i,4);for(;r=l.nextNode();)r.data=r.data.replace(/ /g," ");return i.normalize(),x(p("PRE",this._config.tagAttributes.pre,[i]))},t),this.focus()):this.changeFormat({tag:"CODE",attributes:this._config.tagAttributes.code},null,t),this}removeCode(){let t=this.getSelection(),e=t.commonAncestorContainer;return S(e,this._root,"PRE")?(this.modifyBlocks(i=>{let s=this._root,r=i.querySelectorAll("PRE"),l=r.length;for(;l--;){let a=r[l],d=new C(a,4),c;for(;c=d.nextNode();){let f=c.data;f=f.replace(/ (?= )/g,"\xA0");let u=document.createDocumentFragment(),m;for(;(m=f.indexOf(`
+`))>-1;)u.appendChild(document.createTextNode(f.slice(0,m))),u.appendChild(p("BR")),f=f.slice(m+1);c.parentNode.insertBefore(u,c),c.data=f}D(a,s),R(a,_(a))}return i},t),this.focus()):this.changeFormat(null,{tag:"CODE"},t),this}toggleCode(){return this.hasFormat("PRE")||this.hasFormat("CODE")?this.removeCode():this.code(),this}_removeFormatting(t,e){for(let n=t.firstChild,i;n;n=i){if(i=n.nextSibling,N(n)){if(n instanceof Text||n.nodeName==="BR"||n.nodeName==="IMG"){e.appendChild(n);continue}}else if(P(n)){e.appendChild(this.createDefaultBlock([this._removeFormatting(n,document.createDocumentFragment())]));continue}this._removeFormatting(n,e)}return e}removeAllFormatting(t){if(t||(t=this.getSelection()),t.collapsed)return this.focus();let e=this._root,n=t.commonAncestorContainer;for(;n&&!P(n);)n=n.parentNode;if(n||(ye(t,e),n=e),n instanceof Text)return this.focus();this.saveUndoState(t),q(t,n,n,e);let i=t.startContainer,s=t.startOffset,r=t.endContainer,l=t.endOffset,a=document.createDocumentFragment(),d=document.createDocumentFragment(),c=w(r,l,n,e),f=w(i,s,n,e),u;for(;f!==c;)u=f.nextSibling,a.appendChild(f),f=u;if(this._removeFormatting(a,d),d.normalize(),f=d.firstChild,u=d.lastChild,f){n.insertBefore(d,c);let m=Array.from(n.childNodes);s=m.indexOf(f),l=u?m.indexOf(u)+1:0}else c&&(s=Array.from(n.childNodes).indexOf(c),l=s);return t.setStart(n,s),t.setEnd(n,l),te(n,t),T(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}};window.Squire=Ce;})();
 //# sourceMappingURL=squire.js.map
diff --git a/dist/squire.js.map b/dist/squire.js.map
index 5ed469e..b92b802 100644
--- a/dist/squire.js.map
+++ b/dist/squire.js.map
@@ -1,7 +1,7 @@
 {
   "version": 3,
   "sources": ["../source/node/TreeIterator.ts", "../source/Constants.ts", "../source/node/Category.ts", "../source/node/Node.ts", "../source/node/Whitespace.ts", "../source/range/Boundaries.ts", "../source/node/MergeSplit.ts", "../source/Clean.ts", "../source/node/Block.ts", "../source/range/Block.ts", "../source/range/InsertDelete.ts", "../source/range/Contents.ts", "../source/Clipboard.ts", "../source/keyboard/Enter.ts", "../source/keyboard/KeyHelpers.ts", "../source/keyboard/Backspace.ts", "../source/keyboard/Delete.ts", "../source/keyboard/Tab.ts", "../source/keyboard/Space.ts", "../source/keyboard/KeyHandlers.ts", "../source/Editor.ts", "../source/Legacy.ts"],
-  "sourcesContent": ["type NODE_TYPE = 1 | 4 | 5;\nconst SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;\nconst SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;\nconst SHOW_ELEMENT_OR_TEXT = 5; // SHOW_ELEMENT|SHOW_TEXT;\n\nconst always = (): true => true;\n\nclass TreeIterator<T extends Node> {\n    root: Node;\n    currentNode: Node;\n    nodeType: NODE_TYPE;\n    filter: (n: T) => boolean;\n\n    constructor(root: Node, nodeType: NODE_TYPE, filter?: (n: T) => boolean) {\n        this.root = root;\n        this.currentNode = root;\n        this.nodeType = nodeType;\n        this.filter = filter || always;\n    }\n\n    isAcceptableNode(node: Node): boolean {\n        const nodeType = node.nodeType;\n        const nodeFilterType =\n            nodeType === Node.ELEMENT_NODE\n                ? SHOW_ELEMENT\n                : nodeType === Node.TEXT_NODE\n                ? SHOW_TEXT\n                : 0;\n        return !!(nodeFilterType & this.nodeType) && this.filter(node as T);\n    }\n\n    nextNode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            node = current.firstChild;\n            while (!node && current) {\n                if (current === root) {\n                    break;\n                }\n                node = current.nextSibling;\n                if (!node) {\n                    current = current.parentNode;\n                }\n            }\n            if (!node) {\n                return null;\n            }\n\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n\n    previousNode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            if (current === root) {\n                return null;\n            }\n            node = current.previousSibling;\n            if (node) {\n                while ((current = node.lastChild)) {\n                    node = current;\n                }\n            } else {\n                node = current.parentNode;\n            }\n            if (!node) {\n                return null;\n            }\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n\n    // Previous node in post-order.\n    previousPONode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            node = current.lastChild;\n            while (!node && current) {\n                if (current === root) {\n                    break;\n                }\n                node = current.previousSibling;\n                if (!node) {\n                    current = current.parentNode;\n                }\n            }\n            if (!node) {\n                return null;\n            }\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n}\n\n// ---\n\nexport { TreeIterator, SHOW_ELEMENT, SHOW_TEXT, SHOW_ELEMENT_OR_TEXT };\n", "const DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING\nconst ELEMENT_NODE = 1; // Node.ELEMENT_NODE;\nconst TEXT_NODE = 3; // Node.TEXT_NODE;\nconst DOCUMENT_NODE = 9; // Node.DOCUMENT_NODE;\nconst DOCUMENT_FRAGMENT_NODE = 11; // Node.DOCUMENT_FRAGMENT_NODE;\n\nconst ZWS = '\\u200B';\n\nconst ua = navigator.userAgent;\n\nconst isMac = /Mac OS X/.test(ua);\nconst isWin = /Windows NT/.test(ua);\nconst isIOS =\n    /iP(?:ad|hone|od)/.test(ua) || (isMac && !!navigator.maxTouchPoints);\nconst isAndroid = /Android/.test(ua);\n\nconst isGecko = /Gecko\\//.test(ua);\nconst isLegacyEdge = /Edge\\//.test(ua);\nconst isWebKit = !isLegacyEdge && /WebKit\\//.test(ua);\n\nconst ctrlKey = isMac || isIOS ? 'Meta-' : 'Ctrl-';\n\nconst cantFocusEmptyTextNodes = isWebKit;\n\nconst supportsInputEvents =\n    'onbeforeinput' in document && 'inputType' in new InputEvent('input');\n\n// Use [^ \\t\\r\\n] instead of \\S so that nbsp does not count as white-space\nconst notWS = /[^ \\t\\r\\n]/;\n\n// ---\n\nexport {\n    DOCUMENT_POSITION_PRECEDING,\n    ELEMENT_NODE,\n    TEXT_NODE,\n    DOCUMENT_NODE,\n    DOCUMENT_FRAGMENT_NODE,\n    notWS,\n    ZWS,\n    ua,\n    isMac,\n    isWin,\n    isIOS,\n    isAndroid,\n    isGecko,\n    isLegacyEdge,\n    isWebKit,\n    ctrlKey,\n    cantFocusEmptyTextNodes,\n    supportsInputEvents,\n};\n", "import { ELEMENT_NODE, TEXT_NODE, DOCUMENT_FRAGMENT_NODE } from '../Constants';\n\n// ---\n\nconst inlineNodeNames =\n    /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/;\n\nconst leafNodeNames = new Set(['BR', 'HR', 'IFRAME', 'IMG', 'INPUT']);\n\nconst UNKNOWN = 0;\nconst INLINE = 1;\nconst BLOCK = 2;\nconst CONTAINER = 3;\n\n// ---\n\nlet cache: WeakMap<Node, number> = new WeakMap();\n\nconst resetNodeCategoryCache = (): void => {\n    cache = new WeakMap();\n};\n\n// ---\n\nconst isLeaf = (node: Node): boolean => {\n    return leafNodeNames.has(node.nodeName);\n};\n\nconst getNodeCategory = (node: Node): number => {\n    switch (node.nodeType) {\n        case TEXT_NODE:\n            return INLINE;\n        case ELEMENT_NODE:\n        case DOCUMENT_FRAGMENT_NODE:\n            if (cache.has(node)) {\n                return cache.get(node) as number;\n            }\n            break;\n        default:\n            return UNKNOWN;\n    }\n\n    let nodeCategory: number;\n    if (!Array.from(node.childNodes).every(isInline)) {\n        // Malformed HTML can have block tags inside inline tags. Need to treat\n        // these as containers rather than inline. See #239.\n        nodeCategory = CONTAINER;\n    } else if (inlineNodeNames.test(node.nodeName)) {\n        nodeCategory = INLINE;\n    } else {\n        nodeCategory = BLOCK;\n    }\n    cache.set(node, nodeCategory);\n    return nodeCategory;\n};\n\nconst isInline = (node: Node): boolean => {\n    return getNodeCategory(node) === INLINE;\n};\n\nconst isBlock = (node: Node): boolean => {\n    return getNodeCategory(node) === BLOCK;\n};\n\nconst isContainer = (node: Node): boolean => {\n    return getNodeCategory(node) === CONTAINER;\n};\n\n// ---\n\nexport {\n    getNodeCategory,\n    isBlock,\n    isContainer,\n    isInline,\n    isLeaf,\n    leafNodeNames,\n    resetNodeCategoryCache,\n};\n", "import { isLeaf } from './Category';\n\n// ---\n\nconst createElement = (\n    tag: string,\n    props?: Record<string, string> | null,\n    children?: Node[],\n): HTMLElement => {\n    const el = document.createElement(tag);\n    if (props instanceof Array) {\n        children = props;\n        props = null;\n    }\n    if (props) {\n        for (const attr in props) {\n            const value = props[attr];\n            if (value !== undefined) {\n                el.setAttribute(attr, value);\n            }\n        }\n    }\n    if (children) {\n        children.forEach((node) => el.appendChild(node));\n    }\n    return el;\n};\n\n// --- Tests\n\nconst areAlike = (\n    node: HTMLElement | Node,\n    node2: HTMLElement | Node,\n): boolean => {\n    if (isLeaf(node)) {\n        return false;\n    }\n    if (node.nodeType !== node2.nodeType || node.nodeName !== node2.nodeName) {\n        return false;\n    }\n    if (node instanceof HTMLElement && node2 instanceof HTMLElement) {\n        return (\n            node.nodeName !== 'A' &&\n            node.className === node2.className &&\n            node.style.cssText === node2.style.cssText\n        );\n    }\n    return true;\n};\n\nconst hasTagAttributes = (\n    node: Node | Element,\n    tag: string,\n    attributes?: Record<string, string> | null,\n): boolean => {\n    if (node.nodeName !== tag) {\n        return false;\n    }\n    for (const attr in attributes) {\n        if (\n            !('getAttribute' in node) ||\n            node.getAttribute(attr) !== attributes[attr]\n        ) {\n            return false;\n        }\n    }\n    return true;\n};\n\n// --- Traversal\n\nconst getNearest = (\n    node: Node | null,\n    root: Element | DocumentFragment,\n    tag: string,\n    attributes?: Record<string, string> | null,\n): Node | null => {\n    while (node && node !== root) {\n        if (hasTagAttributes(node, tag, attributes)) {\n            return node;\n        }\n        node = node.parentNode;\n    }\n    return null;\n};\n\nconst getNodeBeforeOffset = (node: Node, offset: number): Node => {\n    let children = node.childNodes;\n    while (offset && node instanceof Element) {\n        node = children[offset - 1];\n        children = node.childNodes;\n        offset = children.length;\n    }\n    return node;\n};\n\nconst getNodeAfterOffset = (node: Node, offset: number): Node | null => {\n    let returnNode: Node | null = node;\n    if (returnNode instanceof Element) {\n        const children = returnNode.childNodes;\n        if (offset < children.length) {\n            returnNode = children[offset];\n        } else {\n            while (returnNode && !returnNode.nextSibling) {\n                returnNode = returnNode.parentNode;\n            }\n            if (returnNode) {\n                returnNode = returnNode.nextSibling;\n            }\n        }\n    }\n    return returnNode;\n};\n\nconst getLength = (node: Node): number => {\n    return node instanceof Element || node instanceof DocumentFragment\n        ? node.childNodes.length\n        : node instanceof CharacterData\n        ? node.length\n        : 0;\n};\n\n// --- Manipulation\n\nconst empty = (node: Node): DocumentFragment => {\n    const frag = document.createDocumentFragment();\n    let child = node.firstChild;\n    while (child) {\n        frag.appendChild(child);\n        child = node.firstChild;\n    }\n    return frag;\n};\n\nconst detach = (node: Node): Node => {\n    const parent = node.parentNode;\n    if (parent) {\n        parent.removeChild(node);\n    }\n    return node;\n};\n\nconst replaceWith = (node: Node, node2: Node): void => {\n    const parent = node.parentNode;\n    if (parent) {\n        parent.replaceChild(node2, node);\n    }\n};\n\n// --- Export\n\nexport {\n    areAlike,\n    createElement,\n    detach,\n    empty,\n    getLength,\n    getNearest,\n    getNodeAfterOffset,\n    getNodeBeforeOffset,\n    hasTagAttributes,\n    replaceWith,\n};\n", "import { ZWS, notWS } from '../Constants';\nimport { isInline } from './Category';\nimport { getLength } from './Node';\nimport { SHOW_ELEMENT_OR_TEXT, SHOW_TEXT, TreeIterator } from './TreeIterator';\n\n// ---\n\nconst notWSTextNode = (node: Node): boolean => {\n    return node instanceof Element\n        ? node.nodeName === 'BR'\n        : // okay if data is 'undefined' here.\n          notWS.test((node as CharacterData).data);\n};\n\nconst isLineBreak = (br: Element, isLBIfEmptyBlock: boolean): boolean => {\n    let block = br.parentNode!;\n    while (isInline(block)) {\n        block = block.parentNode!;\n    }\n    const walker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        notWSTextNode,\n    );\n    walker.currentNode = br;\n    return !!walker.nextNode() || (isLBIfEmptyBlock && !walker.previousNode());\n};\n\n// --- Workaround for browsers that can't focus empty text nodes\n\n// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256\n\n// Walk down the tree starting at the root and remove any ZWS. If the node only\n// contained ZWS space then remove it too. We may want to keep one ZWS node at\n// the bottom of the tree so the block can be selected. Define that node as the\n// keepNode.\nconst removeZWS = (root: Node, keepNode?: Node | null): void => {\n    const walker = new TreeIterator<Text>(root, SHOW_TEXT);\n    let textNode: Text | null;\n    let index: number;\n    while ((textNode = walker.nextNode())) {\n        while (\n            (index = textNode.data.indexOf(ZWS)) > -1 &&\n            // eslint-disable-next-line no-unmodified-loop-condition\n            (!keepNode || textNode.parentNode !== keepNode)\n        ) {\n            if (textNode.length === 1) {\n                let node: Node = textNode;\n                let parent = node.parentNode;\n                while (parent) {\n                    parent.removeChild(node);\n                    walker.currentNode = parent;\n                    if (!isInline(parent) || getLength(parent)) {\n                        break;\n                    }\n                    node = parent;\n                    parent = node.parentNode;\n                }\n                break;\n            } else {\n                textNode.deleteData(index, 1);\n            }\n        }\n    }\n};\n\n// ---\n\nexport { isLineBreak, removeZWS };\n", "import { isLeaf } from '../node/Category';\nimport { getLength, getNearest } from '../node/Node';\nimport { isLineBreak } from '../node/Whitespace';\nimport { TEXT_NODE } from '../Constants';\n\n// ---\n\nconst START_TO_START = 0; // Range.START_TO_START\nconst START_TO_END = 1; // Range.START_TO_END\nconst END_TO_END = 2; // Range.END_TO_END\nconst END_TO_START = 3; // Range.END_TO_START\n\nconst isNodeContainedInRange = (\n    range: Range,\n    node: Node,\n    partial: boolean,\n): boolean => {\n    const nodeRange = document.createRange();\n    nodeRange.selectNode(node);\n    if (partial) {\n        // Node must not finish before range starts or start after range\n        // finishes.\n        const nodeEndBeforeStart =\n            range.compareBoundaryPoints(END_TO_START, nodeRange) > -1;\n        const nodeStartAfterEnd =\n            range.compareBoundaryPoints(START_TO_END, nodeRange) < 1;\n        return !nodeEndBeforeStart && !nodeStartAfterEnd;\n    } else {\n        // Node must start after range starts and finish before range\n        // finishes\n        const nodeStartAfterStart =\n            range.compareBoundaryPoints(START_TO_START, nodeRange) < 1;\n        const nodeEndBeforeEnd =\n            range.compareBoundaryPoints(END_TO_END, nodeRange) > -1;\n        return nodeStartAfterStart && nodeEndBeforeEnd;\n    }\n};\n\n/**\n * Moves the range to an equivalent position with the start/end as deep in\n * the tree as possible.\n */\nconst moveRangeBoundariesDownTree = (range: Range): void => {\n    let { startContainer, startOffset, endContainer, endOffset } = range;\n\n    while (!(startContainer instanceof Text)) {\n        let child: ChildNode | null = startContainer.childNodes[startOffset];\n        if (!child || isLeaf(child)) {\n            if (startOffset) {\n                child = startContainer.childNodes[startOffset - 1];\n                if (child instanceof Text) {\n                    // Need a new variable to satisfy TypeScript's type checker\n                    // for some reason.\n                    let textChild: Text = child;\n                    // If we have an empty text node next to another text node,\n                    // just skip and remove it.\n                    let prev: ChildNode | null;\n                    while (\n                        !textChild.length &&\n                        (prev = textChild.previousSibling) &&\n                        prev instanceof Text\n                    ) {\n                        textChild.remove();\n                        textChild = prev;\n                    }\n                    startContainer = textChild;\n                    startOffset = textChild.data.length;\n                }\n            }\n            break;\n        }\n        startContainer = child;\n        startOffset = 0;\n    }\n    if (endOffset) {\n        while (!(endContainer instanceof Text)) {\n            const child = endContainer.childNodes[endOffset - 1];\n            if (!child || isLeaf(child)) {\n                if (\n                    child &&\n                    child.nodeName === 'BR' &&\n                    !isLineBreak(child as Element, false)\n                ) {\n                    endOffset -= 1;\n                    continue;\n                }\n                break;\n            }\n            endContainer = child;\n            endOffset = getLength(endContainer);\n        }\n    } else {\n        while (!(endContainer instanceof Text)) {\n            const child = endContainer.firstChild!;\n            if (!child || isLeaf(child)) {\n                break;\n            }\n            endContainer = child;\n        }\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\nconst moveRangeBoundariesUpTree = (\n    range: Range,\n    startMax: Node,\n    endMax: Node,\n    root: Node,\n): void => {\n    let startContainer = range.startContainer;\n    let startOffset = range.startOffset;\n    let endContainer = range.endContainer;\n    let endOffset = range.endOffset;\n    let parent: Node;\n\n    if (!startMax) {\n        startMax = range.commonAncestorContainer;\n    }\n    if (!endMax) {\n        endMax = startMax;\n    }\n\n    while (\n        !startOffset &&\n        startContainer !== startMax &&\n        startContainer !== root\n    ) {\n        parent = startContainer.parentNode!;\n        startOffset = Array.from(parent.childNodes).indexOf(\n            startContainer as ChildNode,\n        );\n        startContainer = parent;\n    }\n\n    while (true) {\n        if (endContainer === endMax || endContainer === root) {\n            break;\n        }\n        if (\n            endContainer.nodeType !== TEXT_NODE &&\n            endContainer.childNodes[endOffset] &&\n            endContainer.childNodes[endOffset].nodeName === 'BR' &&\n            !isLineBreak(endContainer.childNodes[endOffset] as Element, false)\n        ) {\n            endOffset += 1;\n        }\n        if (endOffset !== getLength(endContainer)) {\n            break;\n        }\n        parent = endContainer.parentNode!;\n        endOffset =\n            Array.from(parent.childNodes).indexOf(endContainer as ChildNode) +\n            1;\n        endContainer = parent;\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\nconst moveRangeBoundaryOutOf = (\n    range: Range,\n    tag: string,\n    root: Element,\n): Range => {\n    let parent = getNearest(range.endContainer, root, tag);\n    if (parent && (parent = parent.parentNode)) {\n        const clone = range.cloneRange();\n        moveRangeBoundariesUpTree(clone, parent, parent, root);\n        if (clone.endContainer === parent) {\n            range.setStart(clone.endContainer, clone.endOffset);\n            range.setEnd(clone.endContainer, clone.endOffset);\n        }\n    }\n    return range;\n};\n\n// ---\n\nexport {\n    isNodeContainedInRange,\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n    moveRangeBoundaryOutOf,\n};\n", "import { ZWS, cantFocusEmptyTextNodes } from '../Constants';\nimport {\n    createElement,\n    getNearest,\n    areAlike,\n    getLength,\n    detach,\n    empty,\n} from './Node';\nimport { isInline, isContainer } from './Category';\n\n// ---\n\nconst fixCursor = (node: Node): Node => {\n    // In Webkit and Gecko, block level elements are collapsed and\n    // unfocusable if they have no content. To remedy this, a <BR> must be\n    // inserted. In Opera and IE, we just need a textnode in order for the\n    // cursor to appear.\n    let fixer: Element | Text | null = null;\n\n    if (node instanceof Text) {\n        return node;\n    }\n\n    if (isInline(node)) {\n        let child = node.firstChild;\n        if (cantFocusEmptyTextNodes) {\n            while (child && child instanceof Text && !child.data) {\n                node.removeChild(child);\n                child = node.firstChild;\n            }\n        }\n        if (!child) {\n            if (cantFocusEmptyTextNodes) {\n                fixer = document.createTextNode(ZWS);\n            } else {\n                fixer = document.createTextNode('');\n            }\n        }\n    } else if (\n        (node instanceof Element || node instanceof DocumentFragment) &&\n        !node.querySelector('BR')\n    ) {\n        fixer = createElement('BR');\n        let parent: Element | DocumentFragment = node;\n        let child: Element | null;\n        while ((child = parent.lastElementChild) && !isInline(child)) {\n            parent = child;\n        }\n        node = parent;\n    }\n    if (fixer) {\n        try {\n            node.appendChild(fixer);\n        } catch (error) {}\n    }\n\n    return node;\n};\n\n// Recursively examine container nodes and wrap any inline children.\nconst fixContainer = (\n    container: Node,\n    root: Element | DocumentFragment,\n): Node => {\n    let wrapper: HTMLElement | null = null;\n    Array.from(container.childNodes).forEach((child) => {\n        const isBR = child.nodeName === 'BR';\n        if (!isBR && isInline(child)) {\n            if (!wrapper) {\n                wrapper = createElement('DIV');\n            }\n            wrapper.appendChild(child);\n        } else if (isBR || wrapper) {\n            if (!wrapper) {\n                wrapper = createElement('DIV');\n            }\n            fixCursor(wrapper);\n            if (isBR) {\n                container.replaceChild(wrapper, child);\n            } else {\n                container.insertBefore(wrapper, child);\n            }\n            wrapper = null;\n        }\n        if (isContainer(child)) {\n            fixContainer(child, root);\n        }\n    });\n    if (wrapper) {\n        container.appendChild(fixCursor(wrapper));\n    }\n    return container;\n};\n\nconst split = (\n    node: Node,\n    offset: number | Node | null,\n    stopNode: Node,\n    root: Element | DocumentFragment,\n): Node | null => {\n    if (node instanceof Text && node !== stopNode) {\n        if (typeof offset !== 'number') {\n            throw new Error('Offset must be a number to split text node!');\n        }\n        if (!node.parentNode) {\n            throw new Error('Cannot split text node with no parent!');\n        }\n        return split(node.parentNode, node.splitText(offset), stopNode, root);\n    }\n\n    let nodeAfterSplit: Node | null =\n        typeof offset === 'number'\n            ? offset < node.childNodes.length\n                ? node.childNodes[offset]\n                : null\n            : offset;\n    const parent = node.parentNode;\n    if (!parent || node === stopNode || !(node instanceof Element)) {\n        return nodeAfterSplit;\n    }\n\n    // Clone node without children\n    const clone = node.cloneNode(false) as Element;\n\n    // Add right-hand siblings to the clone\n    while (nodeAfterSplit) {\n        const next = nodeAfterSplit.nextSibling;\n        clone.appendChild(nodeAfterSplit);\n        nodeAfterSplit = next;\n    }\n\n    // Maintain li numbering if inside a quote.\n    if (\n        node instanceof HTMLOListElement &&\n        getNearest(node, root, 'BLOCKQUOTE')\n    ) {\n        (clone as HTMLOListElement).start =\n            (+node.start || 1) + node.childNodes.length - 1;\n    }\n\n    // DO NOT NORMALISE. This may undo the fixCursor() call\n    // of a node lower down the tree!\n    // We need something in the element in order for the cursor to appear.\n    fixCursor(node);\n    fixCursor(clone);\n\n    // Inject clone after original node\n    parent.insertBefore(clone, node.nextSibling);\n\n    // Keep on splitting up the tree\n    return split(parent, clone, stopNode, root);\n};\n\nconst _mergeInlines = (\n    node: Node,\n    fakeRange: {\n        startContainer: Node;\n        startOffset: number;\n        endContainer: Node;\n        endOffset: number;\n    },\n): void => {\n    const children = node.childNodes;\n    let l = children.length;\n    const frags: DocumentFragment[] = [];\n    while (l--) {\n        const child = children[l];\n        const prev = l ? children[l - 1] : null;\n        if (prev && isInline(child) && areAlike(child, prev)) {\n            if (fakeRange.startContainer === child) {\n                fakeRange.startContainer = prev;\n                fakeRange.startOffset += getLength(prev);\n            }\n            if (fakeRange.endContainer === child) {\n                fakeRange.endContainer = prev;\n                fakeRange.endOffset += getLength(prev);\n            }\n            if (fakeRange.startContainer === node) {\n                if (fakeRange.startOffset > l) {\n                    fakeRange.startOffset -= 1;\n                } else if (fakeRange.startOffset === l) {\n                    fakeRange.startContainer = prev;\n                    fakeRange.startOffset = getLength(prev);\n                }\n            }\n            if (fakeRange.endContainer === node) {\n                if (fakeRange.endOffset > l) {\n                    fakeRange.endOffset -= 1;\n                } else if (fakeRange.endOffset === l) {\n                    fakeRange.endContainer = prev;\n                    fakeRange.endOffset = getLength(prev);\n                }\n            }\n            detach(child);\n            if (child instanceof Text) {\n                (prev as Text).appendData(child.data);\n            } else {\n                frags.push(empty(child));\n            }\n        } else if (child instanceof Element) {\n            let frag: DocumentFragment | undefined;\n            while ((frag = frags.pop())) {\n                child.appendChild(frag);\n            }\n            _mergeInlines(child, fakeRange);\n        }\n    }\n};\n\nconst mergeInlines = (node: Node, range: Range): void => {\n    const element = node instanceof Text ? node.parentNode : node;\n    if (element instanceof Element) {\n        const fakeRange = {\n            startContainer: range.startContainer,\n            startOffset: range.startOffset,\n            endContainer: range.endContainer,\n            endOffset: range.endOffset,\n        };\n        _mergeInlines(element, fakeRange);\n        range.setStart(fakeRange.startContainer, fakeRange.startOffset);\n        range.setEnd(fakeRange.endContainer, fakeRange.endOffset);\n    }\n};\n\nconst mergeWithBlock = (\n    block: Node,\n    next: Node,\n    range: Range,\n    root: Element,\n): void => {\n    let container = next;\n    let parent: Node | null;\n    let offset: number;\n    while (\n        (parent = container.parentNode) &&\n        parent !== root &&\n        parent instanceof Element &&\n        parent.childNodes.length === 1\n    ) {\n        container = parent;\n    }\n    detach(container);\n\n    offset = block.childNodes.length;\n\n    // Remove extra <BR> fixer if present.\n    const last = block.lastChild;\n    if (last && last.nodeName === 'BR') {\n        block.removeChild(last);\n        offset -= 1;\n    }\n\n    block.appendChild(empty(next));\n\n    range.setStart(block, offset);\n    range.collapse(true);\n    mergeInlines(block, range);\n};\n\nconst mergeContainers = (node: Node, root: Element): void => {\n    const prev = node.previousSibling;\n    const first = node.firstChild;\n    const isListItem = node.nodeName === 'LI';\n\n    // Do not merge LIs, unless it only contains a UL\n    if (isListItem && (!first || !/^[OU]L$/.test(first.nodeName))) {\n        return;\n    }\n\n    if (prev && areAlike(prev, node)) {\n        if (!isContainer(prev)) {\n            if (isListItem) {\n                const block = createElement('DIV');\n                block.appendChild(empty(prev));\n                prev.appendChild(block);\n            } else {\n                return;\n            }\n        }\n        detach(node);\n        const needsFix = !isContainer(node);\n        prev.appendChild(empty(node));\n        if (needsFix) {\n            fixContainer(prev, root);\n        }\n        if (first) {\n            mergeContainers(first, root);\n        }\n    } else if (isListItem) {\n        const block = createElement('DIV');\n        node.insertBefore(block, first);\n        fixCursor(block);\n    }\n};\n\n// ---\n\nexport {\n    fixContainer,\n    fixCursor,\n    mergeContainers,\n    mergeInlines,\n    mergeWithBlock,\n    split,\n};\n", "import { notWS } from './Constants';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from './node/TreeIterator';\nimport { createElement, empty, detach, replaceWith } from './node/Node';\nimport { isInline, isLeaf } from './node/Category';\nimport { fixContainer } from './node/MergeSplit';\nimport { isLineBreak } from './node/Whitespace';\n\nimport type { SquireConfig } from './Editor';\n\n// ---\n\ntype StyleRewriter = (\n    node: HTMLElement,\n    parent: Node,\n    config: SquireConfig,\n) => HTMLElement;\n\n// ---\n\nconst styleToSemantic: Record<\n    string,\n    { regexp: RegExp; replace: (x: any, y: string) => HTMLElement }\n> = {\n    'font-weight': {\n        regexp: /^bold|^700/i,\n        replace(): HTMLElement {\n            return createElement('B');\n        },\n    },\n    'font-style': {\n        regexp: /^italic/i,\n        replace(): HTMLElement {\n            return createElement('I');\n        },\n    },\n    'font-family': {\n        regexp: notWS,\n        replace(\n            classNames: { fontFamily: string },\n            family: string,\n        ): HTMLElement {\n            return createElement('SPAN', {\n                class: classNames.fontFamily,\n                style: 'font-family:' + family,\n            });\n        },\n    },\n    'font-size': {\n        regexp: notWS,\n        replace(classNames: { fontSize: string }, size: string): HTMLElement {\n            return createElement('SPAN', {\n                class: classNames.fontSize,\n                style: 'font-size:' + size,\n            });\n        },\n    },\n    'text-decoration': {\n        regexp: /^underline/i,\n        replace(): HTMLElement {\n            return createElement('U');\n        },\n    },\n};\n\nconst replaceStyles = (\n    node: HTMLElement,\n    _: Node,\n    config: SquireConfig,\n): HTMLElement => {\n    const style = node.style;\n    let newTreeBottom: HTMLElement | undefined;\n    let newTreeTop: HTMLElement | undefined;\n\n    for (const attr in styleToSemantic) {\n        const converter = styleToSemantic[attr];\n        const css = style.getPropertyValue(attr);\n        if (css && converter.regexp.test(css)) {\n            const el = converter.replace(config.classNames, css);\n            if (\n                el.nodeName === node.nodeName &&\n                el.className === node.className\n            ) {\n                continue;\n            }\n            if (!newTreeTop) {\n                newTreeTop = el;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(el);\n            }\n            newTreeBottom = el;\n            node.style.removeProperty(attr);\n        }\n    }\n\n    if (newTreeTop && newTreeBottom) {\n        newTreeBottom.appendChild(empty(node));\n        if (node.style.cssText) {\n            node.appendChild(newTreeTop);\n        } else {\n            replaceWith(node, newTreeTop);\n        }\n    }\n\n    return newTreeBottom || node;\n};\n\nconst replaceWithTag = (tag: string) => {\n    return (node: HTMLElement, parent: Node) => {\n        const el = createElement(tag);\n        const attributes = node.attributes;\n        for (let i = 0, l = attributes.length; i < l; i += 1) {\n            const attribute = attributes[i];\n            el.setAttribute(attribute.name, attribute.value);\n        }\n        parent.replaceChild(el, node);\n        el.appendChild(empty(node));\n        return el;\n    };\n};\n\nconst fontSizes: Record<string, string> = {\n    '1': '10',\n    '2': '13',\n    '3': '16',\n    '4': '18',\n    '5': '24',\n    '6': '32',\n    '7': '48',\n};\n\nconst stylesRewriters: Record<string, StyleRewriter> = {\n    STRONG: replaceWithTag('B'),\n    EM: replaceWithTag('I'),\n    INS: replaceWithTag('U'),\n    STRIKE: replaceWithTag('S'),\n    SPAN: replaceStyles,\n    FONT: (\n        node: HTMLElement,\n        parent: Node,\n        config: SquireConfig,\n    ): HTMLElement => {\n        const font = node as HTMLFontElement;\n        const face = font.face;\n        const size = font.size;\n        let color = font.color;\n        const classNames = config.classNames;\n        let fontSpan: HTMLElement;\n        let sizeSpan: HTMLElement;\n        let colorSpan: HTMLElement;\n        let newTreeBottom: HTMLElement | undefined;\n        let newTreeTop: HTMLElement | undefined;\n        if (face) {\n            fontSpan = createElement('SPAN', {\n                class: classNames.fontFamily,\n                style: 'font-family:' + face,\n            });\n            newTreeTop = fontSpan;\n            newTreeBottom = fontSpan;\n        }\n        if (size) {\n            sizeSpan = createElement('SPAN', {\n                class: classNames.fontSize,\n                style: 'font-size:' + fontSizes[size] + 'px',\n            });\n            if (!newTreeTop) {\n                newTreeTop = sizeSpan;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(sizeSpan);\n            }\n            newTreeBottom = sizeSpan;\n        }\n        if (color && /^#?([\\dA-F]{3}){1,2}$/i.test(color)) {\n            if (color.charAt(0) !== '#') {\n                color = '#' + color;\n            }\n            colorSpan = createElement('SPAN', {\n                class: classNames.color,\n                style: 'color:' + color,\n            });\n            if (!newTreeTop) {\n                newTreeTop = colorSpan;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(colorSpan);\n            }\n            newTreeBottom = colorSpan;\n        }\n        if (!newTreeTop || !newTreeBottom) {\n            newTreeTop = newTreeBottom = createElement('SPAN');\n        }\n        parent.replaceChild(newTreeTop, font);\n        newTreeBottom.appendChild(empty(font));\n        return newTreeBottom;\n    },\n    TT: (node: Node, parent: Node, config: SquireConfig): HTMLElement => {\n        const el = createElement('SPAN', {\n            class: config.classNames.fontFamily,\n            style: 'font-family:menlo,consolas,\"courier new\",monospace',\n        });\n        parent.replaceChild(el, node);\n        el.appendChild(empty(node));\n        return el;\n    },\n};\n\nconst allowedBlock =\n    /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/;\n\nconst blacklist = /^(?:HEAD|META|STYLE)/;\n\n/*\n    Two purposes:\n\n    1. Remove nodes we don't want, such as weird <o:p> tags, comment nodes\n       and whitespace nodes.\n    2. Convert inline tags into our preferred format.\n*/\nconst cleanTree = (\n    node: Node,\n    config: SquireConfig,\n    preserveWS?: boolean,\n): Node => {\n    const children = node.childNodes;\n\n    let nonInlineParent = node;\n    while (isInline(nonInlineParent)) {\n        nonInlineParent = nonInlineParent.parentNode!;\n    }\n    const walker = new TreeIterator<Element | Text>(\n        nonInlineParent,\n        SHOW_ELEMENT_OR_TEXT,\n    );\n\n    for (let i = 0, l = children.length; i < l; i += 1) {\n        let child = children[i];\n        const nodeName = child.nodeName;\n        const rewriter = stylesRewriters[nodeName];\n        if (child instanceof HTMLElement) {\n            const childLength = child.childNodes.length;\n            if (rewriter) {\n                child = rewriter(child, node, config);\n            } else if (blacklist.test(nodeName)) {\n                node.removeChild(child);\n                i -= 1;\n                l -= 1;\n                continue;\n            } else if (!allowedBlock.test(nodeName) && !isInline(child)) {\n                i -= 1;\n                l += childLength - 1;\n                node.replaceChild(empty(child), child);\n                continue;\n            }\n            if (childLength) {\n                cleanTree(child, config, preserveWS || nodeName === 'PRE');\n            }\n        } else {\n            if (child instanceof Text) {\n                let data = child.data;\n                const startsWithWS = !notWS.test(data.charAt(0));\n                const endsWithWS = !notWS.test(data.charAt(data.length - 1));\n                if (preserveWS || (!startsWithWS && !endsWithWS)) {\n                    continue;\n                }\n                // Iterate through the nodes; if we hit some other content\n                // before the start of a new block we don't trim\n                if (startsWithWS) {\n                    walker.currentNode = child;\n                    let sibling;\n                    while ((sibling = walker.previousPONode())) {\n                        if (\n                            sibling.nodeName === 'IMG' ||\n                            (sibling instanceof Text &&\n                                notWS.test(sibling.data))\n                        ) {\n                            break;\n                        }\n                        if (!isInline(sibling)) {\n                            sibling = null;\n                            break;\n                        }\n                    }\n                    data = data.replace(/^[ \\t\\r\\n]+/g, sibling ? ' ' : '');\n                }\n                if (endsWithWS) {\n                    walker.currentNode = child;\n                    let sibling;\n                    while ((sibling = walker.nextNode())) {\n                        if (\n                            sibling.nodeName === 'IMG' ||\n                            (sibling instanceof Text &&\n                                notWS.test(sibling.data))\n                        ) {\n                            break;\n                        }\n                        if (!isInline(sibling)) {\n                            sibling = null;\n                            break;\n                        }\n                    }\n                    data = data.replace(/[ \\t\\r\\n]+$/g, sibling ? ' ' : '');\n                }\n                if (data) {\n                    child.data = data;\n                    continue;\n                }\n            }\n            node.removeChild(child);\n            i -= 1;\n            l -= 1;\n        }\n    }\n    return node;\n};\n\n// ---\n\nconst removeEmptyInlines = (node: Node): void => {\n    const children = node.childNodes;\n    let l = children.length;\n    while (l--) {\n        const child = children[l];\n        if (child instanceof Element && !isLeaf(child)) {\n            removeEmptyInlines(child);\n            if (isInline(child) && !child.firstChild) {\n                node.removeChild(child);\n            }\n        } else if (child instanceof Text && !child.data) {\n            node.removeChild(child);\n        }\n    }\n};\n\n// ---\n\n// <br> elements are treated specially, and differently depending on the\n// browser, when in rich text editor mode. When adding HTML from external\n// sources, we must remove them, replacing the ones that actually affect\n// line breaks by wrapping the inline text in a <div>. Browsers that want <br>\n// elements at the end of each block will then have them added back in a later\n// fixCursor method call.\nconst cleanupBRs = (\n    node: Element | DocumentFragment,\n    root: Element,\n    keepForBlankLine: boolean,\n): void => {\n    const brs: NodeListOf<HTMLBRElement> = node.querySelectorAll('BR');\n    const brBreaksLine: boolean[] = [];\n    let l = brs.length;\n\n    // Must calculate whether the <br> breaks a line first, because if we\n    // have two <br>s next to each other, after the first one is converted\n    // to a block split, the second will be at the end of a block and\n    // therefore seem to not be a line break. But in its original context it\n    // was, so we should also convert it to a block split.\n    for (let i = 0; i < l; i += 1) {\n        brBreaksLine[i] = isLineBreak(brs[i], keepForBlankLine);\n    }\n    while (l--) {\n        const br = brs[l];\n        // Cleanup may have removed it\n        const parent = br.parentNode;\n        if (!parent) {\n            continue;\n        }\n        // If it doesn't break a line, just remove it; it's not doing\n        // anything useful. We'll add it back later if required by the\n        // browser. If it breaks a line, wrap the content in div tags\n        // and replace the brs.\n        if (!brBreaksLine[l]) {\n            detach(br);\n        } else if (!isInline(parent)) {\n            fixContainer(parent, root);\n        }\n    }\n};\n\n// ---\n\nconst escapeHTML = (text: string): string => {\n    return text\n        .split('&')\n        .join('&amp;')\n        .split('<')\n        .join('&lt;')\n        .split('>')\n        .join('&gt;')\n        .split('\"')\n        .join('&quot;');\n};\n\n// ---\n\nexport { cleanTree, cleanupBRs, isLineBreak, removeEmptyInlines, escapeHTML };\n", "import { TreeIterator, SHOW_ELEMENT } from './TreeIterator';\nimport { isBlock } from './Category';\n\n// ---\n\nconst getBlockWalker = (\n    node: Node,\n    root: Element | DocumentFragment,\n): TreeIterator<HTMLElement> => {\n    const walker = new TreeIterator<HTMLElement>(root, SHOW_ELEMENT, isBlock);\n    walker.currentNode = node;\n    return walker;\n};\n\nconst getPreviousBlock = (\n    node: Node,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const block = getBlockWalker(node, root).previousNode();\n    return block !== root ? block : null;\n};\n\nconst getNextBlock = (\n    node: Node,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const block = getBlockWalker(node, root).nextNode();\n    return block !== root ? block : null;\n};\n\nconst isEmptyBlock = (block: Element): boolean => {\n    return !block.textContent && !block.querySelector('IMG');\n};\n\n// ---\n\nexport { getBlockWalker, getPreviousBlock, getNextBlock, isEmptyBlock };\n", "import { isInline, isBlock } from '../node/Category';\nimport { getPreviousBlock, getNextBlock } from '../node/Block';\nimport { getNodeBeforeOffset, getNodeAfterOffset } from '../node/Node';\nimport { ZWS, notWS } from '../Constants';\nimport { isNodeContainedInRange } from './Boundaries';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from '../node/TreeIterator';\n\n// ---\n\n// Returns the first block at least partially contained by the range,\n// or null if no block is contained by the range.\nconst getStartBlockOfRange = (\n    range: Range,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const container = range.startContainer;\n    let block: HTMLElement | null;\n\n    // If inline, get the containing block.\n    if (isInline(container)) {\n        block = getPreviousBlock(container, root);\n    } else if (\n        container !== root &&\n        container instanceof HTMLElement &&\n        isBlock(container)\n    ) {\n        block = container;\n    } else {\n        const node = getNodeBeforeOffset(container, range.startOffset);\n        block = getNextBlock(node, root);\n    }\n    // Check the block actually intersects the range\n    return block && isNodeContainedInRange(range, block, true) ? block : null;\n};\n\n// Returns the last block at least partially contained by the range,\n// or null if no block is contained by the range.\nconst getEndBlockOfRange = (\n    range: Range,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const container = range.endContainer;\n    let block: HTMLElement | null;\n\n    // If inline, get the containing block.\n    if (isInline(container)) {\n        block = getPreviousBlock(container, root);\n    } else if (\n        container !== root &&\n        container instanceof HTMLElement &&\n        isBlock(container)\n    ) {\n        block = container;\n    } else {\n        let node = getNodeAfterOffset(container, range.endOffset);\n        if (!node || !root.contains(node)) {\n            node = root;\n            let child: Node | null;\n            while ((child = node.lastChild)) {\n                node = child;\n            }\n        }\n        block = getPreviousBlock(node, root);\n    }\n    // Check the block actually intersects the range\n    return block && isNodeContainedInRange(range, block, true) ? block : null;\n};\n\nconst isContent = (node: Element | Text): boolean => {\n    return node instanceof Text\n        ? notWS.test(node.data)\n        : node.nodeName === 'IMG';\n};\n\nconst rangeDoesStartAtBlockBoundary = (\n    range: Range,\n    root: Element,\n): boolean => {\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    let nodeAfterCursor: Node | null;\n\n    // If in the middle or end of a text node, we're not at the boundary.\n    if (startContainer instanceof Text) {\n        const text = startContainer.data;\n        for (let i = startOffset; i > 0; i -= 1) {\n            if (text.charAt(i - 1) !== ZWS) {\n                return false;\n            }\n        }\n        nodeAfterCursor = startContainer;\n    } else {\n        nodeAfterCursor = getNodeAfterOffset(startContainer, startOffset);\n        if (nodeAfterCursor && !root.contains(nodeAfterCursor)) {\n            nodeAfterCursor = null;\n        }\n        // The cursor was right at the end of the document\n        if (!nodeAfterCursor) {\n            nodeAfterCursor = getNodeBeforeOffset(startContainer, startOffset);\n            if (nodeAfterCursor instanceof Text && nodeAfterCursor.length) {\n                return false;\n            }\n        }\n    }\n\n    // Otherwise, look for any previous content in the same block.\n    const block = getStartBlockOfRange(range, root);\n    if (!block) {\n        return false;\n    }\n    const contentWalker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        isContent,\n    );\n    contentWalker.currentNode = nodeAfterCursor;\n\n    return !contentWalker.previousNode();\n};\n\nconst rangeDoesEndAtBlockBoundary = (range: Range, root: Element): boolean => {\n    const endContainer = range.endContainer;\n    const endOffset = range.endOffset;\n    let currentNode: Node;\n\n    // If in a text node with content, and not at the end, we're not\n    // at the boundary. Ignore ZWS.\n    if (endContainer instanceof Text) {\n        const text = endContainer.data;\n        const length = text.length;\n        for (let i = endOffset; i < length; i += 1) {\n            if (text.charAt(i) !== ZWS) {\n                return false;\n            }\n        }\n        currentNode = endContainer;\n    } else {\n        currentNode = getNodeBeforeOffset(endContainer, endOffset);\n    }\n\n    // Otherwise, look for any further content in the same block.\n    const block = getEndBlockOfRange(range, root);\n    if (!block) {\n        return false;\n    }\n    const contentWalker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        isContent,\n    );\n    contentWalker.currentNode = currentNode;\n    return !contentWalker.nextNode();\n};\n\nconst expandRangeToBlockBoundaries = (range: Range, root: Element): void => {\n    const start = getStartBlockOfRange(range, root);\n    const end = getEndBlockOfRange(range, root);\n    let parent: Node;\n\n    if (start && end) {\n        parent = start.parentNode!;\n        range.setStart(parent, Array.from(parent.childNodes).indexOf(start));\n        parent = end.parentNode!;\n        range.setEnd(parent, Array.from(parent.childNodes).indexOf(end) + 1);\n    }\n};\n\n// ---\n\nexport {\n    getStartBlockOfRange,\n    getEndBlockOfRange,\n    rangeDoesStartAtBlockBoundary,\n    rangeDoesEndAtBlockBoundary,\n    expandRangeToBlockBoundaries,\n};\n", "import { cleanupBRs } from '../Clean';\nimport {\n    split,\n    fixCursor,\n    mergeWithBlock,\n    fixContainer,\n    mergeContainers,\n} from '../node/MergeSplit';\nimport { detach, getNearest, getLength } from '../node/Node';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from '../node/TreeIterator';\nimport { isInline, isContainer, isLeaf } from '../node/Category';\nimport { getNextBlock, isEmptyBlock, getPreviousBlock } from '../node/Block';\nimport {\n    getStartBlockOfRange,\n    getEndBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n    rangeDoesStartAtBlockBoundary,\n} from './Block';\nimport {\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n} from './Boundaries';\n\n// ---\n\nfunction createRange(startContainer: Node, startOffset: number): Range;\nfunction createRange(\n    startContainer: Node,\n    startOffset: number,\n    endContainer: Node,\n    endOffset: number,\n): Range;\nfunction createRange(\n    startContainer: Node,\n    startOffset: number,\n    endContainer?: Node,\n    endOffset?: number,\n): Range {\n    const range = document.createRange();\n    range.setStart(startContainer, startOffset);\n    if (endContainer && typeof endOffset === 'number') {\n        range.setEnd(endContainer, endOffset);\n    } else {\n        range.setEnd(startContainer, startOffset);\n    }\n    return range;\n}\n\nconst insertNodeInRange = (range: Range, node: Node): void => {\n    // Insert at start.\n    let { startContainer, startOffset, endContainer, endOffset } = range;\n    let children: NodeListOf<ChildNode>;\n\n    // If part way through a text node, split it.\n    if (startContainer instanceof Text) {\n        const parent = startContainer.parentNode!;\n        children = parent.childNodes;\n        if (startOffset === startContainer.length) {\n            startOffset = Array.from(children).indexOf(startContainer) + 1;\n            if (range.collapsed) {\n                endContainer = parent;\n                endOffset = startOffset;\n            }\n        } else {\n            if (startOffset) {\n                const afterSplit = startContainer.splitText(startOffset);\n                if (endContainer === startContainer) {\n                    endOffset -= startOffset;\n                    endContainer = afterSplit;\n                } else if (endContainer === parent) {\n                    endOffset += 1;\n                }\n                startContainer = afterSplit;\n            }\n            startOffset = Array.from(children).indexOf(\n                startContainer as ChildNode,\n            );\n        }\n        startContainer = parent;\n    } else {\n        children = startContainer.childNodes;\n    }\n\n    const childCount = children.length;\n\n    if (startOffset === childCount) {\n        startContainer.appendChild(node);\n    } else {\n        startContainer.insertBefore(node, children[startOffset]);\n    }\n\n    if (startContainer === endContainer) {\n        endOffset += children.length - childCount;\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\n/**\n * Removes the contents of the range and returns it as a DocumentFragment.\n * The range at the end will be at the same position, with the edges just\n * before/after the split. If the start/end have the same parents, it will\n * be collapsed.\n */\nconst extractContentsOfRange = (\n    range: Range,\n    common: Node | null,\n    root: Element,\n): DocumentFragment => {\n    const frag = document.createDocumentFragment();\n    if (range.collapsed) {\n        return frag;\n    }\n\n    if (!common) {\n        common = range.commonAncestorContainer;\n    }\n    if (common instanceof Text) {\n        common = common.parentNode!;\n    }\n\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n\n    let endContainer = split(range.endContainer, range.endOffset, common, root);\n    let endOffset = 0;\n\n    let node = split(startContainer, startOffset, common, root);\n    while (node && node !== endContainer) {\n        const next = node.nextSibling;\n        frag.appendChild(node);\n        node = next;\n    }\n\n    // Merge text nodes if adjacent\n    if (startContainer instanceof Text && endContainer instanceof Text) {\n        startContainer.appendData(endContainer.data);\n        detach(endContainer);\n        endContainer = startContainer;\n        endOffset = startOffset;\n    }\n\n    range.setStart(startContainer, startOffset);\n    if (endContainer) {\n        range.setEnd(endContainer, endOffset);\n    } else {\n        // endContainer will be null if at end of parent's child nodes list.\n        range.setEnd(common, common.childNodes.length);\n    }\n\n    fixCursor(common);\n\n    return frag;\n};\n\n/**\n * Returns the next/prev node that's part of the same inline content.\n */\nconst getAdjacentInlineNode = (\n    iterator: TreeIterator<Node>,\n    method: 'nextNode' | 'previousPONode',\n    node: Node,\n): Node | null => {\n    iterator.currentNode = node;\n    let nextNode: Node | null;\n    while ((nextNode = iterator[method]())) {\n        if (nextNode instanceof Text || isLeaf(nextNode)) {\n            return nextNode;\n        }\n        if (!isInline(nextNode)) {\n            return null;\n        }\n    }\n    return null;\n};\n\nconst deleteContentsOfRange = (\n    range: Range,\n    root: Element,\n): DocumentFragment => {\n    const startBlock = getStartBlockOfRange(range, root);\n    let endBlock = getEndBlockOfRange(range, root);\n    const needsMerge = startBlock !== endBlock;\n\n    // Move boundaries up as much as possible without exiting block,\n    // to reduce need to split.\n    if (startBlock && endBlock) {\n        moveRangeBoundariesDownTree(range);\n        moveRangeBoundariesUpTree(range, startBlock, endBlock, root);\n    }\n\n    // Remove selected range\n    const frag = extractContentsOfRange(range, null, root);\n\n    // Move boundaries back down tree as far as possible.\n    moveRangeBoundariesDownTree(range);\n\n    // If we split into two different blocks, merge the blocks.\n    if (needsMerge) {\n        // endBlock will have been split, so need to refetch\n        endBlock = getEndBlockOfRange(range, root);\n        if (startBlock && endBlock && startBlock !== endBlock) {\n            mergeWithBlock(startBlock, endBlock, range, root);\n        }\n    }\n\n    // Ensure block has necessary children\n    if (startBlock) {\n        fixCursor(startBlock);\n    }\n\n    // Ensure root has a block-level element in it.\n    const child = root.firstChild;\n    if (!child || child.nodeName === 'BR') {\n        fixCursor(root);\n        if (root.firstChild) {\n            range.selectNodeContents(root.firstChild);\n        }\n    }\n\n    range.collapse(true);\n\n    // Now we may need to swap a space for a nbsp if the browser is going\n    // to swallow it due to HTML whitespace rules:\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    const iterator = new TreeIterator(root, SHOW_ELEMENT_OR_TEXT);\n\n    // Find the character after cursor point\n    let afterNode: Node | null = startContainer;\n    let afterOffset = startOffset;\n    if (!(afterNode instanceof Text) || afterOffset === afterNode.data.length) {\n        afterNode = getAdjacentInlineNode(iterator, 'nextNode', afterNode);\n        afterOffset = 0;\n    }\n\n    // Find the character before cursor point\n    let beforeNode: Node | null = startContainer;\n    let beforeOffset = startOffset - 1;\n    if (!(beforeNode instanceof Text) || beforeOffset === -1) {\n        beforeNode = getAdjacentInlineNode(\n            iterator,\n            'previousPONode',\n            afterNode ||\n                (startContainer instanceof Text\n                    ? startContainer\n                    : startContainer.childNodes[startOffset] || startContainer),\n        );\n        if (beforeNode instanceof Text) {\n            beforeOffset = beforeNode.data.length;\n        }\n    }\n\n    // If range starts at block boundary and character after cursor point\n    // is a space, replace with nbsp\n    let node = null;\n    let offset = 0;\n    if (\n        afterNode instanceof Text &&\n        afterNode.data.charAt(afterOffset) === ' ' &&\n        rangeDoesStartAtBlockBoundary(range, root)\n    ) {\n        node = afterNode;\n        offset = afterOffset;\n    } else if (\n        beforeNode instanceof Text &&\n        beforeNode.data.charAt(beforeOffset) === ' '\n    ) {\n        // If character before cursor point is a space, replace with nbsp\n        // if either:\n        // a) There is a space after it; or\n        // b) The point after is the end of the block\n        if (\n            (afterNode instanceof Text &&\n                afterNode.data.charAt(afterOffset) === ' ') ||\n            rangeDoesEndAtBlockBoundary(range, root)\n        ) {\n            node = beforeNode;\n            offset = beforeOffset;\n        }\n    }\n    if (node) {\n        node.replaceData(offset, 1, '\u00A0'); // nbsp\n    }\n    // Range needs to be put back in place\n    range.setStart(startContainer, startOffset);\n    range.collapse(true);\n\n    return frag;\n};\n\n// Contents of range will be deleted.\n// After method, range will be around inserted content\nconst insertTreeFragmentIntoRange = (\n    range: Range,\n    frag: DocumentFragment,\n    root: Element,\n): void => {\n    const firstInFragIsInline = frag.firstChild && isInline(frag.firstChild);\n    let node: Node | null;\n\n    // Fixup content: ensure no top-level inline, and add cursor fix elements.\n    fixContainer(frag, root);\n    node = frag;\n    while ((node = getNextBlock(node, root))) {\n        fixCursor(node);\n    }\n\n    // Delete any selected content.\n    if (!range.collapsed) {\n        deleteContentsOfRange(range, root);\n    }\n\n    // Move range down into text nodes.\n    moveRangeBoundariesDownTree(range);\n    range.collapse(false); // collapse to end\n\n    // Where will we split up to? First blockquote parent, otherwise root.\n    const stopPoint =\n        getNearest(range.endContainer, root, 'BLOCKQUOTE') || root;\n\n    // Merge the contents of the first block in the frag with the focused block.\n    // If there are contents in the block after the focus point, collect this\n    // up to insert in the last block later. This preserves the style that was\n    // present in this bit of the page.\n    //\n    // If the block being inserted into is empty though, replace it instead of\n    // merging if the fragment had block contents.\n    // e.g. <blockquote><p>Foo</p></blockquote>\n    // This seems a reasonable approximation of user intent.\n    let block = getStartBlockOfRange(range, root);\n    let blockContentsAfterSplit: DocumentFragment | null = null;\n    const firstBlockInFrag = getNextBlock(frag, frag);\n    const replaceBlock = !firstInFragIsInline && !!block && isEmptyBlock(block);\n    if (\n        block &&\n        firstBlockInFrag &&\n        !replaceBlock &&\n        // Don't merge table cells or PRE elements into block\n        !getNearest(firstBlockInFrag, frag, 'PRE') &&\n        !getNearest(firstBlockInFrag, frag, 'TABLE')\n    ) {\n        moveRangeBoundariesUpTree(range, block, block, root);\n        range.collapse(true); // collapse to start\n        let container = range.endContainer;\n        let offset = range.endOffset;\n        // Remove trailing <br> \u2013 we don't want this considered content to be\n        // inserted again later\n        cleanupBRs(block as HTMLElement, root, false);\n        if (isInline(container)) {\n            // Split up to block parent.\n            const nodeAfterSplit = split(\n                container,\n                offset,\n                getPreviousBlock(container, root) || root,\n                root,\n            ) as Node;\n            container = nodeAfterSplit.parentNode!;\n            offset = Array.from(container.childNodes).indexOf(\n                nodeAfterSplit as ChildNode,\n            );\n        }\n        if (/*isBlock( container ) && */ offset !== getLength(container)) {\n            // Collect any inline contents of the block after the range point\n            blockContentsAfterSplit = document.createDocumentFragment();\n            while ((node = container.childNodes[offset])) {\n                blockContentsAfterSplit.appendChild(node);\n            }\n        }\n        // And merge the first block in.\n        mergeWithBlock(container, firstBlockInFrag, range, root);\n\n        // And where we will insert\n        offset =\n            Array.from(container.parentNode!.childNodes).indexOf(\n                container as ChildNode,\n            ) + 1;\n        container = container.parentNode!;\n        range.setEnd(container, offset);\n    }\n\n    // Is there still any content in the fragment?\n    if (getLength(frag)) {\n        if (replaceBlock && block) {\n            range.setEndBefore(block);\n            range.collapse(false);\n            detach(block);\n        }\n        moveRangeBoundariesUpTree(range, stopPoint, stopPoint, root);\n        // Now split after block up to blockquote (if a parent) or root\n        let nodeAfterSplit = split(\n            range.endContainer,\n            range.endOffset,\n            stopPoint,\n            root,\n        ) as Node | null;\n        const nodeBeforeSplit = nodeAfterSplit\n            ? nodeAfterSplit.previousSibling\n            : stopPoint.lastChild;\n        stopPoint.insertBefore(frag, nodeAfterSplit);\n        if (nodeAfterSplit) {\n            range.setEndBefore(nodeAfterSplit);\n        } else {\n            range.setEnd(stopPoint, getLength(stopPoint));\n        }\n        block = getEndBlockOfRange(range, root);\n\n        // Get a reference that won't be invalidated if we merge containers.\n        moveRangeBoundariesDownTree(range);\n        const container = range.endContainer;\n        const offset = range.endOffset;\n\n        // Merge inserted containers with edges of split\n        if (nodeAfterSplit && isContainer(nodeAfterSplit)) {\n            mergeContainers(nodeAfterSplit, root);\n        }\n        nodeAfterSplit = nodeBeforeSplit && nodeBeforeSplit.nextSibling;\n        if (nodeAfterSplit && isContainer(nodeAfterSplit)) {\n            mergeContainers(nodeAfterSplit, root);\n        }\n        range.setEnd(container, offset);\n    }\n\n    // Insert inline content saved from before.\n    if (blockContentsAfterSplit && block) {\n        const tempRange = range.cloneRange();\n        fixCursor(blockContentsAfterSplit);\n        mergeWithBlock(block, blockContentsAfterSplit, tempRange, root);\n        range.setEnd(tempRange.endContainer, tempRange.endOffset);\n    }\n    moveRangeBoundariesDownTree(range);\n};\n\n// ---\n\nexport {\n    createRange,\n    deleteContentsOfRange,\n    extractContentsOfRange,\n    insertNodeInRange,\n    insertTreeFragmentIntoRange,\n};\n", "import { SHOW_ELEMENT_OR_TEXT, TreeIterator } from '../node/TreeIterator';\nimport { isNodeContainedInRange } from './Boundaries';\nimport { isInline } from '../node/Category';\n\n// ---\n\nconst getTextContentsOfRange = (range: Range) => {\n    if (range.collapsed) {\n        return '';\n    }\n    const startContainer = range.startContainer;\n    const endContainer = range.endContainer;\n    const walker = new TreeIterator<Element | Text>(\n        range.commonAncestorContainer,\n        SHOW_ELEMENT_OR_TEXT,\n        (node) => {\n            return isNodeContainedInRange(range, node, true);\n        },\n    );\n    walker.currentNode = startContainer;\n\n    let node: Node | null = startContainer;\n    let textContent = '';\n    let addedTextInBlock = false;\n    let value: string;\n\n    if (\n        (!(node instanceof Element) && !(node instanceof Text)) ||\n        !walker.filter(node)\n    ) {\n        node = walker.nextNode();\n    }\n\n    while (node) {\n        if (node instanceof Text) {\n            value = node.data;\n            if (value && /\\S/.test(value)) {\n                if (node === endContainer) {\n                    value = value.slice(0, range.endOffset);\n                }\n                if (node === startContainer) {\n                    value = value.slice(range.startOffset);\n                }\n                textContent += value;\n                addedTextInBlock = true;\n            }\n        } else if (\n            node.nodeName === 'BR' ||\n            (addedTextInBlock && !isInline(node))\n        ) {\n            textContent += '\\n';\n            addedTextInBlock = false;\n        }\n        node = walker.nextNode();\n    }\n    // Replace nbsp with regular space;\n    // eslint-disable-next-line no-irregular-whitespace\n    textContent = textContent.replace(/\u00A0/g, ' ');\n\n    return textContent;\n};\n\n// ---\n\nexport { getTextContentsOfRange };\n", "import { isWin, isGecko, isLegacyEdge, notWS } from './Constants';\nimport { createElement, detach } from './node/Node';\nimport { getStartBlockOfRange, getEndBlockOfRange } from './range/Block';\nimport { createRange, deleteContentsOfRange } from './range/InsertDelete';\nimport {\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n} from './range/Boundaries';\n\nimport type { Squire } from './Editor';\nimport { getTextContentsOfRange } from './range/Contents';\n\n// ---\n\nconst indexOf = Array.prototype.indexOf;\n\nconst extractRangeToClipboard = (\n    event: ClipboardEvent,\n    range: Range,\n    root: HTMLElement,\n    removeRangeFromDocument: boolean,\n    toCleanHTML: null | ((html: string) => string),\n    toPlainText: null | ((html: string) => string),\n    plainTextOnly: boolean,\n): boolean => {\n    // Edge only seems to support setting plain text as of 2016-03-11.\n    const clipboardData = event.clipboardData;\n    if (isLegacyEdge || !clipboardData) {\n        return false;\n    }\n    // First get the plain text version from the range (unless we have a custom\n    // HTML -> Text conversion fn)\n    let text = toPlainText ? '' : getTextContentsOfRange(range);\n\n    // Clipboard content should include all parents within block, or all\n    // parents up to root if selection across blocks\n    const startBlock = getStartBlockOfRange(range, root);\n    const endBlock = getEndBlockOfRange(range, root);\n    let copyRoot = root;\n\n    // If the content is not in well-formed blocks, the start and end block\n    // may be the same, but actually the range goes outside it. Must check!\n    if (\n        startBlock === endBlock &&\n        startBlock?.contains(range.commonAncestorContainer)\n    ) {\n        copyRoot = startBlock;\n    }\n\n    // Extract the contents\n    let contents: Node;\n    if (removeRangeFromDocument) {\n        contents = deleteContentsOfRange(range, root);\n    } else {\n        // Clone range to mutate, then move up as high as possible without\n        // passing the copy root node.\n        range = range.cloneRange();\n        moveRangeBoundariesDownTree(range);\n        moveRangeBoundariesUpTree(range, copyRoot, copyRoot, root);\n        contents = range.cloneContents();\n    }\n\n    // Add any other parents not in extracted content, up to copy root\n    let parent = range.commonAncestorContainer;\n    if (parent instanceof Text) {\n        parent = parent.parentNode!;\n    }\n    while (parent && parent !== copyRoot) {\n        const newContents = parent.cloneNode(false);\n        newContents.appendChild(contents);\n        contents = newContents;\n        parent = parent.parentNode!;\n    }\n\n    // Get HTML version of data\n    let html: string | undefined;\n    if (\n        contents.childNodes.length === 1 &&\n        contents.childNodes[0] instanceof Text\n    ) {\n        // Replace nbsp with regular space;\n        // eslint-disable-next-line no-irregular-whitespace\n        text = contents.childNodes[0].data.replace(/\u00A0/g, ' ');\n        plainTextOnly = true;\n    } else {\n        const node = createElement('DIV') as HTMLDivElement;\n        node.appendChild(contents);\n        html = node.innerHTML;\n        if (toCleanHTML) {\n            html = toCleanHTML(html);\n        }\n    }\n\n    // Get Text version of data if converting from HTML\n    if (toPlainText && html !== undefined) {\n        text = toPlainText(html);\n    }\n\n    // Firefox (and others?) returns unix line endings (\\n) even on Windows.\n    // If on Windows, normalise to \\r\\n, since Notepad and some other crappy\n    // apps do not understand just \\n.\n    if (isWin) {\n        text = text.replace(/\\r?\\n/g, '\\r\\n');\n    }\n\n    // Set clipboard data\n    if (!plainTextOnly && html && text !== html) {\n        clipboardData.setData('text/html', html);\n    }\n    clipboardData.setData('text/plain', text);\n    event.preventDefault();\n\n    return true;\n};\n\n// ---\n\nconst _onCut = function (this: Squire, event: ClipboardEvent): void {\n    const range: Range = this.getSelection();\n    const root: HTMLElement = this._root;\n\n    // Nothing to do\n    if (range.collapsed) {\n        event.preventDefault();\n        return;\n    }\n\n    // Save undo checkpoint\n    this.saveUndoState(range);\n\n    const handled = extractRangeToClipboard(\n        event,\n        range,\n        root,\n        true,\n        this._config.willCutCopy,\n        this._config.toPlainText,\n        false,\n    );\n    if (!handled) {\n        setTimeout(() => {\n            try {\n                // If all content removed, ensure div at start of root.\n                this._ensureBottomLine();\n            } catch (error) {\n                this._config.didError(error);\n            }\n        }, 0);\n    }\n\n    this.setSelection(range);\n};\n\nconst _onCopy = function (this: Squire, event: ClipboardEvent): void {\n    extractRangeToClipboard(\n        event,\n        this.getSelection(),\n        this._root,\n        false,\n        this._config.willCutCopy,\n        this._config.toPlainText,\n        false,\n    );\n};\n\n// Need to monitor for shift key like this, as event.shiftKey is not available\n// in paste event.\nconst _monitorShiftKey = function (this: Squire, event: KeyboardEvent): void {\n    this._isShiftDown = event.shiftKey;\n};\n\nconst _onPaste = function (this: Squire, event: ClipboardEvent): void {\n    const clipboardData = event.clipboardData;\n    const items = clipboardData?.items;\n    const choosePlain: boolean | undefined = this._isShiftDown;\n    let hasRTF = false;\n    let hasImage = false;\n    let plainItem: null | DataTransferItem = null;\n    let htmlItem: null | DataTransferItem = null;\n\n    // Current HTML5 Clipboard interface\n    // ---------------------------------\n    // https://html.spec.whatwg.org/multipage/interaction.html\n    if (items) {\n        let l = items.length;\n        while (l--) {\n            const item = items[l];\n            const type = item.type;\n            if (type === 'text/html') {\n                htmlItem = item;\n                // iOS copy URL gives you type text/uri-list which is just a list\n                // of 1 or more URLs separated by new lines. Can just treat as\n                // plain text.\n            } else if (type === 'text/plain' || type === 'text/uri-list') {\n                plainItem = item;\n            } else if (type === 'text/rtf') {\n                hasRTF = true;\n            } else if (/^image\\/.*/.test(type)) {\n                hasImage = true;\n            }\n        }\n\n        // Treat image paste as a drop of an image file. When you copy\n        // an image in Chrome/Firefox (at least), it copies the image data\n        // but also an HTML version (referencing the original URL of the image)\n        // and a plain text version.\n        //\n        // However, when you copy in Excel, you get html, rtf, text, image;\n        // in this instance you want the html version! So let's try using\n        // the presence of text/rtf as an indicator to choose the html version\n        // over the image.\n        if (hasImage && !(hasRTF && htmlItem)) {\n            event.preventDefault();\n            this.fireEvent('pasteImage', {\n                clipboardData,\n            });\n            return;\n        }\n\n        // Edge only provides access to plain text as of 2016-03-11 and gives no\n        // indication there should be an HTML part. However, it does support\n        // access to image data, so we check for that first. Otherwise though,\n        // fall through to fallback clipboard handling methods\n        if (!isLegacyEdge) {\n            event.preventDefault();\n            if (htmlItem && (!choosePlain || !plainItem)) {\n                htmlItem.getAsString((html) => {\n                    this.insertHTML(html, true);\n                });\n            } else if (plainItem) {\n                plainItem.getAsString((text) => {\n                    // If we have a selection and text is solely a URL,\n                    // just make the text a link.\n                    let isLink = false;\n                    const range = this.getSelection();\n                    if (!range.collapsed && notWS.test(range.toString())) {\n                        const match = this.linkRegExp.exec(text);\n                        isLink = !!match && match[0].length === text.length;\n                    }\n                    if (isLink) {\n                        this.makeLink(text);\n                    } else {\n                        this.insertPlainText(text, true);\n                    }\n                });\n            }\n            return;\n        }\n    }\n\n    // Old interface\n    // -------------\n\n    // Safari (and indeed many other OS X apps) copies stuff as text/rtf\n    // rather than text/html; even from a webpage in Safari. The only way\n    // to get an HTML version is to fallback to letting the browser insert\n    // the content. Same for getting image data. *Sigh*.\n    //\n    // Firefox is even worse: it doesn't even let you know that there might be\n    // an RTF version on the clipboard, but it will also convert to HTML if you\n    // let the browser insert the content. I've filed\n    // https://bugzilla.mozilla.org/show_bug.cgi?id=1254028\n    const types = clipboardData?.types;\n    if (\n        !isLegacyEdge &&\n        types &&\n        (indexOf.call(types, 'text/html') > -1 ||\n            (!isGecko &&\n                indexOf.call(types, 'text/plain') > -1 &&\n                indexOf.call(types, 'text/rtf') < 0))\n    ) {\n        event.preventDefault();\n        // Abiword on Linux copies a plain text and html version, but the HTML\n        // version is the empty string! So always try to get HTML, but if none,\n        // insert plain text instead. On iOS, Facebook (and possibly other\n        // apps?) copy links as type text/uri-list, but also insert a **blank**\n        // text/plain item onto the clipboard. Why? Who knows.\n        let data;\n        if (!choosePlain && (data = clipboardData.getData('text/html'))) {\n            this.insertHTML(data, true);\n        } else if (\n            (data = clipboardData.getData('text/plain')) ||\n            (data = clipboardData.getData('text/uri-list'))\n        ) {\n            this.insertPlainText(data, true);\n        }\n        return;\n    }\n\n    // No interface. Includes all versions of IE :(\n    // --------------------------------------------\n\n    const body = document.body;\n    const range = this.getSelection();\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    const endContainer = range.endContainer;\n    const endOffset = range.endOffset;\n\n    // We need to position the pasteArea in the visible portion of the screen\n    // to stop the browser auto-scrolling.\n    let pasteArea: Element = createElement('DIV', {\n        contenteditable: 'true',\n        style: 'position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;',\n    });\n    body.appendChild(pasteArea);\n    range.selectNodeContents(pasteArea);\n    this.setSelection(range);\n\n    // A setTimeout of 0 means this is added to the back of the\n    // single javascript thread, so it will be executed after the\n    // paste event.\n    setTimeout(() => {\n        try {\n            // Get the pasted content and clean\n            let html = '';\n            let next: Element = pasteArea;\n            let first: Node | null;\n\n            // #88: Chrome can apparently split the paste area if certain\n            // content is inserted; gather them all up.\n            while ((pasteArea = next)) {\n                next = pasteArea.nextSibling as Element;\n                detach(pasteArea);\n                // Safari and IE like putting extra divs around things.\n                first = pasteArea.firstChild;\n                if (\n                    first &&\n                    first === pasteArea.lastChild &&\n                    first instanceof HTMLDivElement\n                ) {\n                    pasteArea = first;\n                }\n                html += pasteArea.innerHTML;\n            }\n\n            this.setSelection(\n                createRange(\n                    startContainer,\n                    startOffset,\n                    endContainer,\n                    endOffset,\n                ),\n            );\n\n            if (html) {\n                this.insertHTML(html, true);\n            }\n        } catch (error) {\n            this._config.didError(error);\n        }\n    }, 0);\n};\n\n// On Windows you can drag an drop text. We can't handle this ourselves, because\n// as far as I can see, there's no way to get the drop insertion point. So just\n// save an undo state and hope for the best.\nconst _onDrop = function (this: Squire, event: DragEvent): void {\n    // it's possible for dataTransfer to be null, let's avoid it.\n    if (!event.dataTransfer) {\n        return;\n    }\n    const types = event.dataTransfer.types;\n    let l = types.length;\n    let hasPlain = false;\n    let hasHTML = false;\n    while (l--) {\n        switch (types[l]) {\n            case 'text/plain':\n                hasPlain = true;\n                break;\n            case 'text/html':\n                hasHTML = true;\n                break;\n            default:\n                return;\n        }\n    }\n    if (hasHTML || (hasPlain && this.saveUndoState)) {\n        this.saveUndoState();\n    }\n};\n\n// ---\n\nexport {\n    extractRangeToClipboard,\n    _onCut,\n    _onCopy,\n    _monitorShiftKey,\n    _onPaste,\n    _onDrop,\n};\n", "import type { Squire } from '../Editor';\n\n// ---\n\nconst Enter = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    event.preventDefault();\n    self.splitBlock(event.shiftKey, range);\n};\n\n// ---\n\nexport { Enter };\n", "import { ZWS } from '../Constants';\nimport { getPreviousBlock } from '../node/Block';\nimport { isInline, isBlock } from '../node/Category';\nimport { fixCursor } from '../node/MergeSplit';\nimport { createElement, detach, getNearest } from '../node/Node';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\n// If you delete the content inside a span with a font styling, Webkit will\n// replace it with a <font> tag (!). If you delete all the text inside a\n// link in Opera, it won't delete the link. Let's make things consistent. If\n// you delete all text inside an inline tag, remove the inline tag.\nconst afterDelete = (self: Squire, range?: Range): void => {\n    try {\n        if (!range) {\n            range = self.getSelection();\n        }\n        let node = range!.startContainer;\n        // Climb the tree from the focus point while we are inside an empty\n        // inline element\n        if (node instanceof Text) {\n            node = node.parentNode!;\n        }\n        let parent = node;\n        while (\n            isInline(parent) &&\n            (!parent.textContent || parent.textContent === ZWS)\n        ) {\n            node = parent;\n            parent = node.parentNode!;\n        }\n        // If focused in empty inline element\n        if (node !== parent) {\n            // Move focus to just before empty inline(s)\n            range!.setStart(\n                parent,\n                Array.from(parent.childNodes as NodeListOf<Node>).indexOf(node),\n            );\n            range!.collapse(true);\n            // Remove empty inline(s)\n            parent.removeChild(node);\n            // Fix cursor in block\n            if (!isBlock(parent)) {\n                parent = getPreviousBlock(parent, self._root) || self._root;\n            }\n            fixCursor(parent);\n            // Move cursor into text node\n            moveRangeBoundariesDownTree(range!);\n        }\n        // If you delete the last character in the sole <div> in Chrome,\n        // it removes the div and replaces it with just a <br> inside the\n        // root. Detach the <br>; the _ensureBottomLine call will insert a new\n        // block.\n        if (\n            node === self._root &&\n            (node = node.firstChild!) &&\n            node.nodeName === 'BR'\n        ) {\n            detach(node);\n        }\n        self._ensureBottomLine();\n        self.setSelection(range);\n        self._updatePath(range, true);\n    } catch (error) {\n        self._config.didError(error);\n    }\n};\n\nconst detachUneditableNode = (node: Node, root: Element): void => {\n    let parent: Node | null;\n    while ((parent = node.parentNode)) {\n        if (parent === root || (parent as HTMLElement).isContentEditable) {\n            break;\n        }\n        node = parent;\n    }\n    detach(node);\n};\n\n// ---\n\nconst linkifyText = (self: Squire, textNode: Text, offset: number): void => {\n    if (getNearest(textNode, self._root, 'A')) {\n        return;\n    }\n    const data = textNode.data || '';\n    const searchFrom =\n        Math.max(\n            data.lastIndexOf(' ', offset - 1),\n            data.lastIndexOf('\u00A0', offset - 1),\n        ) + 1;\n    const searchText = data.slice(searchFrom, offset);\n    const match = self.linkRegExp.exec(searchText);\n    if (match) {\n        // Record an undo point\n        const selection = self.getSelection();\n        self._docWasChanged();\n        self._recordUndoState(selection);\n        self._getRangeAndRemoveBookmark(selection);\n\n        const index = searchFrom + match.index;\n        const endIndex = index + match[0].length;\n        const needsSelectionUpdate = selection.startContainer === textNode;\n        const newSelectionOffset = selection.startOffset - endIndex;\n        if (index) {\n            textNode = textNode.splitText(index);\n        }\n\n        const defaultAttributes = self._config.tagAttributes.a;\n        const link = createElement(\n            'A',\n            Object.assign(\n                {\n                    href: match[1]\n                        ? /^(?:ht|f)tps?:/i.test(match[1])\n                            ? match[1]\n                            : 'http://' + match[1]\n                        : 'mailto:' + match[0],\n                },\n                defaultAttributes,\n            ),\n        );\n        link.textContent = data.slice(index, endIndex);\n        textNode.parentNode!.insertBefore(link, textNode);\n        textNode.data = data.slice(endIndex);\n\n        if (needsSelectionUpdate) {\n            selection.setStart(textNode, newSelectionOffset);\n            selection.setEnd(textNode, newSelectionOffset);\n        }\n        self.setSelection(selection);\n    }\n};\n\n// ---\n\nexport { afterDelete, detachUneditableNode, linkifyText };\n", "import type { Squire } from '../Editor';\nimport { getPreviousBlock } from '../node/Block';\nimport {\n    fixContainer,\n    mergeContainers,\n    mergeWithBlock,\n} from '../node/MergeSplit';\nimport { getNearest } from '../node/Node';\nimport {\n    getStartBlockOfRange,\n    rangeDoesStartAtBlockBoundary,\n} from '../range/Block';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport { afterDelete, detachUneditableNode } from './KeyHelpers';\n\n// ---\n\nconst Backspace = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root: Element = self._root;\n    self._removeZWS();\n    // Record undo checkpoint.\n    self.saveUndoState(range);\n    if (!range.collapsed) {\n        // If not collapsed, delete contents\n        event.preventDefault();\n        deleteContentsOfRange(range, root);\n        afterDelete(self, range);\n    } else if (rangeDoesStartAtBlockBoundary(range, root)) {\n        // If at beginning of block, merge with previous\n        event.preventDefault();\n        const startBlock = getStartBlockOfRange(range, root);\n        if (!startBlock) {\n            return;\n        }\n        let current = startBlock;\n        // In case inline data has somehow got between blocks.\n        fixContainer(current.parentNode!, root);\n        // Now get previous block\n        const previous = getPreviousBlock(current, root);\n        // Must not be at the very beginning of the text area.\n        if (previous) {\n            // If not editable, just delete whole block.\n            if (!(previous as HTMLElement).isContentEditable) {\n                detachUneditableNode(previous, root);\n                return;\n            }\n            // Otherwise merge.\n            mergeWithBlock(previous, current, range, root);\n            // If deleted line between containers, merge newly adjacent\n            // containers.\n            current = previous.parentNode as HTMLElement;\n            while (current !== root && !current.nextSibling) {\n                current = current.parentNode as HTMLElement;\n            }\n            if (\n                current !== root &&\n                (current = current.nextSibling as HTMLElement)\n            ) {\n                mergeContainers(current, root);\n            }\n            self.setSelection(range);\n            // If at very beginning of text area, allow backspace\n            // to break lists/blockquote.\n        } else if (current) {\n            if (\n                getNearest(current, root, 'UL') ||\n                getNearest(current, root, 'OL')\n            ) {\n                // Break list\n                self.decreaseListLevel(range);\n                return;\n            } else if (getNearest(current, root, 'BLOCKQUOTE')) {\n                // Break blockquote\n                self.removeQuote(range);\n                return;\n            }\n            self.setSelection(range);\n            self._updatePath(range, true);\n        }\n    } else {\n        // If deleting text inside a link that looks like a URL, delink.\n        // This is to allow you to easily correct auto-linked text.\n        moveRangeBoundariesDownTree(range);\n        const text = range.startContainer;\n        const offset = range.startOffset;\n        const a = text.parentNode;\n        if (\n            text instanceof Text &&\n            a instanceof HTMLAnchorElement &&\n            offset &&\n            a.href.includes(text.data)\n        ) {\n            text.deleteData(offset - 1, 1);\n            self.setSelection(range);\n            self.removeLink();\n            event.preventDefault();\n        } else {\n            // Otherwise, leave to browser but check afterwards whether it has\n            // left behind an empty inline tag.\n            self.setSelection(range);\n            setTimeout(() => {\n                afterDelete(self);\n            }, 0);\n        }\n    }\n};\n\n// ---\n\nexport { Backspace };\n", "import { getNextBlock } from '../node/Block';\nimport {\n    fixContainer,\n    mergeWithBlock,\n    mergeContainers,\n} from '../node/MergeSplit';\nimport { detach } from '../node/Node';\nimport {\n    rangeDoesEndAtBlockBoundary,\n    getStartBlockOfRange,\n} from '../range/Block';\nimport {\n    moveRangeBoundariesUpTree,\n    moveRangeBoundariesDownTree,\n} from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport { afterDelete, detachUneditableNode } from './KeyHelpers';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\nconst Delete = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    let current: Node | null;\n    let next: Node | null;\n    let originalRange: Range;\n    let cursorContainer: Node;\n    let cursorOffset: number;\n    let nodeAfterCursor: Node;\n    self._removeZWS();\n    // Record undo checkpoint.\n    self.saveUndoState(range);\n    // If not collapsed, delete contents\n    if (!range.collapsed) {\n        event.preventDefault();\n        deleteContentsOfRange(range, root);\n        afterDelete(self, range);\n        // If at end of block, merge next into this block\n    } else if (rangeDoesEndAtBlockBoundary(range, root)) {\n        event.preventDefault();\n        current = getStartBlockOfRange(range, root);\n        if (!current) {\n            return;\n        }\n        // In case inline data has somehow got between blocks.\n        fixContainer(current.parentNode!, root);\n        // Now get next block\n        next = getNextBlock(current, root);\n        // Must not be at the very end of the text area.\n        if (next) {\n            // If not editable, just delete whole block.\n            if (!(next as HTMLElement).isContentEditable) {\n                detachUneditableNode(next, root);\n                return;\n            }\n            // Otherwise merge.\n            mergeWithBlock(current, next, range, root);\n            // If deleted line between containers, merge newly adjacent\n            // containers.\n            next = current.parentNode!;\n            while (next !== root && !next.nextSibling) {\n                next = next.parentNode!;\n            }\n            if (next !== root && (next = next.nextSibling)) {\n                mergeContainers(next, root);\n            }\n            self.setSelection(range);\n            self._updatePath(range, true);\n        }\n        // Otherwise, leave to browser but check afterwards whether it has\n        // left behind an empty inline tag.\n    } else {\n        // But first check if the cursor is just before an IMG tag. If so,\n        // delete it ourselves, because the browser won't if it is not\n        // inline.\n        originalRange = range.cloneRange();\n        moveRangeBoundariesUpTree(range, root, root, root);\n        cursorContainer = range.endContainer;\n        cursorOffset = range.endOffset;\n        if (cursorContainer instanceof Element) {\n            nodeAfterCursor = cursorContainer.childNodes[cursorOffset];\n            if (nodeAfterCursor && nodeAfterCursor.nodeName === 'IMG') {\n                event.preventDefault();\n                detach(nodeAfterCursor);\n                moveRangeBoundariesDownTree(range);\n                afterDelete(self, range);\n                return;\n            }\n        }\n        self.setSelection(originalRange);\n        setTimeout(() => {\n            afterDelete(self);\n        }, 0);\n    }\n};\n\n// ---\n\nexport { Delete };\n", "import {\n    rangeDoesStartAtBlockBoundary,\n    getStartBlockOfRange,\n} from '../range/Block';\nimport { getNearest } from '../node/Node';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\nconst Tab = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    self._removeZWS();\n    // If no selection and at start of block\n    if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) {\n        let node: Node = getStartBlockOfRange(range, root)!;\n        // Iterate through the block's parents\n        let parent: Node | null;\n        while ((parent = node.parentNode)) {\n            // If we find a UL or OL (so are in a list, node must be an LI)\n            if (parent.nodeName === 'UL' || parent.nodeName === 'OL') {\n                // Then increase the list level\n                event.preventDefault();\n                self.increaseListLevel(range);\n                break;\n            }\n            node = parent;\n        }\n    }\n};\n\nconst ShiftTab = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    self._removeZWS();\n    // If no selection and at start of block\n    if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) {\n        // Break list\n        const node = range.startContainer;\n        if (getNearest(node, root, 'UL') || getNearest(node, root, 'OL')) {\n            event.preventDefault();\n            self.decreaseListLevel(range);\n        }\n    }\n};\n\n// ---\n\nexport { Tab, ShiftTab };\n", "import { detach, getLength } from '../node/Node';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\n\nimport type { Squire } from '../Editor';\nimport { linkifyText } from './KeyHelpers';\nimport {\n    getStartBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n} from '../range/Block';\nimport { SHOW_TEXT, TreeIterator } from '../node/TreeIterator';\nimport { ZWS } from '../Constants';\n\n// ---\n\nconst Space = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    let node: Node | null;\n    const root = self._root;\n    self._recordUndoState(range);\n    self._getRangeAndRemoveBookmark(range);\n\n    // Delete the selection if not collapsed\n    if (!range.collapsed) {\n        deleteContentsOfRange(range, root);\n        self._ensureBottomLine();\n        self.setSelection(range);\n        self._updatePath(range, true);\n    } else if (rangeDoesEndAtBlockBoundary(range, root)) {\n        const block = getStartBlockOfRange(range, root);\n        if (block && block.nodeName !== 'PRE') {\n            const text = block.textContent?.trimEnd().replace(ZWS, '');\n            if (text === '*' || text === '1.') {\n                event.preventDefault();\n                self.insertPlainText(' ', false);\n                self._docWasChanged();\n                self.saveUndoState(range);\n                const walker = new TreeIterator<Text>(block, SHOW_TEXT);\n                let textNode: Text | null;\n                while ((textNode = walker.nextNode())) {\n                    detach(textNode);\n                }\n                if (text === '*') {\n                    self.makeUnorderedList();\n                } else {\n                    self.makeOrderedList();\n                }\n                return;\n            }\n        }\n    }\n\n    // If the cursor is at the end of a link (<a>foo|</a>) then move it\n    // outside of the link (<a>foo</a>|) so that the space is not part of\n    // the link text.\n    node = range.endContainer;\n    if (range.endOffset === getLength(node)) {\n        do {\n            if (node.nodeName === 'A') {\n                range.setStartAfter(node);\n                break;\n            }\n        } while (\n            !node.nextSibling &&\n            (node = node.parentNode) &&\n            node !== root\n        );\n    }\n\n    // Linkify text\n    if (self._config.addLinks) {\n        const linkRange = range.cloneRange();\n        moveRangeBoundariesDownTree(linkRange);\n        const textNode = linkRange.startContainer as Text;\n        const offset = linkRange.startOffset;\n        setTimeout(() => {\n            linkifyText(self, textNode, offset);\n        }, 0);\n    }\n\n    self.setSelection(range);\n};\n\n// ---\n\nexport { Space };\n", "import {\n    isMac,\n    isWin,\n    isIOS,\n    ctrlKey,\n    supportsInputEvents,\n} from '../Constants';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport type { Squire } from '../Editor';\nimport { Enter } from './Enter';\nimport { Backspace } from './Backspace';\nimport { Delete } from './Delete';\nimport { ShiftTab, Tab } from './Tab';\nimport { Space } from './Space';\nimport { rangeDoesEndAtBlockBoundary } from '../range/Block';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\n\n// ---\n\nconst _onKey = function (this: Squire, event: KeyboardEvent): void {\n    // Ignore key events where event.isComposing, to stop us from blatting\n    // Kana-Kanji conversion\n    if (event.defaultPrevented || event.isComposing) {\n        return;\n    }\n\n    // We need to apply the Backspace/delete handlers regardless of\n    // control key modifiers.\n    let key = event.key;\n    let modifiers = '';\n    const code = event.code;\n    // If pressing a number key + Shift, make sure we handle it as the number\n    // key and not whatever different character the shift might turn it into.\n    if (/^Digit\\d$/.test(code)) {\n        key = code.slice(-1);\n    }\n    if (key !== 'Backspace' && key !== 'Delete') {\n        if (event.altKey) {\n            modifiers += 'Alt-';\n        }\n        if (event.ctrlKey) {\n            modifiers += 'Ctrl-';\n        }\n        if (event.metaKey) {\n            modifiers += 'Meta-';\n        }\n        if (event.shiftKey) {\n            modifiers += 'Shift-';\n        }\n    }\n    // However, on Windows, Shift-Delete is apparently \"cut\" (WTF right?), so\n    // we want to let the browser handle Shift-Delete in this situation.\n    if (isWin && event.shiftKey && key === 'Delete') {\n        modifiers += 'Shift-';\n    }\n    key = modifiers + key;\n\n    const range: Range = this.getSelection();\n    if (this._keyHandlers[key]) {\n        this._keyHandlers[key](this, event, range);\n    } else if (\n        !range.collapsed &&\n        !event.ctrlKey &&\n        !event.metaKey &&\n        key.length === 1\n    ) {\n        // Record undo checkpoint.\n        this.saveUndoState(range);\n        // Delete the selection\n        deleteContentsOfRange(range, this._root);\n        this._ensureBottomLine();\n        this.setSelection(range);\n        this._updatePath(range, true);\n    }\n};\n\n// ---\n\ntype KeyHandler = (self: Squire, event: KeyboardEvent, range: Range) => void;\n\nconst keyHandlers: Record<string, KeyHandler> = {\n    'Backspace': Backspace,\n    'Delete': Delete,\n    'Tab': Tab,\n    'Shift-Tab': ShiftTab,\n    ' ': Space,\n    'ArrowLeft'(self: Squire): void {\n        self._removeZWS();\n    },\n    'ArrowRight'(self: Squire, event: KeyboardEvent, range: Range): void {\n        self._removeZWS();\n        // Allow right arrow to always break out of <code> block.\n        const root = self.getRoot();\n        if (rangeDoesEndAtBlockBoundary(range, root)) {\n            moveRangeBoundariesDownTree(range);\n            let node: Node | null = range.endContainer;\n            do {\n                if (node.nodeName === 'CODE') {\n                    let next = node.nextSibling;\n                    if (!(next instanceof Text)) {\n                        const textNode = document.createTextNode('\u00A0'); // nbsp\n                        node.parentNode!.insertBefore(textNode, next);\n                        next = textNode;\n                    }\n                    range.setStart(next, 1);\n                    self.setSelection(range);\n                    event.preventDefault();\n                    break;\n                }\n            } while (\n                !node.nextSibling &&\n                (node = node.parentNode) &&\n                node !== root\n            );\n        }\n    },\n};\n\nif (!supportsInputEvents) {\n    keyHandlers.Enter = Enter;\n    keyHandlers['Shift-Enter'] = Enter;\n}\n\n// System standard for page up/down on Mac/iOS is to just scroll, not move the\n// cursor. On Linux/Windows, it should move the cursor, but some browsers don't\n// implement this natively. Override to support it.\nif (!isMac && !isIOS) {\n    keyHandlers.PageUp = (self: Squire) => {\n        self.moveCursorToStart();\n    };\n    keyHandlers.PageDown = (self: Squire) => {\n        self.moveCursorToEnd();\n    };\n}\n\n// ---\n\nconst mapKeyToFormat = (\n    tag: string,\n    remove?: { tag: string } | null,\n): KeyHandler => {\n    remove = remove || null;\n    return (self: Squire, event: Event) => {\n        event.preventDefault();\n        const range = self.getSelection();\n        if (self.hasFormat(tag, null, range)) {\n            self.changeFormat(null, { tag }, range);\n        } else {\n            self.changeFormat({ tag }, remove, range);\n        }\n    };\n};\n\nkeyHandlers[ctrlKey + 'b'] = mapKeyToFormat('B');\nkeyHandlers[ctrlKey + 'i'] = mapKeyToFormat('I');\nkeyHandlers[ctrlKey + 'u'] = mapKeyToFormat('U');\nkeyHandlers[ctrlKey + 'Shift-7'] = mapKeyToFormat('S');\nkeyHandlers[ctrlKey + 'Shift-5'] = mapKeyToFormat('SUB', { tag: 'SUP' });\nkeyHandlers[ctrlKey + 'Shift-6'] = mapKeyToFormat('SUP', { tag: 'SUB' });\n\nkeyHandlers[ctrlKey + 'Shift-8'] = (\n    self: Squire,\n    event: KeyboardEvent,\n): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (!/(?:^|>)UL/.test(path)) {\n        self.makeUnorderedList();\n    } else {\n        self.removeList();\n    }\n};\nkeyHandlers[ctrlKey + 'Shift-9'] = (\n    self: Squire,\n    event: KeyboardEvent,\n): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (!/(?:^|>)OL/.test(path)) {\n        self.makeOrderedList();\n    } else {\n        self.removeList();\n    }\n};\n\nkeyHandlers[ctrlKey + '['] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) {\n        self.decreaseQuoteLevel();\n    } else {\n        self.decreaseListLevel();\n    }\n};\nkeyHandlers[ctrlKey + ']'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) {\n        self.increaseQuoteLevel();\n    } else {\n        self.increaseListLevel();\n    }\n};\n\nkeyHandlers[ctrlKey + 'd'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    self.toggleCode();\n};\n\nkeyHandlers[ctrlKey + 'z'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    self.undo();\n};\nkeyHandlers[ctrlKey + 'y'] =\n    // Depending on platform, the Shift may cause the key to come through as\n    // upper case, but sometimes not. Just add both as shortcuts \u2014 the browser\n    // will only ever fire one or the other.\n    keyHandlers[ctrlKey + 'Shift-z'] =\n    keyHandlers[ctrlKey + 'Shift-Z'] =\n        (self: Squire, event: KeyboardEvent): void => {\n            event.preventDefault();\n            self.redo();\n        };\n\nexport { _onKey, keyHandlers };\n", "import {\n    TreeIterator,\n    SHOW_TEXT,\n    SHOW_ELEMENT_OR_TEXT,\n} from './node/TreeIterator';\nimport {\n    createElement,\n    detach,\n    empty,\n    getNearest,\n    hasTagAttributes,\n    replaceWith,\n} from './node/Node';\nimport {\n    isLeaf,\n    isInline,\n    resetNodeCategoryCache,\n    isContainer,\n    isBlock,\n} from './node/Category';\nimport { isLineBreak, removeZWS } from './node/Whitespace';\nimport {\n    moveRangeBoundariesDownTree,\n    isNodeContainedInRange,\n    moveRangeBoundaryOutOf,\n    moveRangeBoundariesUpTree,\n} from './range/Boundaries';\nimport {\n    createRange,\n    deleteContentsOfRange,\n    extractContentsOfRange,\n    insertNodeInRange,\n    insertTreeFragmentIntoRange,\n} from './range/InsertDelete';\nimport {\n    fixContainer,\n    fixCursor,\n    mergeContainers,\n    mergeInlines,\n    split,\n} from './node/MergeSplit';\nimport { getBlockWalker, getNextBlock, isEmptyBlock } from './node/Block';\nimport { cleanTree, cleanupBRs, escapeHTML, removeEmptyInlines } from './Clean';\nimport { cantFocusEmptyTextNodes, ZWS } from './Constants';\nimport {\n    expandRangeToBlockBoundaries,\n    getEndBlockOfRange,\n    getStartBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n    rangeDoesStartAtBlockBoundary,\n} from './range/Block';\nimport {\n    _monitorShiftKey,\n    _onCopy,\n    _onCut,\n    _onDrop,\n    _onPaste,\n} from './Clipboard';\nimport { keyHandlers, _onKey } from './keyboard/KeyHandlers';\nimport { linkifyText } from './keyboard/KeyHelpers';\nimport { getTextContentsOfRange } from './range/Contents';\n\ndeclare const DOMPurify: any;\n\n// ---\n\ntype EventHandler = { handleEvent: (e: Event) => void } | ((e: Event) => void);\n\ntype KeyHandlerFunction = (x: Squire, y: KeyboardEvent, z: Range) => void;\n\ntype TagAttributes = {\n    [key: string]: { [key: string]: string };\n};\n\ninterface SquireConfig {\n    blockTag: string;\n    blockAttributes: null | Record<string, string>;\n    tagAttributes: TagAttributes;\n    classNames: {\n        color: string;\n        fontFamily: string;\n        fontSize: string;\n        highlight: string;\n    };\n    undo: {\n        documentSizeThreshold: number;\n        undoLimit: number;\n    };\n    addLinks: boolean;\n    willCutCopy: null | ((html: string) => string);\n    toPlainText: null | ((html: string) => string);\n    sanitizeToDOMFragment: (html: string, editor: Squire) => DocumentFragment;\n    didError: (x: any) => void;\n}\n\n// ---\n\nclass Squire {\n    _root: HTMLElement;\n    _config: SquireConfig;\n\n    _isFocused: boolean;\n    _lastSelection: Range;\n    _willRestoreSelection: boolean;\n    _mayHaveZWS: boolean;\n\n    _lastAnchorNode: Node | null;\n    _lastFocusNode: Node | null;\n    _path: string;\n\n    _events: Map<string, Array<EventHandler>>;\n\n    _undoIndex: number;\n    _undoStack: Array<string>;\n    _undoStackLength: number;\n    _isInUndoState: boolean;\n    _ignoreChange: boolean;\n    _ignoreAllChanges: boolean;\n\n    _isShiftDown: boolean;\n    _keyHandlers: Record<string, KeyHandlerFunction>;\n\n    _mutation: MutationObserver;\n\n    constructor(root: HTMLElement, config?: Partial<SquireConfig>) {\n        this._root = root;\n\n        this._config = this._makeConfig(config);\n\n        this._isFocused = false;\n        this._lastSelection = createRange(root, 0);\n        this._willRestoreSelection = false;\n        this._mayHaveZWS = false;\n\n        this._lastAnchorNode = null;\n        this._lastFocusNode = null;\n        this._path = '';\n\n        this._events = new Map();\n\n        this._undoIndex = -1;\n        this._undoStack = [];\n        this._undoStackLength = 0;\n        this._isInUndoState = false;\n        this._ignoreChange = false;\n        this._ignoreAllChanges = false;\n\n        // Add event listeners\n        this.addEventListener('selectionchange', this._updatePathOnEvent);\n\n        // On blur, restore focus except if the user taps or clicks to focus a\n        // specific point. Can't actually use click event because focus happens\n        // before click, so use mousedown/touchstart\n        this.addEventListener('blur', this._enableRestoreSelection);\n        this.addEventListener('mousedown', this._disableRestoreSelection);\n        this.addEventListener('touchstart', this._disableRestoreSelection);\n        this.addEventListener('focus', this._restoreSelection);\n\n        // Clipboard support\n        this._isShiftDown = false;\n        this.addEventListener('cut', _onCut as (e: Event) => void);\n        this.addEventListener('copy', _onCopy as (e: Event) => void);\n        this.addEventListener('paste', _onPaste as (e: Event) => void);\n        this.addEventListener('drop', _onDrop as (e: Event) => void);\n        this.addEventListener(\n            'keydown',\n            _monitorShiftKey as (e: Event) => void,\n        );\n        this.addEventListener('keyup', _monitorShiftKey as (e: Event) => void);\n\n        // Keyboard support\n        this.addEventListener('keydown', _onKey as (e: Event) => void);\n        this._keyHandlers = Object.create(keyHandlers);\n\n        const mutation = new MutationObserver(() => this._docWasChanged());\n        mutation.observe(root, {\n            childList: true,\n            attributes: true,\n            characterData: true,\n            subtree: true,\n        });\n        this._mutation = mutation;\n\n        // Make it editable\n        root.setAttribute('contenteditable', 'true');\n\n        // Modern browsers let you override their default content editable\n        // handling!\n        this.addEventListener(\n            'beforeinput',\n            this._beforeInput as (e: Event) => void,\n        );\n\n        this.setHTML('');\n    }\n\n    destroy(): void {\n        this._events.forEach((_, type) => {\n            this.removeEventListener(type);\n        });\n\n        this._mutation.disconnect();\n\n        this._undoIndex = -1;\n        this._undoStack = [];\n        this._undoStackLength = 0;\n    }\n\n    _makeConfig(userConfig?: object): SquireConfig {\n        const config = {\n            blockTag: 'DIV',\n            blockAttributes: null,\n            tagAttributes: {},\n            classNames: {\n                color: 'color',\n                fontFamily: 'font',\n                fontSize: 'size',\n                highlight: 'highlight',\n            },\n            undo: {\n                documentSizeThreshold: -1, // -1 means no threshold\n                undoLimit: -1, // -1 means no limit\n            },\n            addLinks: true,\n            willCutCopy: null,\n            toPlainText: null,\n            sanitizeToDOMFragment: (\n                html: string,\n                /* editor: Squire, */\n            ): DocumentFragment => {\n                const frag = DOMPurify.sanitize(html, {\n                    ALLOW_UNKNOWN_PROTOCOLS: true,\n                    WHOLE_DOCUMENT: false,\n                    RETURN_DOM: true,\n                    RETURN_DOM_FRAGMENT: true,\n                    FORCE_BODY: false,\n                });\n                return frag\n                    ? document.importNode(frag, true)\n                    : document.createDocumentFragment();\n            },\n            didError: (error: any): void => console.log(error),\n        };\n        if (userConfig) {\n            Object.assign(config, userConfig);\n            config.blockTag = config.blockTag.toUpperCase();\n        }\n\n        return config;\n    }\n\n    setKeyHandler(key: string, fn: KeyHandlerFunction) {\n        this._keyHandlers[key] = fn;\n        return this;\n    }\n\n    _beforeInput(event: InputEvent): void {\n        switch (event.inputType) {\n            case 'insertLineBreak':\n                event.preventDefault();\n                this.splitBlock(true);\n                break;\n            case 'insertParagraph':\n                event.preventDefault();\n                this.splitBlock(false);\n                break;\n            case 'insertOrderedList':\n                event.preventDefault();\n                this.makeOrderedList();\n                break;\n            case 'insertUnoderedList':\n                event.preventDefault();\n                this.makeUnorderedList();\n                break;\n            case 'historyUndo':\n                event.preventDefault();\n                this.undo();\n                break;\n            case 'historyRedo':\n                event.preventDefault();\n                this.redo();\n                break;\n            case 'formatBold':\n                event.preventDefault();\n                this.bold();\n                break;\n            case 'formaItalic':\n                event.preventDefault();\n                this.italic();\n                break;\n            case 'formatUnderline':\n                event.preventDefault();\n                this.underline();\n                break;\n            case 'formatStrikeThrough':\n                event.preventDefault();\n                this.strikethrough();\n                break;\n            case 'formatSuperscript':\n                event.preventDefault();\n                this.superscript();\n                break;\n            case 'formatSubscript':\n                event.preventDefault();\n                this.subscript();\n                break;\n            case 'formatJustifyFull':\n            case 'formatJustifyCenter':\n            case 'formatJustifyRight':\n            case 'formatJustifyLeft': {\n                event.preventDefault();\n                let alignment = event.inputType.slice(13).toLowerCase();\n                if (alignment === 'full') {\n                    alignment = 'justify';\n                }\n                this.setTextAlignment(alignment);\n                break;\n            }\n            case 'formatRemove':\n                event.preventDefault();\n                this.removeAllFormatting();\n                break;\n            case 'formatSetBlockTextDirection': {\n                event.preventDefault();\n                let dir = event.data;\n                if (dir === 'null') {\n                    dir = null;\n                }\n                this.setTextDirection(dir);\n                break;\n            }\n            case 'formatBackColor':\n                event.preventDefault();\n                this.setHighlightColor(event.data);\n                break;\n            case 'formatFontColor':\n                event.preventDefault();\n                this.setTextColor(event.data);\n                break;\n            case 'formatFontName':\n                event.preventDefault();\n                this.setFontFace(event.data);\n                break;\n        }\n    }\n\n    // --- Events\n\n    handleEvent(event: Event): void {\n        this.fireEvent(event.type, event);\n    }\n\n    fireEvent(type: string, detail?: Event | object): Squire {\n        let handlers = this._events.get(type);\n        // UI code, especially modal views, may be monitoring for focus events\n        // and immediately removing focus. In certain conditions, this can\n        // cause the focus event to fire after the blur event, which can cause\n        // an infinite loop. So we detect whether we're actually\n        // focused/blurred before firing.\n        if (/^(?:focus|blur)/.test(type)) {\n            const isFocused = this._root === document.activeElement;\n            if (type === 'focus') {\n                if (!isFocused || this._isFocused) {\n                    return this;\n                }\n                this._isFocused = true;\n            } else {\n                if (isFocused || !this._isFocused) {\n                    return this;\n                }\n                this._isFocused = false;\n            }\n        }\n        if (handlers) {\n            const event: Event =\n                detail instanceof Event\n                    ? detail\n                    : new CustomEvent(type, {\n                          detail,\n                      });\n            // Clone handlers array, so any handlers added/removed do not\n            // affect it.\n            handlers = handlers.slice();\n            for (const handler of handlers) {\n                try {\n                    if ('handleEvent' in handler) {\n                        handler.handleEvent(event);\n                    } else {\n                        handler.call(this, event);\n                    }\n                } catch (error) {\n                    this._config.didError(error);\n                }\n            }\n        }\n        return this;\n    }\n\n    /**\n     * Subscribing to these events won't automatically add a listener to the\n     * document node, since these events are fired in a custom manner by the\n     * editor code.\n     */\n    customEvents = new Set([\n        'pathChange',\n        'select',\n        'input',\n        'pasteImage',\n        'undoStateChange',\n    ]);\n\n    addEventListener(type: string, fn: EventHandler): Squire {\n        let handlers = this._events.get(type);\n        let target: Document | HTMLElement = this._root;\n        if (!handlers) {\n            handlers = [];\n            this._events.set(type, handlers);\n            if (!this.customEvents.has(type)) {\n                if (type === 'selectionchange') {\n                    target = document;\n                }\n                target.addEventListener(type, this, true);\n            }\n        }\n        handlers.push(fn);\n        return this;\n    }\n\n    removeEventListener(type: string, fn?: EventHandler): Squire {\n        const handlers = this._events.get(type);\n        let target: Document | HTMLElement = this._root;\n        if (handlers) {\n            if (fn) {\n                let l = handlers.length;\n                while (l--) {\n                    if (handlers[l] === fn) {\n                        handlers.splice(l, 1);\n                    }\n                }\n            } else {\n                handlers.length = 0;\n            }\n            if (!handlers.length) {\n                this._events.delete(type);\n                if (!this.customEvents.has(type)) {\n                    if (type === 'selectionchange') {\n                        target = document;\n                    }\n                    target.removeEventListener(type, this, true);\n                }\n            }\n        }\n        return this;\n    }\n\n    // --- Focus\n\n    focus(): Squire {\n        this._root.focus({ preventScroll: true });\n        return this;\n    }\n\n    blur(): Squire {\n        this._root.blur();\n        return this;\n    }\n\n    // --- Selection and bookmarking\n\n    _enableRestoreSelection(): void {\n        this._willRestoreSelection = true;\n    }\n\n    _disableRestoreSelection(): void {\n        this._willRestoreSelection = false;\n    }\n\n    _restoreSelection() {\n        if (this._willRestoreSelection) {\n            this.setSelection(this._lastSelection);\n        }\n    }\n\n    // ---\n\n    _removeZWS(): void {\n        if (!this._mayHaveZWS) {\n            return;\n        }\n        removeZWS(this._root);\n        this._mayHaveZWS = false;\n    }\n\n    // ---\n\n    startSelectionId = 'squire-selection-start';\n    endSelectionId = 'squire-selection-end';\n\n    _saveRangeToBookmark(range: Range): void {\n        let startNode = createElement('INPUT', {\n            id: this.startSelectionId,\n            type: 'hidden',\n        });\n        let endNode = createElement('INPUT', {\n            id: this.endSelectionId,\n            type: 'hidden',\n        });\n        let temp: HTMLElement;\n\n        insertNodeInRange(range, startNode);\n        range.collapse(false);\n        insertNodeInRange(range, endNode);\n\n        // In a collapsed range, the start is sometimes inserted after the end!\n        if (\n            startNode.compareDocumentPosition(endNode) &\n            Node.DOCUMENT_POSITION_PRECEDING\n        ) {\n            startNode.id = this.endSelectionId;\n            endNode.id = this.startSelectionId;\n            temp = startNode;\n            startNode = endNode;\n            endNode = temp;\n        }\n\n        range.setStartAfter(startNode);\n        range.setEndBefore(endNode);\n    }\n\n    _getRangeAndRemoveBookmark(range?: Range): Range | null {\n        const root = this._root;\n        const start = root.querySelector('#' + this.startSelectionId);\n        const end = root.querySelector('#' + this.endSelectionId);\n\n        if (start && end) {\n            let startContainer: Node = start.parentNode!;\n            let endContainer: Node = end.parentNode!;\n            const startOffset = Array.from(startContainer.childNodes).indexOf(\n                start,\n            );\n            let endOffset = Array.from(endContainer.childNodes).indexOf(end);\n\n            if (startContainer === endContainer) {\n                endOffset -= 1;\n            }\n\n            start.remove();\n            end.remove();\n\n            if (!range) {\n                range = document.createRange();\n            }\n            range.setStart(startContainer, startOffset);\n            range.setEnd(endContainer, endOffset);\n\n            // Merge any text nodes we split\n            mergeInlines(startContainer, range);\n            if (startContainer !== endContainer) {\n                mergeInlines(endContainer, range);\n            }\n\n            // If we didn't split a text node, we should move into any adjacent\n            // text node to current selection point\n            if (range.collapsed) {\n                startContainer = range.startContainer;\n                if (startContainer instanceof Text) {\n                    endContainer = startContainer.childNodes[range.startOffset];\n                    if (!endContainer || !(endContainer instanceof Text)) {\n                        endContainer =\n                            startContainer.childNodes[range.startOffset - 1];\n                    }\n                    if (endContainer && endContainer instanceof Text) {\n                        range.setStart(endContainer, 0);\n                        range.collapse(true);\n                    }\n                }\n            }\n        }\n        return range || null;\n    }\n\n    getSelection(): Range {\n        const selection = window.getSelection();\n        const root = this._root;\n        let range: Range | null = null;\n        // If not focused, always rely on cached selection; another function may\n        // have set it but the DOM is not modified until focus again\n        if (this._isFocused && selection && selection.rangeCount) {\n            range = selection.getRangeAt(0).cloneRange();\n            const startContainer = range.startContainer;\n            const endContainer = range.endContainer;\n            // FF can return the selection as being inside an <img>. WTF?\n            if (startContainer && isLeaf(startContainer)) {\n                range.setStartBefore(startContainer);\n            }\n            if (endContainer && isLeaf(endContainer)) {\n                range.setEndBefore(endContainer);\n            }\n        }\n        if (range && root.contains(range.commonAncestorContainer)) {\n            this._lastSelection = range;\n        } else {\n            range = this._lastSelection;\n            // Check the editor is in the live document; if not, the range has\n            // probably been rewritten by the browser and is bogus\n            if (!document.contains(range.commonAncestorContainer)) {\n                range = null;\n            }\n        }\n        if (!range) {\n            range = createRange(root.firstElementChild || root, 0);\n        }\n        return range;\n    }\n\n    setSelection(range: Range): Squire {\n        this._lastSelection = range;\n        // If we're setting selection, that automatically, and synchronously,\n        // triggers a focus event. So just store the selection and mark it as\n        // needing restore on focus.\n        if (!this._isFocused) {\n            this._enableRestoreSelection();\n        } else {\n            const selection = window.getSelection();\n            if (selection) {\n                if ('setBaseAndExtent' in Selection.prototype) {\n                    selection.setBaseAndExtent(\n                        range.startContainer,\n                        range.startOffset,\n                        range.endContainer,\n                        range.endOffset,\n                    );\n                } else {\n                    selection.removeAllRanges();\n                    selection.addRange(range);\n                }\n            }\n        }\n        return this;\n    }\n\n    // ---\n\n    _moveCursorTo(toStart: boolean): Squire {\n        const root = this._root;\n        const range = createRange(root, toStart ? 0 : root.childNodes.length);\n        moveRangeBoundariesDownTree(range);\n        this.setSelection(range);\n        return this;\n    }\n\n    moveCursorToStart(): Squire {\n        return this._moveCursorTo(true);\n    }\n\n    moveCursorToEnd(): Squire {\n        return this._moveCursorTo(false);\n    }\n\n    // ---\n\n    getCursorPosition(): DOMRect {\n        const range = this.getSelection();\n        let rect = range.getBoundingClientRect();\n        // If the range is outside of the viewport, some browsers at least\n        // will return 0 for all the values; need to get a DOM node to find\n        // the position instead.\n        if (rect && !rect.top) {\n            this._ignoreChange = true;\n            const node = createElement('SPAN');\n            node.textContent = ZWS;\n            insertNodeInRange(range, node);\n            rect = node.getBoundingClientRect();\n            const parent = node.parentNode!;\n            parent.removeChild(node);\n            mergeInlines(parent, range);\n        }\n        return rect;\n    }\n\n    // --- Path\n\n    getPath(): string {\n        return this._path;\n    }\n\n    _updatePathOnEvent(): void {\n        if (this._isFocused) {\n            this._updatePath(this.getSelection());\n        }\n    }\n\n    _updatePath(range: Range, force?: boolean): void {\n        const anchor = range.startContainer;\n        const focus = range.endContainer;\n        let newPath: string;\n        if (\n            force ||\n            anchor !== this._lastAnchorNode ||\n            focus !== this._lastFocusNode\n        ) {\n            this._lastAnchorNode = anchor;\n            this._lastFocusNode = focus;\n            newPath =\n                anchor && focus\n                    ? anchor === focus\n                        ? this._getPath(focus)\n                        : '(selection)'\n                    : '';\n            if (this._path !== newPath) {\n                this._path = newPath;\n                this.fireEvent('pathChange', {\n                    path: newPath,\n                });\n            }\n        }\n        this.fireEvent(range.collapsed ? 'cursor' : 'select', {\n            range: range,\n        });\n    }\n\n    _getPath(node: Node) {\n        const root = this._root;\n        const config = this._config;\n        let path = '';\n        if (node && node !== root) {\n            const parent = node.parentNode;\n            path = parent ? this._getPath(parent) : '';\n            if (node instanceof HTMLElement) {\n                const id = node.id;\n                const classList = node.classList;\n                const classNames = Array.from(classList).sort();\n                const dir = node.dir;\n                const styleNames = config.classNames;\n                path += (path ? '>' : '') + node.nodeName;\n                if (id) {\n                    path += '#' + id;\n                }\n                if (classNames.length) {\n                    path += '.';\n                    path += classNames.join('.');\n                }\n                if (dir) {\n                    path += '[dir=' + dir + ']';\n                }\n                if (classList.contains(styleNames.highlight)) {\n                    path +=\n                        '[backgroundColor=' +\n                        node.style.backgroundColor.replace(/ /g, '') +\n                        ']';\n                }\n                if (classList.contains(styleNames.color)) {\n                    path +=\n                        '[color=' + node.style.color.replace(/ /g, '') + ']';\n                }\n                if (classList.contains(styleNames.fontFamily)) {\n                    path +=\n                        '[fontFamily=' +\n                        node.style.fontFamily.replace(/ /g, '') +\n                        ']';\n                }\n                if (classList.contains(styleNames.fontSize)) {\n                    path += '[fontSize=' + node.style.fontSize + ']';\n                }\n            }\n        }\n        return path;\n    }\n\n    // --- History\n\n    modifyDocument(modificationFn: () => void): Squire {\n        const mutation = this._mutation;\n        if (mutation) {\n            if (mutation.takeRecords().length) {\n                this._docWasChanged();\n            }\n            mutation.disconnect();\n        }\n\n        this._ignoreAllChanges = true;\n        modificationFn();\n        this._ignoreAllChanges = false;\n\n        if (mutation) {\n            mutation.observe(this._root, {\n                childList: true,\n                attributes: true,\n                characterData: true,\n                subtree: true,\n            });\n            this._ignoreChange = false;\n        }\n\n        return this;\n    }\n\n    _docWasChanged(): void {\n        resetNodeCategoryCache();\n        this._mayHaveZWS = true;\n        if (this._ignoreAllChanges) {\n            return;\n        }\n\n        if (this._ignoreChange) {\n            this._ignoreChange = false;\n            return;\n        }\n        if (this._isInUndoState) {\n            this._isInUndoState = false;\n            this.fireEvent('undoStateChange', {\n                canUndo: true,\n                canRedo: false,\n            });\n        }\n        this.fireEvent('input');\n    }\n\n    /**\n     * Leaves bookmark.\n     */\n    _recordUndoState(range: Range, replace?: boolean): Squire {\n        const isInUndoState = this._isInUndoState;\n        if (!isInUndoState || replace) {\n            // Advance pointer to new position\n            let undoIndex = this._undoIndex + 1;\n            const undoStack = this._undoStack;\n            const undoConfig = this._config.undo;\n            const undoThreshold = undoConfig.documentSizeThreshold;\n            const undoLimit = undoConfig.undoLimit;\n\n            // Truncate stack if longer (i.e. if has been previously undone)\n            if (undoIndex < this._undoStackLength) {\n                undoStack.length = this._undoStackLength = undoIndex;\n            }\n\n            // Add bookmark\n            if (range) {\n                this._saveRangeToBookmark(range);\n            }\n\n            // Don't record if we're already in an undo state\n            if (isInUndoState) {\n                return this;\n            }\n\n            // Get data\n            const html = this._getRawHTML();\n\n            // If this document is above the configured size threshold,\n            // limit the number of saved undo states.\n            // Threshold is in bytes, JS uses 2 bytes per character\n            if (replace) {\n                undoIndex -= 1;\n            }\n            if (undoThreshold > -1 && html.length * 2 > undoThreshold) {\n                if (undoLimit > -1 && undoIndex > undoLimit) {\n                    undoStack.splice(0, undoIndex - undoLimit);\n                    undoIndex = undoLimit;\n                    this._undoStackLength = undoLimit;\n                }\n            }\n\n            // Save data\n            undoStack[undoIndex] = html;\n            this._undoIndex = undoIndex;\n            this._undoStackLength += 1;\n            this._isInUndoState = true;\n        }\n        return this;\n    }\n\n    saveUndoState(range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        this._recordUndoState(range, this._isInUndoState);\n        this._getRangeAndRemoveBookmark(range);\n\n        return this;\n    }\n\n    undo(): Squire {\n        // Sanity check: must not be at beginning of the history stack\n        if (this._undoIndex !== 0 || !this._isInUndoState) {\n            // Make sure any changes since last checkpoint are saved.\n            this._recordUndoState(this.getSelection(), false);\n            this._undoIndex -= 1;\n            this._setRawHTML(this._undoStack[this._undoIndex]);\n            const range = this._getRangeAndRemoveBookmark();\n            if (range) {\n                this.setSelection(range);\n            }\n            this._isInUndoState = true;\n            this.fireEvent('undoStateChange', {\n                canUndo: this._undoIndex !== 0,\n                canRedo: true,\n            });\n            this.fireEvent('input');\n        }\n        return this.focus();\n    }\n\n    redo(): Squire {\n        // Sanity check: must not be at end of stack and must be in an undo\n        // state.\n        const undoIndex = this._undoIndex;\n        const undoStackLength = this._undoStackLength;\n        if (undoIndex + 1 < undoStackLength && this._isInUndoState) {\n            this._undoIndex += 1;\n            this._setRawHTML(this._undoStack[this._undoIndex]);\n            const range = this._getRangeAndRemoveBookmark();\n            if (range) {\n                this.setSelection(range);\n            }\n            this.fireEvent('undoStateChange', {\n                canUndo: true,\n                canRedo: undoIndex + 2 < undoStackLength,\n            });\n            this.fireEvent('input');\n        }\n        return this.focus();\n    }\n\n    // --- Get and set data\n\n    getRoot(): HTMLElement {\n        return this._root;\n    }\n\n    _getRawHTML(): string {\n        return this._root.innerHTML;\n    }\n\n    _setRawHTML(html: string): Squire {\n        const root = this._root;\n        root.innerHTML = html;\n\n        let node: Element | null = root;\n        const child = node.firstChild;\n        if (!child || child.nodeName === 'BR') {\n            const block = this.createDefaultBlock();\n            if (child) {\n                node.replaceChild(block, child);\n            } else {\n                node.appendChild(block);\n            }\n        } else {\n            while ((node = getNextBlock(node, root))) {\n                fixCursor(node);\n            }\n        }\n\n        this._ignoreChange = true;\n\n        return this;\n    }\n\n    getHTML(withBookmark?: boolean): string {\n        let range: Range | undefined;\n        if (withBookmark) {\n            range = this.getSelection();\n            this._saveRangeToBookmark(range);\n        }\n        const html = this._getRawHTML().replace(/\\u200B/g, '');\n        if (withBookmark) {\n            this._getRangeAndRemoveBookmark(range);\n        }\n        return html;\n    }\n\n    setHTML(html: string): Squire {\n        // Parse HTML into DOM tree\n        const frag = this._config.sanitizeToDOMFragment(html, this);\n        const root = this._root;\n\n        // Fixup DOM tree\n        cleanTree(frag, this._config);\n        cleanupBRs(frag, root, false);\n        fixContainer(frag, root);\n\n        // Fix cursor\n        let node: DocumentFragment | HTMLElement | null = frag;\n        let child = node.firstChild;\n        if (!child || child.nodeName === 'BR') {\n            const block = this.createDefaultBlock();\n            if (child) {\n                node.replaceChild(block, child);\n            } else {\n                node.appendChild(block);\n            }\n        } else {\n            while ((node = getNextBlock(node, root))) {\n                fixCursor(node);\n            }\n        }\n\n        // Don't fire an input event\n        this._ignoreChange = true;\n\n        // Remove existing root children and insert new content\n        while ((child = root.lastChild)) {\n            root.removeChild(child);\n        }\n        root.appendChild(frag);\n\n        // Reset the undo stack\n        this._undoIndex = -1;\n        this._undoStack.length = 0;\n        this._undoStackLength = 0;\n        this._isInUndoState = false;\n\n        // Record undo state\n        const range =\n            this._getRangeAndRemoveBookmark() ||\n            createRange(root.firstElementChild || root, 0);\n        this.saveUndoState(range);\n\n        // Set inital selection\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    /**\n     * Insert HTML at the cursor location. If the selection is not collapsed\n     * insertTreeFragmentIntoRange will delete the selection so that it is\n     * replaced by the html being inserted.\n     */\n    insertHTML(html: string, isPaste?: boolean): Squire {\n        // Parse\n        const config = this._config;\n        let frag = config.sanitizeToDOMFragment(html, this);\n\n        // Record undo checkpoint\n        const range = this.getSelection();\n        this.saveUndoState(range);\n\n        try {\n            const root = this._root;\n\n            if (config.addLinks) {\n                this.addDetectedLinks(frag, frag);\n            }\n            cleanTree(frag, this._config);\n            cleanupBRs(frag, root, false);\n            removeEmptyInlines(frag);\n            frag.normalize();\n\n            let node: HTMLElement | DocumentFragment | null = frag;\n            while ((node = getNextBlock(node, frag))) {\n                fixCursor(node);\n            }\n\n            let doInsert = true;\n            if (isPaste) {\n                const event = new CustomEvent('willPaste', {\n                    cancelable: true,\n                    detail: {\n                        fragment: frag,\n                    },\n                });\n                this.fireEvent('willPaste', event);\n                frag = event.detail.fragment;\n                doInsert = !event.defaultPrevented;\n            }\n\n            if (doInsert) {\n                insertTreeFragmentIntoRange(range, frag, root);\n                range.collapse(false);\n\n                // After inserting the fragment, check whether the cursor is\n                // inside an <a> element and if so if there is an equivalent\n                // cursor position after the <a> element. If there is, move it\n                // there.\n                moveRangeBoundaryOutOf(range, 'A', root);\n\n                this._ensureBottomLine();\n            }\n\n            this.setSelection(range);\n            this._updatePath(range, true);\n            // Safari sometimes loses focus after paste. Weird.\n            if (isPaste) {\n                this.focus();\n            }\n        } catch (error) {\n            this._config.didError(error);\n        }\n        return this;\n    }\n\n    insertElement(el: Element, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        range.collapse(true);\n        if (isInline(el)) {\n            insertNodeInRange(range, el);\n            range.setStartAfter(el);\n        } else {\n            // Get containing block node.\n            const root = this._root;\n            const startNode: HTMLElement | null = getStartBlockOfRange(\n                range,\n                root,\n            );\n            let splitNode: Element | Node = startNode || root;\n\n            let nodeAfterSplit: Node | null = null;\n            // While at end of container node, move up DOM tree.\n            while (splitNode !== root && !splitNode.nextSibling) {\n                splitNode = splitNode.parentNode!;\n            }\n            // If in the middle of a container node, split up to root.\n            if (splitNode !== root) {\n                const parent = splitNode.parentNode!;\n                nodeAfterSplit = split(\n                    parent,\n                    splitNode.nextSibling,\n                    root,\n                    root,\n                ) as Node;\n            }\n\n            // If the startNode was empty remove it so that we don't end up\n            // with two blank lines.\n            if (startNode && isEmptyBlock(startNode)) {\n                detach(startNode);\n            }\n\n            // Insert element and blank line.\n            root.insertBefore(el, nodeAfterSplit);\n            const blankLine = this.createDefaultBlock();\n            root.insertBefore(blankLine, nodeAfterSplit);\n\n            // Move cursor to blank line after inserted element.\n            range.setStart(blankLine, 0);\n            range.setEnd(blankLine, 0);\n            moveRangeBoundariesDownTree(range);\n        }\n        this.focus();\n        this.setSelection(range);\n        this._updatePath(range);\n\n        return this;\n    }\n\n    insertImage(\n        src: string,\n        attributes: Record<string, string>,\n    ): HTMLImageElement {\n        const img = createElement(\n            'IMG',\n            Object.assign(\n                {\n                    src: src,\n                },\n                attributes,\n            ),\n        ) as HTMLImageElement;\n        this.insertElement(img);\n        return img;\n    }\n\n    insertPlainText(plainText: string, isPaste: boolean): Squire {\n        const range = this.getSelection();\n        if (\n            range.collapsed &&\n            getNearest(range.startContainer, this._root, 'PRE')\n        ) {\n            const startContainer: Node = range.startContainer;\n            let offset = range.startOffset;\n            let textNode: Text;\n            if (!startContainer || !(startContainer instanceof Text)) {\n                const text = document.createTextNode('');\n                startContainer.insertBefore(\n                    text,\n                    startContainer.childNodes[offset],\n                );\n                textNode = text;\n                offset = 0;\n            } else {\n                textNode = startContainer;\n            }\n            let doInsert = true;\n            if (isPaste) {\n                const event = new CustomEvent('willPaste', {\n                    cancelable: true,\n                    detail: {\n                        text: plainText,\n                    },\n                });\n                this.fireEvent('willPaste', event);\n                plainText = event.detail.text;\n                doInsert = !event.defaultPrevented;\n            }\n\n            if (doInsert) {\n                textNode.insertData(offset, plainText);\n                range.setStart(textNode, offset + plainText.length);\n                range.collapse(true);\n            }\n            this.setSelection(range);\n            return this;\n        }\n        const lines = plainText.split('\\n');\n        const config = this._config;\n        const tag = config.blockTag;\n        const attributes = config.blockAttributes;\n        const closeBlock = '</' + tag + '>';\n        let openBlock = '<' + tag;\n\n        for (const attr in attributes) {\n            openBlock += ' ' + attr + '=\"' + escapeHTML(attributes[attr]) + '\"';\n        }\n        openBlock += '>';\n\n        for (let i = 0, l = lines.length; i < l; i += 1) {\n            let line = lines[i];\n            line = escapeHTML(line).replace(/ (?=(?: |$))/g, '&nbsp;');\n            // We don't wrap the first line in the block, so if it gets inserted\n            // into a blank line it keeps that line's formatting.\n            // Wrap each line in <div></div>\n            if (i) {\n                line = openBlock + (line || '<BR>') + closeBlock;\n            }\n            lines[i] = line;\n        }\n        return this.insertHTML(lines.join(''), isPaste);\n    }\n\n    getSelectedText(range?: Range): string {\n        return getTextContentsOfRange(range || this.getSelection());\n    }\n\n    // --- Inline formatting\n\n    /**\n     * Extracts the font-family and font-size (if any) of the element\n     * holding the cursor. If there's a selection, returns an empty object.\n     */\n    getFontInfo(range?: Range): Record<string, string | undefined> {\n        const fontInfo = {\n            color: undefined,\n            backgroundColor: undefined,\n            fontFamily: undefined,\n            fontSize: undefined,\n        } as Record<string, string | undefined>;\n\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        let seenAttributes = 0;\n        let element: Node | null = range.commonAncestorContainer;\n        if (range.collapsed || element instanceof Text) {\n            if (element instanceof Text) {\n                element = element.parentNode!;\n            }\n            while (seenAttributes < 4 && element) {\n                const style = (element as HTMLElement).style;\n                if (style) {\n                    const color = style.color;\n                    if (!fontInfo.color && color) {\n                        fontInfo.color = color;\n                        seenAttributes += 1;\n                    }\n                    const backgroundColor = style.backgroundColor;\n                    if (!fontInfo.backgroundColor && backgroundColor) {\n                        fontInfo.backgroundColor = backgroundColor;\n                        seenAttributes += 1;\n                    }\n                    const fontFamily = style.fontFamily;\n                    if (!fontInfo.fontFamily && fontFamily) {\n                        fontInfo.fontFamily = fontFamily;\n                        seenAttributes += 1;\n                    }\n                    const fontSize = style.fontSize;\n                    if (!fontInfo.fontSize && fontSize) {\n                        fontInfo.fontSize = fontSize;\n                        seenAttributes += 1;\n                    }\n                }\n                element = element.parentNode;\n            }\n        }\n        return fontInfo;\n    }\n\n    /**\n     * Looks for matching tag and attributes, so won't work if <strong>\n     * instead of <b> etc.\n     */\n    hasFormat(\n        tag: string,\n        attributes?: Record<string, string> | null,\n        range?: Range,\n    ): boolean {\n        // 1. Normalise the arguments and get selection\n        tag = tag.toUpperCase();\n        if (!attributes) {\n            attributes = {};\n        }\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Move range up one level in the DOM tree if at the edge of a text\n        // node, so we don't consider it included when it's not really.\n        if (\n            !range.collapsed &&\n            range.startContainer instanceof Text &&\n            range.startOffset === range.startContainer.length &&\n            range.startContainer.nextSibling\n        ) {\n            range.setStartBefore(range.startContainer.nextSibling);\n        }\n        if (\n            !range.collapsed &&\n            range.endContainer instanceof Text &&\n            range.endOffset === 0 &&\n            range.endContainer.previousSibling\n        ) {\n            range.setEndAfter(range.endContainer.previousSibling);\n        }\n\n        // If the common ancestor is inside the tag we require, we definitely\n        // have the format.\n        const root = this._root;\n        const common = range.commonAncestorContainer;\n        if (getNearest(common, root, tag, attributes)) {\n            return true;\n        }\n\n        // If common ancestor is a text node and doesn't have the format, we\n        // definitely don't have it.\n        if (common instanceof Text) {\n            return false;\n        }\n\n        // Otherwise, check each text node at least partially contained within\n        // the selection and make sure all of them have the format we want.\n        const walker = new TreeIterator<Text>(common, SHOW_TEXT, (node) => {\n            return isNodeContainedInRange(range!, node, true);\n        });\n\n        let seenNode = false;\n        let node: Node | null;\n        while ((node = walker.nextNode())) {\n            if (!getNearest(node, root, tag, attributes)) {\n                return false;\n            }\n            seenNode = true;\n        }\n\n        return seenNode;\n    }\n\n    changeFormat(\n        add: { tag: string; attributes?: Record<string, string> } | null,\n        remove?: { tag: string; attributes?: Record<string, string> } | null,\n        range?: Range,\n        partial?: boolean,\n    ): Squire {\n        // Normalise the arguments and get selection\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Save undo checkpoint\n        this.saveUndoState(range);\n\n        if (remove) {\n            range = this._removeFormat(\n                remove.tag.toUpperCase(),\n                remove.attributes || {},\n                range,\n                partial,\n            );\n        }\n        if (add) {\n            range = this._addFormat(\n                add.tag.toUpperCase(),\n                add.attributes || {},\n                range,\n            );\n        }\n\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    _addFormat(\n        tag: string,\n        attributes: Record<string, string> | null,\n        range: Range,\n    ): Range {\n        // If the range is collapsed we simply insert the node by wrapping\n        // it round the range and focus it.\n        const root = this._root;\n        if (range.collapsed) {\n            const el = fixCursor(createElement(tag, attributes));\n            insertNodeInRange(range, el);\n            const focusNode = el.firstChild || el;\n            // Focus after the ZWS if present\n            const focusOffset =\n                focusNode instanceof Text ? focusNode.length : 0;\n            range.setStart(focusNode, focusOffset);\n            range.collapse(true);\n\n            // Clean up any previous formats that may have been set on this\n            // block that are unused.\n            let block = el;\n            while (isInline(block)) {\n                block = block.parentNode!;\n            }\n            removeZWS(block, el);\n            // Otherwise we find all the textnodes in the range (splitting\n            // partially selected nodes) and if they're not already formatted\n            // correctly we wrap them in the appropriate tag.\n        } else {\n            // Create an iterator to walk over all the text nodes under this\n            // ancestor which are in the range and not already formatted\n            // correctly.\n            //\n            // In Blink/WebKit, empty blocks may have no text nodes, just a\n            // <br>. Therefore we wrap this in the tag as well, as this will\n            // then cause it to apply when the user types something in the\n            // block, which is presumably what was intended.\n            //\n            // IMG tags are included because we may want to create a link around\n            // them, and adding other styles is harmless.\n            const walker = new TreeIterator<Element | Text>(\n                range.commonAncestorContainer,\n                SHOW_ELEMENT_OR_TEXT,\n                (node: Node) => {\n                    return (\n                        (node instanceof Text ||\n                            node.nodeName === 'BR' ||\n                            node.nodeName === 'IMG') &&\n                        isNodeContainedInRange(range, node, true)\n                    );\n                },\n            );\n\n            // Start at the beginning node of the range and iterate through\n            // all the nodes in the range that need formatting.\n            let { startContainer, startOffset, endContainer, endOffset } =\n                range;\n\n            // Make sure we start with a valid node.\n            walker.currentNode = startContainer;\n            if (\n                (!(startContainer instanceof Element) &&\n                    !(startContainer instanceof Text)) ||\n                !walker.filter(startContainer)\n            ) {\n                const next = walker.nextNode();\n                // If there are no interesting nodes in the selection, abort\n                if (!next) {\n                    return range;\n                }\n                startContainer = next;\n                startOffset = 0;\n            }\n\n            do {\n                let node = walker.currentNode;\n                const needsFormat = !getNearest(node, root, tag, attributes);\n                if (needsFormat) {\n                    // <br> can never be a container node, so must have a text\n                    // node if node == (end|start)Container\n                    if (\n                        node === endContainer &&\n                        (node as Text).length > endOffset\n                    ) {\n                        (node as Text).splitText(endOffset);\n                    }\n                    if (node === startContainer && startOffset) {\n                        node = (node as Text).splitText(startOffset);\n                        if (endContainer === startContainer) {\n                            endContainer = node;\n                            endOffset -= startOffset;\n                        } else if (endContainer === startContainer.parentNode) {\n                            endOffset += 1;\n                        }\n                        startContainer = node;\n                        startOffset = 0;\n                    }\n                    const el = createElement(tag, attributes);\n                    replaceWith(node, el);\n                    el.appendChild(node);\n                }\n            } while (walker.nextNode());\n\n            // Now set the selection to as it was before\n            range = createRange(\n                startContainer,\n                startOffset,\n                endContainer,\n                endOffset,\n            );\n        }\n        return range;\n    }\n\n    _removeFormat(\n        tag: string,\n        attributes: Record<string, string>,\n        range: Range,\n        partial?: boolean,\n    ): Range {\n        // Add bookmark\n        this._saveRangeToBookmark(range);\n\n        // We need a node in the selection to break the surrounding\n        // formatted text.\n        let fixer: Node | Text | null | undefined;\n        if (range.collapsed) {\n            if (cantFocusEmptyTextNodes) {\n                fixer = document.createTextNode(ZWS);\n            } else {\n                fixer = document.createTextNode('');\n            }\n            insertNodeInRange(range, fixer!);\n        }\n\n        // Find block-level ancestor of selection\n        let root = range.commonAncestorContainer;\n        while (isInline(root)) {\n            root = root.parentNode!;\n        }\n\n        // Find text nodes inside formatTags that are not in selection and\n        // add an extra tag with the same formatting.\n        const startContainer = range.startContainer;\n        const startOffset = range.startOffset;\n        const endContainer = range.endContainer;\n        const endOffset = range.endOffset;\n        const toWrap: [Node, Node][] = [];\n        const examineNode = (node: Node, exemplar: Node) => {\n            // If the node is completely contained by the range then\n            // we're going to remove all formatting so ignore it.\n            if (isNodeContainedInRange(range, node, false)) {\n                return;\n            }\n\n            let child: Node;\n            let next: Node;\n\n            // If not at least partially contained, wrap entire contents\n            // in a clone of the tag we're removing and we're done.\n            if (!isNodeContainedInRange(range, node, true)) {\n                // Ignore bookmarks and empty text nodes\n                if (\n                    !(node instanceof HTMLInputElement) &&\n                    (!(node instanceof Text) || node.data)\n                ) {\n                    toWrap.push([exemplar, node]);\n                }\n                return;\n            }\n\n            // Split any partially selected text nodes.\n            if (node instanceof Text) {\n                if (node === endContainer && endOffset !== node.length) {\n                    toWrap.push([exemplar, node.splitText(endOffset)]);\n                }\n                if (node === startContainer && startOffset) {\n                    node.splitText(startOffset);\n                    toWrap.push([exemplar, node]);\n                }\n            } else {\n                // If not a text node, recurse onto all children.\n                // Beware, the tree may be rewritten with each call\n                // to examineNode, hence find the next sibling first.\n                for (child = node.firstChild!; child; child = next) {\n                    next = child.nextSibling!;\n                    examineNode(child, exemplar);\n                }\n            }\n        };\n        const formatTags = Array.from(\n            (root as Element).getElementsByTagName(tag),\n        ).filter((el: Node): boolean => {\n            return (\n                isNodeContainedInRange(range, el, true) &&\n                hasTagAttributes(el, tag, attributes)\n            );\n        });\n\n        if (!partial) {\n            formatTags.forEach((node: Node) => {\n                examineNode(node, node);\n            });\n        }\n\n        // Now wrap unselected nodes in the tag\n        toWrap.forEach(([el, node]) => {\n            el = el.cloneNode(false);\n            replaceWith(node, el);\n            el.appendChild(node);\n        });\n        // and remove old formatting tags.\n        formatTags.forEach((el: Element) => {\n            replaceWith(el, empty(el));\n        });\n\n        if (cantFocusEmptyTextNodes && fixer) {\n            // Clean up any previous ZWS in this block. They are not needed,\n            // and this works around a Chrome bug where it doesn't render the\n            // text in some situations with multiple ZWS(!)\n            fixer = fixer.parentNode;\n            let block = fixer;\n            while (block && isInline(block)) {\n                block = block.parentNode;\n            }\n            if (block) {\n                removeZWS(block, fixer);\n            }\n        }\n\n        // Merge adjacent inlines:\n        this._getRangeAndRemoveBookmark(range);\n        if (fixer) {\n            range.collapse(false);\n        }\n        mergeInlines(root, range);\n\n        return range;\n    }\n\n    // ---\n\n    bold(): Squire {\n        return this.changeFormat({ tag: 'B' });\n    }\n\n    removeBold(): Squire {\n        return this.changeFormat(null, { tag: 'B' });\n    }\n\n    italic(): Squire {\n        return this.changeFormat({ tag: 'I' });\n    }\n\n    removeItalic(): Squire {\n        return this.changeFormat(null, { tag: 'I' });\n    }\n\n    underline(): Squire {\n        return this.changeFormat({ tag: 'U' });\n    }\n\n    removeUnderline(): Squire {\n        return this.changeFormat(null, { tag: 'U' });\n    }\n\n    strikethrough(): Squire {\n        return this.changeFormat({ tag: 'S' });\n    }\n\n    removeStrikethrough(): Squire {\n        return this.changeFormat(null, { tag: 'S' });\n    }\n\n    subscript(): Squire {\n        return this.changeFormat({ tag: 'SUB' }, { tag: 'SUP' });\n    }\n\n    removeSubscript(): Squire {\n        return this.changeFormat(null, { tag: 'SUB' });\n    }\n\n    superscript(): Squire {\n        return this.changeFormat({ tag: 'SUP' }, { tag: 'SUB' });\n    }\n\n    removeSuperscript(): Squire {\n        return this.changeFormat(null, { tag: 'SUP' });\n    }\n\n    // ---\n\n    makeLink(url: string, attributes?: Record<string, string>): Squire {\n        const range = this.getSelection();\n        if (range.collapsed) {\n            let protocolEnd = url.indexOf(':') + 1;\n            if (protocolEnd) {\n                while (url[protocolEnd] === '/') {\n                    protocolEnd += 1;\n                }\n            }\n            insertNodeInRange(\n                range,\n                document.createTextNode(url.slice(protocolEnd)),\n            );\n        }\n        attributes = Object.assign(\n            {\n                href: url,\n            },\n            this._config.tagAttributes.a,\n            attributes,\n        );\n\n        return this.changeFormat(\n            {\n                tag: 'A',\n                attributes: attributes as Record<string, string>,\n            },\n            {\n                tag: 'A',\n            },\n            range,\n        );\n    }\n\n    removeLink(): Squire {\n        return this.changeFormat(\n            null,\n            {\n                tag: 'A',\n            },\n            this.getSelection(),\n            true,\n        );\n    }\n\n    /*\n    linkRegExp = new RegExp(\n        // Only look on boundaries\n        '\\\\b(?:' +\n        // Capture group 1: URLs\n        '(' +\n            // Add links to URLS\n            // Starts with:\n            '(?:' +\n                // http(s):// or ftp://\n                '(?:ht|f)tps?:\\\\/\\\\/' +\n                // or\n                '|' +\n                // www.\n                'www\\\\d{0,3}[.]' +\n                // or\n                '|' +\n                // foo90.com/\n                '[a-z0-9][a-z0-9.\\\\-]*[.][a-z]{2,}\\\\/' +\n            ')' +\n            // Then we get one or more:\n            '(?:' +\n                // Run of non-spaces, non ()<>\n                '[^\\\\s()<>]+' +\n                // or\n                '|' +\n                // balanced parentheses (one level deep only)\n                '\\\\([^\\\\s()<>]+\\\\)' +\n            ')+' +\n            // And we finish with\n            '(?:' +\n                // Not a space or punctuation character\n                '[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]' +\n                // or\n                '|' +\n                // Balanced parentheses.\n                '\\\\([^\\\\s()<>]+\\\\)' +\n            ')' +\n        // Capture group 2: Emails\n        ')|(' +\n            // Add links to emails\n            '[\\\\w\\\\-.%+]+@(?:[\\\\w\\\\-]+\\\\.)+[a-z]{2,}\\\\b' +\n            // Allow query parameters in the mailto: style\n            '(?:' +\n                '[?][^&?\\\\s]+=[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+' +\n                '(?:&[^&?\\\\s]+=[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+)*' +\n            ')?' +\n        '))',\n        'i'\n    );\n    */\n    linkRegExp =\n        /\\b(?:((?:(?:ht|f)tps?:\\/\\/|www\\d{0,3}[.]|[a-z0-9][a-z0-9.\\-]*[.][a-z]{2,}\\/)(?:[^\\s()<>]+|\\([^\\s()<>]+\\))+(?:[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]|\\([^\\s()<>]+\\)))|([\\w\\-.%+]+@(?:[\\w\\-]+\\.)+[a-z]{2,}\\b(?:[?][^&?\\s]+=[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+(?:&[^&?\\s]+=[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+)*)?))/i;\n\n    addDetectedLinks(\n        searchInNode: DocumentFragment | Node,\n        root?: DocumentFragment | HTMLElement,\n    ): Squire {\n        const walker = new TreeIterator<Text>(\n            searchInNode,\n            SHOW_TEXT,\n            (node) => !getNearest(node, root || this._root, 'A'),\n        );\n        const linkRegExp = this.linkRegExp;\n        const defaultAttributes = this._config.tagAttributes.a;\n        let node: Text | null;\n        while ((node = walker.nextNode())) {\n            const parent = node.parentNode!;\n            let data = node.data;\n            let match: RegExpExecArray | null;\n            while ((match = linkRegExp.exec(data))) {\n                const index = match.index;\n                const endIndex = index + match[0].length;\n                if (index) {\n                    parent.insertBefore(\n                        document.createTextNode(data.slice(0, index)),\n                        node,\n                    );\n                }\n                const child = createElement(\n                    'A',\n                    Object.assign(\n                        {\n                            href: match[1]\n                                ? /^(?:ht|f)tps?:/i.test(match[1])\n                                    ? match[1]\n                                    : 'http://' + match[1]\n                                : 'mailto:' + match[0],\n                        },\n                        defaultAttributes,\n                    ),\n                );\n                child.textContent = data.slice(index, endIndex);\n                parent.insertBefore(child, node);\n                node.data = data = data.slice(endIndex);\n            }\n        }\n        return this;\n    }\n\n    // ---\n\n    setFontFace(name: string | null): Squire {\n        const className = this._config.classNames.fontFamily;\n        return this.changeFormat(\n            name\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'font-family: ' + name + ', sans-serif;',\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setFontSize(size: string | null): Squire {\n        const className = this._config.classNames.fontSize;\n        return this.changeFormat(\n            size\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style:\n                              'font-size: ' +\n                              (typeof size === 'number' ? size + 'px' : size),\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setTextColor(color: string | null): Squire {\n        const className = this._config.classNames.color;\n        return this.changeFormat(\n            color\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'color:' + color,\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setHighlightColor(color: string | null): Squire {\n        const className = this._config.classNames.highlight;\n        return this.changeFormat(\n            color\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'background-color:' + color,\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    // --- Block formatting\n\n    _ensureBottomLine(): void {\n        const root = this._root;\n        const last = root.lastElementChild;\n        if (\n            !last ||\n            last.nodeName !== this._config.blockTag ||\n            !isBlock(last)\n        ) {\n            root.appendChild(this.createDefaultBlock());\n        }\n    }\n\n    createDefaultBlock(children?: Node[]): HTMLElement {\n        const config = this._config;\n        return fixCursor(\n            createElement(config.blockTag, config.blockAttributes, children),\n        ) as HTMLElement;\n    }\n\n    tagAfterSplit: Record<string, string> = {\n        DT: 'DD',\n        DD: 'DT',\n        LI: 'LI',\n        PRE: 'PRE',\n    };\n\n    splitBlock(lineBreakOnly: boolean, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        const root = this._root;\n        let block: Node | Element | null;\n        let parent: Node | null;\n        let node: Node;\n        let nodeAfterSplit: Node;\n\n        // Save undo checkpoint and remove any zws so we don't think there's\n        // content in an empty block.\n        this._recordUndoState(range);\n        this._removeZWS();\n        this._getRangeAndRemoveBookmark(range);\n\n        // Selected text is overwritten, therefore delete the contents\n        // to collapse selection.\n        if (!range.collapsed) {\n            deleteContentsOfRange(range, root);\n        }\n\n        // Linkify text\n        if (this._config.addLinks) {\n            moveRangeBoundariesDownTree(range);\n            const textNode = range.startContainer as Text;\n            const offset = range.startOffset;\n            setTimeout(() => {\n                linkifyText(this, textNode, offset);\n            }, 0);\n        }\n\n        block = getStartBlockOfRange(range, root);\n\n        // Inside a PRE, insert literal newline, unless on blank line.\n        if (block && (parent = getNearest(block, root, 'PRE'))) {\n            moveRangeBoundariesDownTree(range);\n            node = range.startContainer;\n            const offset = range.startOffset;\n            if (!(node instanceof Text)) {\n                node = document.createTextNode('');\n                parent.insertBefore(node, parent.firstChild);\n            }\n            // If blank line: split and insert default block\n            if (\n                !lineBreakOnly &&\n                node instanceof Text &&\n                (node.data.charAt(offset - 1) === '\\n' ||\n                    rangeDoesStartAtBlockBoundary(range, root)) &&\n                (node.data.charAt(offset) === '\\n' ||\n                    rangeDoesEndAtBlockBoundary(range, root))\n            ) {\n                node.deleteData(offset && offset - 1, offset ? 2 : 1);\n                nodeAfterSplit = split(\n                    node,\n                    offset && offset - 1,\n                    root,\n                    root,\n                ) as Node;\n                node = nodeAfterSplit.previousSibling!;\n                if (!node.textContent) {\n                    detach(node);\n                }\n                node = this.createDefaultBlock();\n                nodeAfterSplit.parentNode!.insertBefore(node, nodeAfterSplit);\n                if (!nodeAfterSplit.textContent) {\n                    detach(nodeAfterSplit);\n                }\n                range.setStart(node, 0);\n            } else {\n                (node as Text).insertData(offset, '\\n');\n                fixCursor(parent);\n                // Firefox bug: if you set the selection in the text node after\n                // the new line, it draws the cursor before the line break still\n                // but if you set the selection to the equivalent position\n                // in the parent, it works.\n                if ((node as Text).length === offset + 1) {\n                    range.setStartAfter(node);\n                } else {\n                    range.setStart(node, offset + 1);\n                }\n            }\n            range.collapse(true);\n            this.setSelection(range);\n            this._updatePath(range, true);\n            this._docWasChanged();\n            return this;\n        }\n\n        // If this is a malformed bit of document or in a table;\n        // just play it safe and insert a <br>.\n        if (!block || lineBreakOnly || /^T[HD]$/.test(block.nodeName)) {\n            // If inside an <a>, move focus out\n            moveRangeBoundaryOutOf(range, 'A', root);\n            insertNodeInRange(range, createElement('BR'));\n            range.collapse(false);\n            this.setSelection(range);\n            this._updatePath(range, true);\n            return this;\n        }\n\n        // If in a list, we'll split the LI instead.\n        if ((parent = getNearest(block, root, 'LI'))) {\n            block = parent;\n        }\n\n        if (isEmptyBlock(block as Element)) {\n            if (\n                getNearest(block, root, 'UL') ||\n                getNearest(block, root, 'OL')\n            ) {\n                // Break list\n                this.decreaseListLevel(range);\n                return this;\n                // Break blockquote\n            } else if (getNearest(block, root, 'BLOCKQUOTE')) {\n                this.removeQuote(range);\n                return this;\n            }\n        }\n\n        // Otherwise, split at cursor point.\n        node = range.startContainer;\n        const offset = range.startOffset;\n        let splitTag = this.tagAfterSplit[block.nodeName];\n        nodeAfterSplit = split(\n            node,\n            offset,\n            block.parentNode!,\n            this._root,\n        ) as Node;\n\n        const config = this._config;\n        let splitProperties: Record<string, string> | null = null;\n        if (!splitTag) {\n            splitTag = config.blockTag;\n            splitProperties = config.blockAttributes;\n        }\n\n        // Make sure the new node is the correct type.\n        if (!hasTagAttributes(nodeAfterSplit, splitTag, splitProperties)) {\n            block = createElement(splitTag, splitProperties);\n            if ((nodeAfterSplit as HTMLElement).dir) {\n                (block as HTMLElement).dir = (\n                    nodeAfterSplit as HTMLElement\n                ).dir;\n            }\n            replaceWith(nodeAfterSplit, block);\n            block.appendChild(empty(nodeAfterSplit));\n            nodeAfterSplit = block;\n        }\n\n        // Clean up any empty inlines if we hit enter at the beginning of the\n        // block\n        removeZWS(block);\n        removeEmptyInlines(block);\n        fixCursor(block);\n\n        // Focus cursor\n        // If there's a <b>/<i> etc. at the beginning of the split\n        // make sure we focus inside it.\n        while (nodeAfterSplit instanceof Element) {\n            let child = nodeAfterSplit.firstChild;\n            let next;\n\n            // Don't continue links over a block break; unlikely to be the\n            // desired outcome.\n            if (\n                nodeAfterSplit.nodeName === 'A' &&\n                (!nodeAfterSplit.textContent ||\n                    nodeAfterSplit.textContent === ZWS)\n            ) {\n                child = document.createTextNode('') as Text;\n                replaceWith(nodeAfterSplit, child);\n                nodeAfterSplit = child;\n                break;\n            }\n\n            while (child && child instanceof Text && !child.data) {\n                next = child.nextSibling;\n                if (!next || next.nodeName === 'BR') {\n                    break;\n                }\n                detach(child);\n                child = next;\n            }\n\n            // 'BR's essentially don't count; they're a browser hack.\n            // If you try to select the contents of a 'BR', FF will not let\n            // you type anything!\n            if (!child || child.nodeName === 'BR' || child instanceof Text) {\n                break;\n            }\n            nodeAfterSplit = child;\n        }\n        range = createRange(nodeAfterSplit, 0);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    forEachBlock(\n        fn: (el: HTMLElement) => any,\n        mutates: boolean,\n        range?: Range,\n    ): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Save undo checkpoint\n        if (mutates) {\n            this.saveUndoState(range);\n        }\n\n        const root = this._root;\n        let start = getStartBlockOfRange(range, root);\n        const end = getEndBlockOfRange(range, root);\n        if (start && end) {\n            do {\n                if (fn(start) || start === end) {\n                    break;\n                }\n            } while ((start = getNextBlock(start, root)));\n        }\n\n        if (mutates) {\n            this.setSelection(range);\n            // Path may have changed\n            this._updatePath(range, true);\n        }\n        return this;\n    }\n\n    modifyBlocks(modify: (x: DocumentFragment) => Node, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // 1. Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        // 2. Expand range to block boundaries\n        const root = this._root;\n        expandRangeToBlockBoundaries(range, root);\n\n        // 3. Remove range.\n        moveRangeBoundariesUpTree(range, root, root, root);\n        const frag = extractContentsOfRange(range, root, root);\n\n        // 4. Modify tree of fragment and reinsert.\n        if (!range.collapsed) {\n            // After extracting contents, the range edges will still be at the\n            // level we began the spilt. We want to insert directly in the\n            // root, so move the range up there.\n            let node = range.endContainer;\n            if (node === root) {\n                range.collapse(false);\n            } else {\n                while (node.parentNode !== root) {\n                    node = node.parentNode!;\n                }\n                range.setStartBefore(node);\n                range.collapse(true);\n            }\n        }\n        insertNodeInRange(range, modify.call(this, frag));\n\n        // 5. Merge containers at edges\n        if (range.endOffset < range.endContainer.childNodes.length) {\n            mergeContainers(\n                range.endContainer.childNodes[range.endOffset],\n                root,\n            );\n        }\n        mergeContainers(\n            range.startContainer.childNodes[range.startOffset],\n            root,\n        );\n\n        // 6. Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    // ---\n\n    setTextAlignment(alignment: string): Squire {\n        this.forEachBlock((block: HTMLElement) => {\n            const className = block.className\n                .split(/\\s+/)\n                .filter((klass) => {\n                    return !!klass && !/^align/.test(klass);\n                })\n                .join(' ');\n            if (alignment) {\n                block.className = className + ' align-' + alignment;\n                block.style.textAlign = alignment;\n            } else {\n                block.className = className;\n                block.style.textAlign = '';\n            }\n        }, true);\n        return this.focus();\n    }\n\n    setTextDirection(direction: string | null): Squire {\n        this.forEachBlock((block: HTMLElement) => {\n            if (direction) {\n                block.dir = direction;\n            } else {\n                block.removeAttribute('dir');\n            }\n        }, true);\n        return this.focus();\n    }\n\n    // ---\n\n    _getListSelection(\n        range: Range,\n        root: Element,\n    ): [Node, Node | null, Node | null] | null {\n        let list: Node | null = range.commonAncestorContainer;\n        let startLi: Node | null = range.startContainer;\n        let endLi: Node | null = range.endContainer;\n        while (list && list !== root && !/^[OU]L$/.test(list.nodeName)) {\n            list = list.parentNode;\n        }\n        if (!list || list === root) {\n            return null;\n        }\n        if (startLi === list) {\n            startLi = startLi.childNodes[range.startOffset];\n        }\n        if (endLi === list) {\n            endLi = endLi.childNodes[range.endOffset];\n        }\n        while (startLi && startLi.parentNode !== list) {\n            startLi = startLi.parentNode;\n        }\n        while (endLi && endLi.parentNode !== list) {\n            endLi = endLi.parentNode;\n        }\n        return [list, startLi, endLi];\n    }\n\n    increaseListLevel(range?: Range) {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Get start+end li in single common ancestor\n        const root = this._root;\n        const listSelection = this._getListSelection(range, root);\n        if (!listSelection) {\n            return this.focus();\n        }\n        // eslint-disable-next-line prefer-const\n        let [list, startLi, endLi] = listSelection;\n        if (!startLi || startLi === list.firstChild) {\n            return this.focus();\n        }\n\n        // Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        // Increase list depth\n        const type = list.nodeName;\n        let newParent = startLi.previousSibling!;\n        let listAttrs: Record<string, string> | null;\n        let next: Node | null;\n        if (newParent.nodeName !== type) {\n            listAttrs = this._config.tagAttributes[type.toLowerCase()];\n            newParent = createElement(type, listAttrs);\n            list.insertBefore(newParent, startLi);\n        }\n        do {\n            next = startLi === endLi ? null : startLi.nextSibling;\n            newParent.appendChild(startLi);\n        } while ((startLi = next));\n        next = newParent.nextSibling;\n        if (next) {\n            mergeContainers(next, root);\n        }\n\n        // Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    decreaseListLevel(range?: Range) {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        const root = this._root;\n        const listSelection = this._getListSelection(range, root);\n        if (!listSelection) {\n            return this.focus();\n        }\n\n        // eslint-disable-next-line prefer-const\n        let [list, startLi, endLi] = listSelection;\n        if (!startLi) {\n            startLi = list.firstChild;\n        }\n        if (!endLi) {\n            endLi = list.lastChild!;\n        }\n\n        // Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        let next: Node | null;\n        let insertBefore: Node | null = null;\n        if (startLi) {\n            // Find the new parent list node\n            let newParent = list.parentNode!;\n\n            // Split list if necessary\n            insertBefore = !endLi.nextSibling\n                ? list.nextSibling\n                : (split(list, endLi.nextSibling, newParent, root) as Node);\n\n            if (newParent !== root && newParent.nodeName === 'LI') {\n                newParent = newParent.parentNode!;\n                while (insertBefore) {\n                    next = insertBefore.nextSibling;\n                    endLi.appendChild(insertBefore);\n                    insertBefore = next;\n                }\n                insertBefore = list.parentNode!.nextSibling;\n            }\n\n            const makeNotList = !/^[OU]L$/.test(newParent.nodeName);\n            do {\n                next = startLi === endLi ? null : startLi.nextSibling;\n                list.removeChild(startLi);\n                if (makeNotList && startLi.nodeName === 'LI') {\n                    startLi = this.createDefaultBlock([empty(startLi)]);\n                }\n                newParent.insertBefore(startLi!, insertBefore);\n            } while ((startLi = next));\n        }\n\n        if (!list.firstChild) {\n            detach(list);\n        }\n\n        if (insertBefore) {\n            mergeContainers(insertBefore, root);\n        }\n\n        // Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    _makeList(frag: DocumentFragment, type: string): DocumentFragment {\n        const walker = getBlockWalker(frag, this._root);\n        const tagAttributes = this._config.tagAttributes;\n        const listAttrs = tagAttributes[type.toLowerCase()];\n        const listItemAttrs = tagAttributes.li;\n        let node: Node | null;\n        while ((node = walker.nextNode())) {\n            if (node.parentNode! instanceof HTMLLIElement) {\n                node = node.parentNode!;\n                walker.currentNode = node.lastChild!;\n            }\n            if (!(node instanceof HTMLLIElement)) {\n                const newLi = createElement('LI', listItemAttrs);\n                if ((node as HTMLElement).dir) {\n                    newLi.dir = (node as HTMLElement).dir;\n                }\n\n                // Have we replaced the previous block with a new <ul>/<ol>?\n                const prev: ChildNode | null = node.previousSibling;\n                if (prev && prev.nodeName === type) {\n                    prev.appendChild(newLi);\n                    detach(node);\n                    // Otherwise, replace this block with the <ul>/<ol>\n                } else {\n                    replaceWith(node, createElement(type, listAttrs, [newLi]));\n                }\n                newLi.appendChild(empty(node));\n                walker.currentNode = newLi;\n            } else {\n                node = node.parentNode;\n                const tag = node!.nodeName;\n                if (tag !== type && /^[OU]L$/.test(tag)) {\n                    replaceWith(\n                        node!,\n                        createElement(type, listAttrs, [empty(node!)]),\n                    );\n                }\n            }\n        }\n        return frag;\n    }\n\n    makeUnorderedList(): Squire {\n        this.modifyBlocks((frag) => this._makeList(frag, 'UL'));\n        return this.focus();\n    }\n\n    makeOrderedList(): Squire {\n        this.modifyBlocks((frag) => this._makeList(frag, 'OL'));\n        return this.focus();\n    }\n\n    removeList(): Squire {\n        this.modifyBlocks((frag) => {\n            const lists = frag.querySelectorAll('UL, OL');\n            const items = frag.querySelectorAll('LI');\n            const root = this._root;\n            for (let i = 0, l = lists.length; i < l; i += 1) {\n                const list = lists[i];\n                const listFrag = empty(list);\n                fixContainer(listFrag, root);\n                replaceWith(list, listFrag);\n            }\n\n            for (let i = 0, l = items.length; i < l; i += 1) {\n                const item = items[i];\n                if (isBlock(item)) {\n                    replaceWith(item, this.createDefaultBlock([empty(item)]));\n                } else {\n                    fixContainer(item, root);\n                    replaceWith(item, empty(item));\n                }\n            }\n            return frag;\n        });\n        return this.focus();\n    }\n\n    // ---\n\n    increaseQuoteLevel(range?: Range): Squire {\n        this.modifyBlocks(\n            (frag) =>\n                createElement(\n                    'BLOCKQUOTE',\n                    this._config.tagAttributes.blockquote,\n                    [frag],\n                ),\n            range,\n        );\n        return this.focus();\n    }\n\n    decreaseQuoteLevel(range?: Range): Squire {\n        this.modifyBlocks((frag) => {\n            Array.from(frag.querySelectorAll('blockquote'))\n                .filter((el: Node) => {\n                    return !getNearest(el.parentNode, frag, 'BLOCKQUOTE');\n                })\n                .forEach((el: Node) => {\n                    replaceWith(el, empty(el));\n                });\n            return frag;\n        }, range);\n        return this.focus();\n    }\n\n    removeQuote(range?: Range): Squire {\n        this.modifyBlocks(\n            (/* frag */) =>\n                this.createDefaultBlock([\n                    createElement('INPUT', {\n                        id: this.startSelectionId,\n                        type: 'hidden',\n                    }),\n                    createElement('INPUT', {\n                        id: this.endSelectionId,\n                        type: 'hidden',\n                    }),\n                ]),\n            range,\n        );\n        return this.focus();\n    }\n\n    // ---\n\n    code(): Squire {\n        const range = this.getSelection();\n        if (range.collapsed || isContainer(range.commonAncestorContainer)) {\n            this.modifyBlocks((frag) => {\n                const root = this._root;\n                const output = document.createDocumentFragment();\n                const blockWalker = getBlockWalker(frag, root);\n                let node: Element | Text | null;\n                // 1. Extract inline content; drop all blocks and contains.\n                while ((node = blockWalker.nextNode())) {\n                    // 2. Replace <br> with \\n in content\n                    let nodes = node.querySelectorAll('BR');\n                    const brBreaksLine: boolean[] = [];\n                    let l = nodes.length;\n                    // Must calculate whether the <br> breaks a line first,\n                    // because if we have two <br>s next to each other, after\n                    // the first one is converted to a block split, the second\n                    // will be at the end of a block and therefore seem to not\n                    // be a line break. But in its original context it was, so\n                    // we should also convert it to a block split.\n                    for (let i = 0; i < l; i += 1) {\n                        brBreaksLine[i] = isLineBreak(nodes[i], false);\n                    }\n                    while (l--) {\n                        const br = nodes[l];\n                        if (!brBreaksLine[l]) {\n                            detach(br);\n                        } else {\n                            replaceWith(br, document.createTextNode('\\n'));\n                        }\n                    }\n                    // 3. Remove <code>; its format clashes with <pre>\n                    nodes = node.querySelectorAll('CODE');\n                    l = nodes.length;\n                    while (l--) {\n                        replaceWith(nodes[l], empty(nodes[l]));\n                    }\n                    if (output.childNodes.length) {\n                        output.appendChild(document.createTextNode('\\n'));\n                    }\n                    output.appendChild(empty(node));\n                }\n                // 4. Replace nbsp with regular sp\n                const textWalker = new TreeIterator<Text>(output, SHOW_TEXT);\n                while ((node = textWalker.nextNode())) {\n                    // eslint-disable-next-line no-irregular-whitespace\n                    node.data = node.data.replace(/\u00A0/g, ' '); // nbsp -> sp\n                }\n                output.normalize();\n                return fixCursor(\n                    createElement('PRE', this._config.tagAttributes.pre, [\n                        output,\n                    ]),\n                );\n            }, range);\n            this.focus();\n        } else {\n            this.changeFormat(\n                {\n                    tag: 'CODE',\n                    attributes: this._config.tagAttributes.code,\n                },\n                null,\n                range,\n            );\n        }\n        return this;\n    }\n\n    removeCode(): Squire {\n        const range = this.getSelection();\n        const ancestor = range.commonAncestorContainer;\n        const inPre = getNearest(ancestor, this._root, 'PRE');\n        if (inPre) {\n            this.modifyBlocks((frag) => {\n                const root = this._root;\n                const pres = frag.querySelectorAll('PRE');\n                let l = pres.length;\n                while (l--) {\n                    const pre = pres[l];\n                    const walker = new TreeIterator<Text>(pre, SHOW_TEXT);\n                    let node: Text | null;\n                    while ((node = walker.nextNode())) {\n                        let value = node.data;\n                        value = value.replace(/ (?= )/g, '\u00A0'); // sp -> nbsp\n                        const contents = document.createDocumentFragment();\n                        let index: number;\n                        while ((index = value.indexOf('\\n')) > -1) {\n                            contents.appendChild(\n                                document.createTextNode(value.slice(0, index)),\n                            );\n                            contents.appendChild(createElement('BR'));\n                            value = value.slice(index + 1);\n                        }\n                        node.parentNode!.insertBefore(contents, node);\n                        node.data = value;\n                    }\n                    fixContainer(pre, root);\n                    replaceWith(pre, empty(pre));\n                }\n                return frag;\n            }, range);\n            this.focus();\n        } else {\n            this.changeFormat(null, { tag: 'CODE' }, range);\n        }\n        return this;\n    }\n\n    toggleCode(): Squire {\n        if (this.hasFormat('PRE') || this.hasFormat('CODE')) {\n            this.removeCode();\n        } else {\n            this.code();\n        }\n        return this;\n    }\n\n    // ---\n\n    _removeFormatting(\n        root: DocumentFragment | Element,\n        clean: DocumentFragment | Element,\n    ): DocumentFragment | Element {\n        for (\n            let node = root.firstChild, next: ChildNode | null;\n            node;\n            node = next\n        ) {\n            next = node.nextSibling;\n            if (isInline(node)) {\n                if (\n                    node instanceof Text ||\n                    node.nodeName === 'BR' ||\n                    node.nodeName === 'IMG'\n                ) {\n                    clean.appendChild(node);\n                    continue;\n                }\n            } else if (isBlock(node)) {\n                clean.appendChild(\n                    this.createDefaultBlock([\n                        this._removeFormatting(\n                            node as Element,\n                            document.createDocumentFragment(),\n                        ),\n                    ]),\n                );\n                continue;\n            }\n            this._removeFormatting(node as Element, clean);\n        }\n        return clean;\n    }\n\n    removeAllFormatting(range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        if (range.collapsed) {\n            return this.focus();\n        }\n\n        const root = this._root;\n        let stopNode = range.commonAncestorContainer;\n        while (stopNode && !isBlock(stopNode)) {\n            stopNode = stopNode.parentNode!;\n        }\n        if (!stopNode) {\n            expandRangeToBlockBoundaries(range, root);\n            stopNode = root;\n        }\n        if (stopNode instanceof Text) {\n            return this.focus();\n        }\n\n        // Record undo point\n        this.saveUndoState(range);\n\n        // Avoid splitting where we're already at edges.\n        moveRangeBoundariesUpTree(range, stopNode, stopNode, root);\n\n        // Split the selection up to the block, or if whole selection in same\n        // block, expand range boundaries to ends of block and split up to root.\n        const startContainer = range.startContainer;\n        let startOffset = range.startOffset;\n        const endContainer = range.endContainer;\n        let endOffset = range.endOffset;\n\n        // Split end point first to avoid problems when end and start\n        // in same container.\n        const formattedNodes = document.createDocumentFragment();\n        const cleanNodes = document.createDocumentFragment();\n        const nodeAfterSplit = split(endContainer, endOffset, stopNode, root);\n        let nodeInSplit = split(startContainer, startOffset, stopNode, root);\n        let nextNode: ChildNode | null;\n\n        // Then replace contents in split with a cleaned version of the same:\n        // blocks become default blocks, text and leaf nodes survive, everything\n        // else is obliterated.\n        while (nodeInSplit !== nodeAfterSplit) {\n            nextNode = nodeInSplit!.nextSibling;\n            formattedNodes.appendChild(nodeInSplit!);\n            nodeInSplit = nextNode;\n        }\n        this._removeFormatting(formattedNodes, cleanNodes);\n        cleanNodes.normalize();\n        nodeInSplit = cleanNodes.firstChild;\n        nextNode = cleanNodes.lastChild;\n\n        // Restore selection\n        if (nodeInSplit) {\n            stopNode.insertBefore(cleanNodes, nodeAfterSplit);\n            const childNodes = Array.from(stopNode.childNodes) as Node[];\n            startOffset = childNodes.indexOf(nodeInSplit);\n            endOffset = nextNode ? childNodes.indexOf(nextNode) + 1 : 0;\n        } else if (nodeAfterSplit) {\n            const childNodes = Array.from(stopNode.childNodes) as Node[];\n            startOffset = childNodes.indexOf(nodeAfterSplit);\n            endOffset = startOffset;\n        }\n\n        // Merge text nodes at edges, if possible\n        range.setStart(stopNode, startOffset);\n        range.setEnd(stopNode, endOffset);\n        mergeInlines(stopNode, range);\n\n        // And move back down the tree\n        moveRangeBoundariesDownTree(range);\n\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n}\n\n// ---\n\nexport { Squire };\nexport type { SquireConfig };\n", "import { Squire } from './Editor';\n\n(window as any).Squire = Squire;\n"],
-  "mappings": "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",
+  "sourcesContent": ["type NODE_TYPE = 1 | 4 | 5;\nconst SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;\nconst SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;\nconst SHOW_ELEMENT_OR_TEXT = 5; // SHOW_ELEMENT|SHOW_TEXT;\n\nconst always = (): true => true;\n\nclass TreeIterator<T extends Node> {\n    root: Node;\n    currentNode: Node;\n    nodeType: NODE_TYPE;\n    filter: (n: T) => boolean;\n\n    constructor(root: Node, nodeType: NODE_TYPE, filter?: (n: T) => boolean) {\n        this.root = root;\n        this.currentNode = root;\n        this.nodeType = nodeType;\n        this.filter = filter || always;\n    }\n\n    isAcceptableNode(node: Node): boolean {\n        const nodeType = node.nodeType;\n        const nodeFilterType =\n            nodeType === Node.ELEMENT_NODE\n                ? SHOW_ELEMENT\n                : nodeType === Node.TEXT_NODE\n                  ? SHOW_TEXT\n                  : 0;\n        return !!(nodeFilterType & this.nodeType) && this.filter(node as T);\n    }\n\n    nextNode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            node = current.firstChild;\n            while (!node && current) {\n                if (current === root) {\n                    break;\n                }\n                node = current.nextSibling;\n                if (!node) {\n                    current = current.parentNode;\n                }\n            }\n            if (!node) {\n                return null;\n            }\n\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n\n    previousNode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            if (current === root) {\n                return null;\n            }\n            node = current.previousSibling;\n            if (node) {\n                while ((current = node.lastChild)) {\n                    node = current;\n                }\n            } else {\n                node = current.parentNode;\n            }\n            if (!node) {\n                return null;\n            }\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n\n    // Previous node in post-order.\n    previousPONode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            node = current.lastChild;\n            while (!node && current) {\n                if (current === root) {\n                    break;\n                }\n                node = current.previousSibling;\n                if (!node) {\n                    current = current.parentNode;\n                }\n            }\n            if (!node) {\n                return null;\n            }\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n}\n\n// ---\n\nexport { TreeIterator, SHOW_ELEMENT, SHOW_TEXT, SHOW_ELEMENT_OR_TEXT };\n", "const DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING\nconst ELEMENT_NODE = 1; // Node.ELEMENT_NODE;\nconst TEXT_NODE = 3; // Node.TEXT_NODE;\nconst DOCUMENT_NODE = 9; // Node.DOCUMENT_NODE;\nconst DOCUMENT_FRAGMENT_NODE = 11; // Node.DOCUMENT_FRAGMENT_NODE;\n\nconst ZWS = '\\u200B';\n\nconst ua = navigator.userAgent;\n\nconst isMac = /Mac OS X/.test(ua);\nconst isWin = /Windows NT/.test(ua);\nconst isIOS =\n    /iP(?:ad|hone|od)/.test(ua) || (isMac && !!navigator.maxTouchPoints);\nconst isAndroid = /Android/.test(ua);\n\nconst isGecko = /Gecko\\//.test(ua);\nconst isLegacyEdge = /Edge\\//.test(ua);\nconst isWebKit = !isLegacyEdge && /WebKit\\//.test(ua);\n\nconst ctrlKey = isMac || isIOS ? 'Meta-' : 'Ctrl-';\n\nconst cantFocusEmptyTextNodes = isWebKit;\n\nconst supportsInputEvents =\n    'onbeforeinput' in document && 'inputType' in new InputEvent('input');\n\n// Use [^ \\t\\r\\n] instead of \\S so that nbsp does not count as white-space\nconst notWS = /[^ \\t\\r\\n]/;\n\n// ---\n\nexport {\n    DOCUMENT_POSITION_PRECEDING,\n    ELEMENT_NODE,\n    TEXT_NODE,\n    DOCUMENT_NODE,\n    DOCUMENT_FRAGMENT_NODE,\n    notWS,\n    ZWS,\n    ua,\n    isMac,\n    isWin,\n    isIOS,\n    isAndroid,\n    isGecko,\n    isLegacyEdge,\n    isWebKit,\n    ctrlKey,\n    cantFocusEmptyTextNodes,\n    supportsInputEvents,\n};\n", "import { ELEMENT_NODE, TEXT_NODE, DOCUMENT_FRAGMENT_NODE } from '../Constants';\n\n// ---\n\nconst inlineNodeNames =\n    /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/;\n\nconst leafNodeNames = new Set(['BR', 'HR', 'IFRAME', 'IMG', 'INPUT']);\n\nconst UNKNOWN = 0;\nconst INLINE = 1;\nconst BLOCK = 2;\nconst CONTAINER = 3;\n\n// ---\n\nlet cache: WeakMap<Node, number> = new WeakMap();\n\nconst resetNodeCategoryCache = (): void => {\n    cache = new WeakMap();\n};\n\n// ---\n\nconst isLeaf = (node: Node): boolean => {\n    return leafNodeNames.has(node.nodeName);\n};\n\nconst getNodeCategory = (node: Node): number => {\n    switch (node.nodeType) {\n        case TEXT_NODE:\n            return INLINE;\n        case ELEMENT_NODE:\n        case DOCUMENT_FRAGMENT_NODE:\n            if (cache.has(node)) {\n                return cache.get(node) as number;\n            }\n            break;\n        default:\n            return UNKNOWN;\n    }\n\n    let nodeCategory: number;\n    if (!Array.from(node.childNodes).every(isInline)) {\n        // Malformed HTML can have block tags inside inline tags. Need to treat\n        // these as containers rather than inline. See #239.\n        nodeCategory = CONTAINER;\n    } else if (inlineNodeNames.test(node.nodeName)) {\n        nodeCategory = INLINE;\n    } else {\n        nodeCategory = BLOCK;\n    }\n    cache.set(node, nodeCategory);\n    return nodeCategory;\n};\n\nconst isInline = (node: Node): boolean => {\n    return getNodeCategory(node) === INLINE;\n};\n\nconst isBlock = (node: Node): boolean => {\n    return getNodeCategory(node) === BLOCK;\n};\n\nconst isContainer = (node: Node): boolean => {\n    return getNodeCategory(node) === CONTAINER;\n};\n\n// ---\n\nexport {\n    getNodeCategory,\n    isBlock,\n    isContainer,\n    isInline,\n    isLeaf,\n    leafNodeNames,\n    resetNodeCategoryCache,\n};\n", "import { isLeaf } from './Category';\n\n// ---\n\nconst createElement = (\n    tag: string,\n    props?: Record<string, string> | null,\n    children?: Node[],\n): HTMLElement => {\n    const el = document.createElement(tag);\n    if (props instanceof Array) {\n        children = props;\n        props = null;\n    }\n    if (props) {\n        for (const attr in props) {\n            const value = props[attr];\n            if (value !== undefined) {\n                el.setAttribute(attr, value);\n            }\n        }\n    }\n    if (children) {\n        children.forEach((node) => el.appendChild(node));\n    }\n    return el;\n};\n\n// --- Tests\n\nconst areAlike = (\n    node: HTMLElement | Node,\n    node2: HTMLElement | Node,\n): boolean => {\n    if (isLeaf(node)) {\n        return false;\n    }\n    if (node.nodeType !== node2.nodeType || node.nodeName !== node2.nodeName) {\n        return false;\n    }\n    if (node instanceof HTMLElement && node2 instanceof HTMLElement) {\n        return (\n            node.nodeName !== 'A' &&\n            node.className === node2.className &&\n            node.style.cssText === node2.style.cssText\n        );\n    }\n    return true;\n};\n\nconst hasTagAttributes = (\n    node: Node | Element,\n    tag: string,\n    attributes?: Record<string, string> | null,\n): boolean => {\n    if (node.nodeName !== tag) {\n        return false;\n    }\n    for (const attr in attributes) {\n        if (\n            !('getAttribute' in node) ||\n            node.getAttribute(attr) !== attributes[attr]\n        ) {\n            return false;\n        }\n    }\n    return true;\n};\n\n// --- Traversal\n\nconst getNearest = (\n    node: Node | null,\n    root: Element | DocumentFragment,\n    tag: string,\n    attributes?: Record<string, string> | null,\n): Node | null => {\n    while (node && node !== root) {\n        if (hasTagAttributes(node, tag, attributes)) {\n            return node;\n        }\n        node = node.parentNode;\n    }\n    return null;\n};\n\nconst getNodeBeforeOffset = (node: Node, offset: number): Node => {\n    let children = node.childNodes;\n    while (offset && node instanceof Element) {\n        node = children[offset - 1];\n        children = node.childNodes;\n        offset = children.length;\n    }\n    return node;\n};\n\nconst getNodeAfterOffset = (node: Node, offset: number): Node | null => {\n    let returnNode: Node | null = node;\n    if (returnNode instanceof Element) {\n        const children = returnNode.childNodes;\n        if (offset < children.length) {\n            returnNode = children[offset];\n        } else {\n            while (returnNode && !returnNode.nextSibling) {\n                returnNode = returnNode.parentNode;\n            }\n            if (returnNode) {\n                returnNode = returnNode.nextSibling;\n            }\n        }\n    }\n    return returnNode;\n};\n\nconst getLength = (node: Node): number => {\n    return node instanceof Element || node instanceof DocumentFragment\n        ? node.childNodes.length\n        : node instanceof CharacterData\n          ? node.length\n          : 0;\n};\n\n// --- Manipulation\n\nconst empty = (node: Node): DocumentFragment => {\n    const frag = document.createDocumentFragment();\n    let child = node.firstChild;\n    while (child) {\n        frag.appendChild(child);\n        child = node.firstChild;\n    }\n    return frag;\n};\n\nconst detach = (node: Node): Node => {\n    const parent = node.parentNode;\n    if (parent) {\n        parent.removeChild(node);\n    }\n    return node;\n};\n\nconst replaceWith = (node: Node, node2: Node): void => {\n    const parent = node.parentNode;\n    if (parent) {\n        parent.replaceChild(node2, node);\n    }\n};\n\n// --- Export\n\nexport {\n    areAlike,\n    createElement,\n    detach,\n    empty,\n    getLength,\n    getNearest,\n    getNodeAfterOffset,\n    getNodeBeforeOffset,\n    hasTagAttributes,\n    replaceWith,\n};\n", "import { ZWS, notWS } from '../Constants';\nimport { isInline } from './Category';\nimport { getLength } from './Node';\nimport { SHOW_ELEMENT_OR_TEXT, SHOW_TEXT, TreeIterator } from './TreeIterator';\n\n// ---\n\nconst notWSTextNode = (node: Node): boolean => {\n    return node instanceof Element\n        ? node.nodeName === 'BR'\n        : // okay if data is 'undefined' here.\n          notWS.test((node as CharacterData).data);\n};\n\nconst isLineBreak = (br: Element, isLBIfEmptyBlock: boolean): boolean => {\n    let block = br.parentNode!;\n    while (isInline(block)) {\n        block = block.parentNode!;\n    }\n    const walker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        notWSTextNode,\n    );\n    walker.currentNode = br;\n    return !!walker.nextNode() || (isLBIfEmptyBlock && !walker.previousNode());\n};\n\n// --- Workaround for browsers that can't focus empty text nodes\n\n// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256\n\n// Walk down the tree starting at the root and remove any ZWS. If the node only\n// contained ZWS space then remove it too. We may want to keep one ZWS node at\n// the bottom of the tree so the block can be selected. Define that node as the\n// keepNode.\nconst removeZWS = (root: Node, keepNode?: Node | null): void => {\n    const walker = new TreeIterator<Text>(root, SHOW_TEXT);\n    let textNode: Text | null;\n    let index: number;\n    while ((textNode = walker.nextNode())) {\n        while (\n            (index = textNode.data.indexOf(ZWS)) > -1 &&\n            // eslint-disable-next-line no-unmodified-loop-condition\n            (!keepNode || textNode.parentNode !== keepNode)\n        ) {\n            if (textNode.length === 1) {\n                let node: Node = textNode;\n                let parent = node.parentNode;\n                while (parent) {\n                    parent.removeChild(node);\n                    walker.currentNode = parent;\n                    if (!isInline(parent) || getLength(parent)) {\n                        break;\n                    }\n                    node = parent;\n                    parent = node.parentNode;\n                }\n                break;\n            } else {\n                textNode.deleteData(index, 1);\n            }\n        }\n    }\n};\n\n// ---\n\nexport { isLineBreak, removeZWS };\n", "import { isLeaf } from '../node/Category';\nimport { getLength, getNearest } from '../node/Node';\nimport { isLineBreak } from '../node/Whitespace';\nimport { TEXT_NODE } from '../Constants';\n\n// ---\n\nconst START_TO_START = 0; // Range.START_TO_START\nconst START_TO_END = 1; // Range.START_TO_END\nconst END_TO_END = 2; // Range.END_TO_END\nconst END_TO_START = 3; // Range.END_TO_START\n\nconst isNodeContainedInRange = (\n    range: Range,\n    node: Node,\n    partial: boolean,\n): boolean => {\n    const nodeRange = document.createRange();\n    nodeRange.selectNode(node);\n    if (partial) {\n        // Node must not finish before range starts or start after range\n        // finishes.\n        const nodeEndBeforeStart =\n            range.compareBoundaryPoints(END_TO_START, nodeRange) > -1;\n        const nodeStartAfterEnd =\n            range.compareBoundaryPoints(START_TO_END, nodeRange) < 1;\n        return !nodeEndBeforeStart && !nodeStartAfterEnd;\n    } else {\n        // Node must start after range starts and finish before range\n        // finishes\n        const nodeStartAfterStart =\n            range.compareBoundaryPoints(START_TO_START, nodeRange) < 1;\n        const nodeEndBeforeEnd =\n            range.compareBoundaryPoints(END_TO_END, nodeRange) > -1;\n        return nodeStartAfterStart && nodeEndBeforeEnd;\n    }\n};\n\n/**\n * Moves the range to an equivalent position with the start/end as deep in\n * the tree as possible.\n */\nconst moveRangeBoundariesDownTree = (range: Range): void => {\n    let { startContainer, startOffset, endContainer, endOffset } = range;\n\n    while (!(startContainer instanceof Text)) {\n        let child: ChildNode | null = startContainer.childNodes[startOffset];\n        if (!child || isLeaf(child)) {\n            if (startOffset) {\n                child = startContainer.childNodes[startOffset - 1];\n                if (child instanceof Text) {\n                    // Need a new variable to satisfy TypeScript's type checker\n                    // for some reason.\n                    let textChild: Text = child;\n                    // If we have an empty text node next to another text node,\n                    // just skip and remove it.\n                    let prev: ChildNode | null;\n                    while (\n                        !textChild.length &&\n                        (prev = textChild.previousSibling) &&\n                        prev instanceof Text\n                    ) {\n                        textChild.remove();\n                        textChild = prev;\n                    }\n                    startContainer = textChild;\n                    startOffset = textChild.data.length;\n                }\n            }\n            break;\n        }\n        startContainer = child;\n        startOffset = 0;\n    }\n    if (endOffset) {\n        while (!(endContainer instanceof Text)) {\n            const child = endContainer.childNodes[endOffset - 1];\n            if (!child || isLeaf(child)) {\n                if (\n                    child &&\n                    child.nodeName === 'BR' &&\n                    !isLineBreak(child as Element, false)\n                ) {\n                    endOffset -= 1;\n                    continue;\n                }\n                break;\n            }\n            endContainer = child;\n            endOffset = getLength(endContainer);\n        }\n    } else {\n        while (!(endContainer instanceof Text)) {\n            const child = endContainer.firstChild!;\n            if (!child || isLeaf(child)) {\n                break;\n            }\n            endContainer = child;\n        }\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\nconst moveRangeBoundariesUpTree = (\n    range: Range,\n    startMax: Node,\n    endMax: Node,\n    root: Node,\n): void => {\n    let startContainer = range.startContainer;\n    let startOffset = range.startOffset;\n    let endContainer = range.endContainer;\n    let endOffset = range.endOffset;\n    let parent: Node;\n\n    if (!startMax) {\n        startMax = range.commonAncestorContainer;\n    }\n    if (!endMax) {\n        endMax = startMax;\n    }\n\n    while (\n        !startOffset &&\n        startContainer !== startMax &&\n        startContainer !== root\n    ) {\n        parent = startContainer.parentNode!;\n        startOffset = Array.from(parent.childNodes).indexOf(\n            startContainer as ChildNode,\n        );\n        startContainer = parent;\n    }\n\n    while (true) {\n        if (endContainer === endMax || endContainer === root) {\n            break;\n        }\n        if (\n            endContainer.nodeType !== TEXT_NODE &&\n            endContainer.childNodes[endOffset] &&\n            endContainer.childNodes[endOffset].nodeName === 'BR' &&\n            !isLineBreak(endContainer.childNodes[endOffset] as Element, false)\n        ) {\n            endOffset += 1;\n        }\n        if (endOffset !== getLength(endContainer)) {\n            break;\n        }\n        parent = endContainer.parentNode!;\n        endOffset =\n            Array.from(parent.childNodes).indexOf(endContainer as ChildNode) +\n            1;\n        endContainer = parent;\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\nconst moveRangeBoundaryOutOf = (\n    range: Range,\n    tag: string,\n    root: Element,\n): Range => {\n    let parent = getNearest(range.endContainer, root, tag);\n    if (parent && (parent = parent.parentNode)) {\n        const clone = range.cloneRange();\n        moveRangeBoundariesUpTree(clone, parent, parent, root);\n        if (clone.endContainer === parent) {\n            range.setStart(clone.endContainer, clone.endOffset);\n            range.setEnd(clone.endContainer, clone.endOffset);\n        }\n    }\n    return range;\n};\n\n// ---\n\nexport {\n    isNodeContainedInRange,\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n    moveRangeBoundaryOutOf,\n};\n", "import { ZWS, cantFocusEmptyTextNodes } from '../Constants';\nimport {\n    createElement,\n    getNearest,\n    areAlike,\n    getLength,\n    detach,\n    empty,\n} from './Node';\nimport { isInline, isContainer } from './Category';\n\n// ---\n\nconst fixCursor = (node: Node): Node => {\n    // In Webkit and Gecko, block level elements are collapsed and\n    // unfocusable if they have no content. To remedy this, a <BR> must be\n    // inserted. In Opera and IE, we just need a textnode in order for the\n    // cursor to appear.\n    let fixer: Element | Text | null = null;\n\n    if (node instanceof Text) {\n        return node;\n    }\n\n    if (isInline(node)) {\n        let child = node.firstChild;\n        if (cantFocusEmptyTextNodes) {\n            while (child && child instanceof Text && !child.data) {\n                node.removeChild(child);\n                child = node.firstChild;\n            }\n        }\n        if (!child) {\n            if (cantFocusEmptyTextNodes) {\n                fixer = document.createTextNode(ZWS);\n            } else {\n                fixer = document.createTextNode('');\n            }\n        }\n    } else if (\n        (node instanceof Element || node instanceof DocumentFragment) &&\n        !node.querySelector('BR')\n    ) {\n        fixer = createElement('BR');\n        let parent: Element | DocumentFragment = node;\n        let child: Element | null;\n        while ((child = parent.lastElementChild) && !isInline(child)) {\n            parent = child;\n        }\n        node = parent;\n    }\n    if (fixer) {\n        try {\n            node.appendChild(fixer);\n        } catch (error) {}\n    }\n\n    return node;\n};\n\n// Recursively examine container nodes and wrap any inline children.\nconst fixContainer = (\n    container: Node,\n    root: Element | DocumentFragment,\n): Node => {\n    let wrapper: HTMLElement | null = null;\n    Array.from(container.childNodes).forEach((child) => {\n        const isBR = child.nodeName === 'BR';\n        if (!isBR && isInline(child)) {\n            if (!wrapper) {\n                wrapper = createElement('DIV');\n            }\n            wrapper.appendChild(child);\n        } else if (isBR || wrapper) {\n            if (!wrapper) {\n                wrapper = createElement('DIV');\n            }\n            fixCursor(wrapper);\n            if (isBR) {\n                container.replaceChild(wrapper, child);\n            } else {\n                container.insertBefore(wrapper, child);\n            }\n            wrapper = null;\n        }\n        if (isContainer(child)) {\n            fixContainer(child, root);\n        }\n    });\n    if (wrapper) {\n        container.appendChild(fixCursor(wrapper));\n    }\n    return container;\n};\n\nconst split = (\n    node: Node,\n    offset: number | Node | null,\n    stopNode: Node,\n    root: Element | DocumentFragment,\n): Node | null => {\n    if (node instanceof Text && node !== stopNode) {\n        if (typeof offset !== 'number') {\n            throw new Error('Offset must be a number to split text node!');\n        }\n        if (!node.parentNode) {\n            throw new Error('Cannot split text node with no parent!');\n        }\n        return split(node.parentNode, node.splitText(offset), stopNode, root);\n    }\n\n    let nodeAfterSplit: Node | null =\n        typeof offset === 'number'\n            ? offset < node.childNodes.length\n                ? node.childNodes[offset]\n                : null\n            : offset;\n    const parent = node.parentNode;\n    if (!parent || node === stopNode || !(node instanceof Element)) {\n        return nodeAfterSplit;\n    }\n\n    // Clone node without children\n    const clone = node.cloneNode(false) as Element;\n\n    // Add right-hand siblings to the clone\n    while (nodeAfterSplit) {\n        const next = nodeAfterSplit.nextSibling;\n        clone.appendChild(nodeAfterSplit);\n        nodeAfterSplit = next;\n    }\n\n    // Maintain li numbering if inside a quote.\n    if (\n        node instanceof HTMLOListElement &&\n        getNearest(node, root, 'BLOCKQUOTE')\n    ) {\n        (clone as HTMLOListElement).start =\n            (+node.start || 1) + node.childNodes.length - 1;\n    }\n\n    // DO NOT NORMALISE. This may undo the fixCursor() call\n    // of a node lower down the tree!\n    // We need something in the element in order for the cursor to appear.\n    fixCursor(node);\n    fixCursor(clone);\n\n    // Inject clone after original node\n    parent.insertBefore(clone, node.nextSibling);\n\n    // Keep on splitting up the tree\n    return split(parent, clone, stopNode, root);\n};\n\nconst _mergeInlines = (\n    node: Node,\n    fakeRange: {\n        startContainer: Node;\n        startOffset: number;\n        endContainer: Node;\n        endOffset: number;\n    },\n): void => {\n    const children = node.childNodes;\n    let l = children.length;\n    const frags: DocumentFragment[] = [];\n    while (l--) {\n        const child = children[l];\n        const prev = l ? children[l - 1] : null;\n        if (prev && isInline(child) && areAlike(child, prev)) {\n            if (fakeRange.startContainer === child) {\n                fakeRange.startContainer = prev;\n                fakeRange.startOffset += getLength(prev);\n            }\n            if (fakeRange.endContainer === child) {\n                fakeRange.endContainer = prev;\n                fakeRange.endOffset += getLength(prev);\n            }\n            if (fakeRange.startContainer === node) {\n                if (fakeRange.startOffset > l) {\n                    fakeRange.startOffset -= 1;\n                } else if (fakeRange.startOffset === l) {\n                    fakeRange.startContainer = prev;\n                    fakeRange.startOffset = getLength(prev);\n                }\n            }\n            if (fakeRange.endContainer === node) {\n                if (fakeRange.endOffset > l) {\n                    fakeRange.endOffset -= 1;\n                } else if (fakeRange.endOffset === l) {\n                    fakeRange.endContainer = prev;\n                    fakeRange.endOffset = getLength(prev);\n                }\n            }\n            detach(child);\n            if (child instanceof Text) {\n                (prev as Text).appendData(child.data);\n            } else {\n                frags.push(empty(child));\n            }\n        } else if (child instanceof Element) {\n            let frag: DocumentFragment | undefined;\n            while ((frag = frags.pop())) {\n                child.appendChild(frag);\n            }\n            _mergeInlines(child, fakeRange);\n        }\n    }\n};\n\nconst mergeInlines = (node: Node, range: Range): void => {\n    const element = node instanceof Text ? node.parentNode : node;\n    if (element instanceof Element) {\n        const fakeRange = {\n            startContainer: range.startContainer,\n            startOffset: range.startOffset,\n            endContainer: range.endContainer,\n            endOffset: range.endOffset,\n        };\n        _mergeInlines(element, fakeRange);\n        range.setStart(fakeRange.startContainer, fakeRange.startOffset);\n        range.setEnd(fakeRange.endContainer, fakeRange.endOffset);\n    }\n};\n\nconst mergeWithBlock = (\n    block: Node,\n    next: Node,\n    range: Range,\n    root: Element,\n): void => {\n    let container = next;\n    let parent: Node | null;\n    let offset: number;\n    while (\n        (parent = container.parentNode) &&\n        parent !== root &&\n        parent instanceof Element &&\n        parent.childNodes.length === 1\n    ) {\n        container = parent;\n    }\n    detach(container);\n\n    offset = block.childNodes.length;\n\n    // Remove extra <BR> fixer if present.\n    const last = block.lastChild;\n    if (last && last.nodeName === 'BR') {\n        block.removeChild(last);\n        offset -= 1;\n    }\n\n    block.appendChild(empty(next));\n\n    range.setStart(block, offset);\n    range.collapse(true);\n    mergeInlines(block, range);\n};\n\nconst mergeContainers = (node: Node, root: Element): void => {\n    const prev = node.previousSibling;\n    const first = node.firstChild;\n    const isListItem = node.nodeName === 'LI';\n\n    // Do not merge LIs, unless it only contains a UL\n    if (isListItem && (!first || !/^[OU]L$/.test(first.nodeName))) {\n        return;\n    }\n\n    if (prev && areAlike(prev, node)) {\n        if (!isContainer(prev)) {\n            if (isListItem) {\n                const block = createElement('DIV');\n                block.appendChild(empty(prev));\n                prev.appendChild(block);\n            } else {\n                return;\n            }\n        }\n        detach(node);\n        const needsFix = !isContainer(node);\n        prev.appendChild(empty(node));\n        if (needsFix) {\n            fixContainer(prev, root);\n        }\n        if (first) {\n            mergeContainers(first, root);\n        }\n    } else if (isListItem) {\n        const block = createElement('DIV');\n        node.insertBefore(block, first);\n        fixCursor(block);\n    }\n};\n\n// ---\n\nexport {\n    fixContainer,\n    fixCursor,\n    mergeContainers,\n    mergeInlines,\n    mergeWithBlock,\n    split,\n};\n", "import { notWS } from './Constants';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from './node/TreeIterator';\nimport { createElement, empty, detach, replaceWith } from './node/Node';\nimport { isInline, isLeaf } from './node/Category';\nimport { fixContainer } from './node/MergeSplit';\nimport { isLineBreak } from './node/Whitespace';\n\nimport type { SquireConfig } from './Editor';\n\n// ---\n\ntype StyleRewriter = (\n    node: HTMLElement,\n    parent: Node,\n    config: SquireConfig,\n) => HTMLElement;\n\n// ---\n\nconst styleToSemantic: Record<\n    string,\n    { regexp: RegExp; replace: (x: any, y: string) => HTMLElement }\n> = {\n    'font-weight': {\n        regexp: /^bold|^700/i,\n        replace(): HTMLElement {\n            return createElement('B');\n        },\n    },\n    'font-style': {\n        regexp: /^italic/i,\n        replace(): HTMLElement {\n            return createElement('I');\n        },\n    },\n    'font-family': {\n        regexp: notWS,\n        replace(\n            classNames: { fontFamily: string },\n            family: string,\n        ): HTMLElement {\n            return createElement('SPAN', {\n                class: classNames.fontFamily,\n                style: 'font-family:' + family,\n            });\n        },\n    },\n    'font-size': {\n        regexp: notWS,\n        replace(classNames: { fontSize: string }, size: string): HTMLElement {\n            return createElement('SPAN', {\n                class: classNames.fontSize,\n                style: 'font-size:' + size,\n            });\n        },\n    },\n    'text-decoration': {\n        regexp: /^underline/i,\n        replace(): HTMLElement {\n            return createElement('U');\n        },\n    },\n};\n\nconst replaceStyles = (\n    node: HTMLElement,\n    _: Node,\n    config: SquireConfig,\n): HTMLElement => {\n    const style = node.style;\n    let newTreeBottom: HTMLElement | undefined;\n    let newTreeTop: HTMLElement | undefined;\n\n    for (const attr in styleToSemantic) {\n        const converter = styleToSemantic[attr];\n        const css = style.getPropertyValue(attr);\n        if (css && converter.regexp.test(css)) {\n            const el = converter.replace(config.classNames, css);\n            if (\n                el.nodeName === node.nodeName &&\n                el.className === node.className\n            ) {\n                continue;\n            }\n            if (!newTreeTop) {\n                newTreeTop = el;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(el);\n            }\n            newTreeBottom = el;\n            node.style.removeProperty(attr);\n        }\n    }\n\n    if (newTreeTop && newTreeBottom) {\n        newTreeBottom.appendChild(empty(node));\n        if (node.style.cssText) {\n            node.appendChild(newTreeTop);\n        } else {\n            replaceWith(node, newTreeTop);\n        }\n    }\n\n    return newTreeBottom || node;\n};\n\nconst replaceWithTag = (tag: string) => {\n    return (node: HTMLElement, parent: Node) => {\n        const el = createElement(tag);\n        const attributes = node.attributes;\n        for (let i = 0, l = attributes.length; i < l; i += 1) {\n            const attribute = attributes[i];\n            el.setAttribute(attribute.name, attribute.value);\n        }\n        parent.replaceChild(el, node);\n        el.appendChild(empty(node));\n        return el;\n    };\n};\n\nconst fontSizes: Record<string, string> = {\n    '1': '10',\n    '2': '13',\n    '3': '16',\n    '4': '18',\n    '5': '24',\n    '6': '32',\n    '7': '48',\n};\n\nconst stylesRewriters: Record<string, StyleRewriter> = {\n    STRONG: replaceWithTag('B'),\n    EM: replaceWithTag('I'),\n    INS: replaceWithTag('U'),\n    STRIKE: replaceWithTag('S'),\n    SPAN: replaceStyles,\n    FONT: (\n        node: HTMLElement,\n        parent: Node,\n        config: SquireConfig,\n    ): HTMLElement => {\n        const font = node as HTMLFontElement;\n        const face = font.face;\n        const size = font.size;\n        let color = font.color;\n        const classNames = config.classNames;\n        let fontSpan: HTMLElement;\n        let sizeSpan: HTMLElement;\n        let colorSpan: HTMLElement;\n        let newTreeBottom: HTMLElement | undefined;\n        let newTreeTop: HTMLElement | undefined;\n        if (face) {\n            fontSpan = createElement('SPAN', {\n                class: classNames.fontFamily,\n                style: 'font-family:' + face,\n            });\n            newTreeTop = fontSpan;\n            newTreeBottom = fontSpan;\n        }\n        if (size) {\n            sizeSpan = createElement('SPAN', {\n                class: classNames.fontSize,\n                style: 'font-size:' + fontSizes[size] + 'px',\n            });\n            if (!newTreeTop) {\n                newTreeTop = sizeSpan;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(sizeSpan);\n            }\n            newTreeBottom = sizeSpan;\n        }\n        if (color && /^#?([\\dA-F]{3}){1,2}$/i.test(color)) {\n            if (color.charAt(0) !== '#') {\n                color = '#' + color;\n            }\n            colorSpan = createElement('SPAN', {\n                class: classNames.color,\n                style: 'color:' + color,\n            });\n            if (!newTreeTop) {\n                newTreeTop = colorSpan;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(colorSpan);\n            }\n            newTreeBottom = colorSpan;\n        }\n        if (!newTreeTop || !newTreeBottom) {\n            newTreeTop = newTreeBottom = createElement('SPAN');\n        }\n        parent.replaceChild(newTreeTop, font);\n        newTreeBottom.appendChild(empty(font));\n        return newTreeBottom;\n    },\n    TT: (node: Node, parent: Node, config: SquireConfig): HTMLElement => {\n        const el = createElement('SPAN', {\n            class: config.classNames.fontFamily,\n            style: 'font-family:menlo,consolas,\"courier new\",monospace',\n        });\n        parent.replaceChild(el, node);\n        el.appendChild(empty(node));\n        return el;\n    },\n};\n\nconst allowedBlock =\n    /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/;\n\nconst blacklist = /^(?:HEAD|META|STYLE)/;\n\n/*\n    Two purposes:\n\n    1. Remove nodes we don't want, such as weird <o:p> tags, comment nodes\n       and whitespace nodes.\n    2. Convert inline tags into our preferred format.\n*/\nconst cleanTree = (\n    node: Node,\n    config: SquireConfig,\n    preserveWS?: boolean,\n): Node => {\n    const children = node.childNodes;\n\n    let nonInlineParent = node;\n    while (isInline(nonInlineParent)) {\n        nonInlineParent = nonInlineParent.parentNode!;\n    }\n    const walker = new TreeIterator<Element | Text>(\n        nonInlineParent,\n        SHOW_ELEMENT_OR_TEXT,\n    );\n\n    for (let i = 0, l = children.length; i < l; i += 1) {\n        let child = children[i];\n        const nodeName = child.nodeName;\n        const rewriter = stylesRewriters[nodeName];\n        if (child instanceof HTMLElement) {\n            const childLength = child.childNodes.length;\n            if (rewriter) {\n                child = rewriter(child, node, config);\n            } else if (blacklist.test(nodeName)) {\n                node.removeChild(child);\n                i -= 1;\n                l -= 1;\n                continue;\n            } else if (!allowedBlock.test(nodeName) && !isInline(child)) {\n                i -= 1;\n                l += childLength - 1;\n                node.replaceChild(empty(child), child);\n                continue;\n            }\n            if (childLength) {\n                cleanTree(child, config, preserveWS || nodeName === 'PRE');\n            }\n        } else {\n            if (child instanceof Text) {\n                let data = child.data;\n                const startsWithWS = !notWS.test(data.charAt(0));\n                const endsWithWS = !notWS.test(data.charAt(data.length - 1));\n                if (preserveWS || (!startsWithWS && !endsWithWS)) {\n                    continue;\n                }\n                // Iterate through the nodes; if we hit some other content\n                // before the start of a new block we don't trim\n                if (startsWithWS) {\n                    walker.currentNode = child;\n                    let sibling;\n                    while ((sibling = walker.previousPONode())) {\n                        if (\n                            sibling.nodeName === 'IMG' ||\n                            (sibling instanceof Text &&\n                                notWS.test(sibling.data))\n                        ) {\n                            break;\n                        }\n                        if (!isInline(sibling)) {\n                            sibling = null;\n                            break;\n                        }\n                    }\n                    data = data.replace(/^[ \\t\\r\\n]+/g, sibling ? ' ' : '');\n                }\n                if (endsWithWS) {\n                    walker.currentNode = child;\n                    let sibling;\n                    while ((sibling = walker.nextNode())) {\n                        if (\n                            sibling.nodeName === 'IMG' ||\n                            (sibling instanceof Text &&\n                                notWS.test(sibling.data))\n                        ) {\n                            break;\n                        }\n                        if (!isInline(sibling)) {\n                            sibling = null;\n                            break;\n                        }\n                    }\n                    data = data.replace(/[ \\t\\r\\n]+$/g, sibling ? ' ' : '');\n                }\n                if (data) {\n                    child.data = data;\n                    continue;\n                }\n            }\n            node.removeChild(child);\n            i -= 1;\n            l -= 1;\n        }\n    }\n    return node;\n};\n\n// ---\n\nconst removeEmptyInlines = (node: Node): void => {\n    const children = node.childNodes;\n    let l = children.length;\n    while (l--) {\n        const child = children[l];\n        if (child instanceof Element && !isLeaf(child)) {\n            removeEmptyInlines(child);\n            if (isInline(child) && !child.firstChild) {\n                node.removeChild(child);\n            }\n        } else if (child instanceof Text && !child.data) {\n            node.removeChild(child);\n        }\n    }\n};\n\n// ---\n\n// <br> elements are treated specially, and differently depending on the\n// browser, when in rich text editor mode. When adding HTML from external\n// sources, we must remove them, replacing the ones that actually affect\n// line breaks by wrapping the inline text in a <div>. Browsers that want <br>\n// elements at the end of each block will then have them added back in a later\n// fixCursor method call.\nconst cleanupBRs = (\n    node: Element | DocumentFragment,\n    root: Element,\n    keepForBlankLine: boolean,\n): void => {\n    const brs: NodeListOf<HTMLBRElement> = node.querySelectorAll('BR');\n    const brBreaksLine: boolean[] = [];\n    let l = brs.length;\n\n    // Must calculate whether the <br> breaks a line first, because if we\n    // have two <br>s next to each other, after the first one is converted\n    // to a block split, the second will be at the end of a block and\n    // therefore seem to not be a line break. But in its original context it\n    // was, so we should also convert it to a block split.\n    for (let i = 0; i < l; i += 1) {\n        brBreaksLine[i] = isLineBreak(brs[i], keepForBlankLine);\n    }\n    while (l--) {\n        const br = brs[l];\n        // Cleanup may have removed it\n        const parent = br.parentNode;\n        if (!parent) {\n            continue;\n        }\n        // If it doesn't break a line, just remove it; it's not doing\n        // anything useful. We'll add it back later if required by the\n        // browser. If it breaks a line, wrap the content in div tags\n        // and replace the brs.\n        if (!brBreaksLine[l]) {\n            detach(br);\n        } else if (!isInline(parent)) {\n            fixContainer(parent, root);\n        }\n    }\n};\n\n// ---\n\nconst escapeHTML = (text: string): string => {\n    return text\n        .split('&')\n        .join('&amp;')\n        .split('<')\n        .join('&lt;')\n        .split('>')\n        .join('&gt;')\n        .split('\"')\n        .join('&quot;');\n};\n\n// ---\n\nexport { cleanTree, cleanupBRs, isLineBreak, removeEmptyInlines, escapeHTML };\n", "import { TreeIterator, SHOW_ELEMENT } from './TreeIterator';\nimport { isBlock } from './Category';\n\n// ---\n\nconst getBlockWalker = (\n    node: Node,\n    root: Element | DocumentFragment,\n): TreeIterator<HTMLElement> => {\n    const walker = new TreeIterator<HTMLElement>(root, SHOW_ELEMENT, isBlock);\n    walker.currentNode = node;\n    return walker;\n};\n\nconst getPreviousBlock = (\n    node: Node,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const block = getBlockWalker(node, root).previousNode();\n    return block !== root ? block : null;\n};\n\nconst getNextBlock = (\n    node: Node,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const block = getBlockWalker(node, root).nextNode();\n    return block !== root ? block : null;\n};\n\nconst isEmptyBlock = (block: Element): boolean => {\n    return !block.textContent && !block.querySelector('IMG');\n};\n\n// ---\n\nexport { getBlockWalker, getPreviousBlock, getNextBlock, isEmptyBlock };\n", "import { isInline, isBlock } from '../node/Category';\nimport { getPreviousBlock, getNextBlock } from '../node/Block';\nimport { getNodeBeforeOffset, getNodeAfterOffset } from '../node/Node';\nimport { ZWS, notWS } from '../Constants';\nimport { isNodeContainedInRange } from './Boundaries';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from '../node/TreeIterator';\n\n// ---\n\n// Returns the first block at least partially contained by the range,\n// or null if no block is contained by the range.\nconst getStartBlockOfRange = (\n    range: Range,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const container = range.startContainer;\n    let block: HTMLElement | null;\n\n    // If inline, get the containing block.\n    if (isInline(container)) {\n        block = getPreviousBlock(container, root);\n    } else if (\n        container !== root &&\n        container instanceof HTMLElement &&\n        isBlock(container)\n    ) {\n        block = container;\n    } else {\n        const node = getNodeBeforeOffset(container, range.startOffset);\n        block = getNextBlock(node, root);\n    }\n    // Check the block actually intersects the range\n    return block && isNodeContainedInRange(range, block, true) ? block : null;\n};\n\n// Returns the last block at least partially contained by the range,\n// or null if no block is contained by the range.\nconst getEndBlockOfRange = (\n    range: Range,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const container = range.endContainer;\n    let block: HTMLElement | null;\n\n    // If inline, get the containing block.\n    if (isInline(container)) {\n        block = getPreviousBlock(container, root);\n    } else if (\n        container !== root &&\n        container instanceof HTMLElement &&\n        isBlock(container)\n    ) {\n        block = container;\n    } else {\n        let node = getNodeAfterOffset(container, range.endOffset);\n        if (!node || !root.contains(node)) {\n            node = root;\n            let child: Node | null;\n            while ((child = node.lastChild)) {\n                node = child;\n            }\n        }\n        block = getPreviousBlock(node, root);\n    }\n    // Check the block actually intersects the range\n    return block && isNodeContainedInRange(range, block, true) ? block : null;\n};\n\nconst isContent = (node: Element | Text): boolean => {\n    return node instanceof Text\n        ? notWS.test(node.data)\n        : node.nodeName === 'IMG';\n};\n\nconst rangeDoesStartAtBlockBoundary = (\n    range: Range,\n    root: Element,\n): boolean => {\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    let nodeAfterCursor: Node | null;\n\n    // If in the middle or end of a text node, we're not at the boundary.\n    if (startContainer instanceof Text) {\n        const text = startContainer.data;\n        for (let i = startOffset; i > 0; i -= 1) {\n            if (text.charAt(i - 1) !== ZWS) {\n                return false;\n            }\n        }\n        nodeAfterCursor = startContainer;\n    } else {\n        nodeAfterCursor = getNodeAfterOffset(startContainer, startOffset);\n        if (nodeAfterCursor && !root.contains(nodeAfterCursor)) {\n            nodeAfterCursor = null;\n        }\n        // The cursor was right at the end of the document\n        if (!nodeAfterCursor) {\n            nodeAfterCursor = getNodeBeforeOffset(startContainer, startOffset);\n            if (nodeAfterCursor instanceof Text && nodeAfterCursor.length) {\n                return false;\n            }\n        }\n    }\n\n    // Otherwise, look for any previous content in the same block.\n    const block = getStartBlockOfRange(range, root);\n    if (!block) {\n        return false;\n    }\n    const contentWalker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        isContent,\n    );\n    contentWalker.currentNode = nodeAfterCursor;\n\n    return !contentWalker.previousNode();\n};\n\nconst rangeDoesEndAtBlockBoundary = (range: Range, root: Element): boolean => {\n    const endContainer = range.endContainer;\n    const endOffset = range.endOffset;\n    let currentNode: Node;\n\n    // If in a text node with content, and not at the end, we're not\n    // at the boundary. Ignore ZWS.\n    if (endContainer instanceof Text) {\n        const text = endContainer.data;\n        const length = text.length;\n        for (let i = endOffset; i < length; i += 1) {\n            if (text.charAt(i) !== ZWS) {\n                return false;\n            }\n        }\n        currentNode = endContainer;\n    } else {\n        currentNode = getNodeBeforeOffset(endContainer, endOffset);\n    }\n\n    // Otherwise, look for any further content in the same block.\n    const block = getEndBlockOfRange(range, root);\n    if (!block) {\n        return false;\n    }\n    const contentWalker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        isContent,\n    );\n    contentWalker.currentNode = currentNode;\n    return !contentWalker.nextNode();\n};\n\nconst expandRangeToBlockBoundaries = (range: Range, root: Element): void => {\n    const start = getStartBlockOfRange(range, root);\n    const end = getEndBlockOfRange(range, root);\n    let parent: Node;\n\n    if (start && end) {\n        parent = start.parentNode!;\n        range.setStart(parent, Array.from(parent.childNodes).indexOf(start));\n        parent = end.parentNode!;\n        range.setEnd(parent, Array.from(parent.childNodes).indexOf(end) + 1);\n    }\n};\n\n// ---\n\nexport {\n    getStartBlockOfRange,\n    getEndBlockOfRange,\n    rangeDoesStartAtBlockBoundary,\n    rangeDoesEndAtBlockBoundary,\n    expandRangeToBlockBoundaries,\n};\n", "import { cleanupBRs } from '../Clean';\nimport {\n    split,\n    fixCursor,\n    mergeWithBlock,\n    fixContainer,\n    mergeContainers,\n} from '../node/MergeSplit';\nimport { detach, getNearest, getLength } from '../node/Node';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from '../node/TreeIterator';\nimport { isInline, isContainer, isLeaf } from '../node/Category';\nimport { getNextBlock, isEmptyBlock, getPreviousBlock } from '../node/Block';\nimport {\n    getStartBlockOfRange,\n    getEndBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n    rangeDoesStartAtBlockBoundary,\n} from './Block';\nimport {\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n} from './Boundaries';\n\n// ---\n\nfunction createRange(startContainer: Node, startOffset: number): Range;\nfunction createRange(\n    startContainer: Node,\n    startOffset: number,\n    endContainer: Node,\n    endOffset: number,\n): Range;\nfunction createRange(\n    startContainer: Node,\n    startOffset: number,\n    endContainer?: Node,\n    endOffset?: number,\n): Range {\n    const range = document.createRange();\n    range.setStart(startContainer, startOffset);\n    if (endContainer && typeof endOffset === 'number') {\n        range.setEnd(endContainer, endOffset);\n    } else {\n        range.setEnd(startContainer, startOffset);\n    }\n    return range;\n}\n\nconst insertNodeInRange = (range: Range, node: Node): void => {\n    // Insert at start.\n    let { startContainer, startOffset, endContainer, endOffset } = range;\n    let children: NodeListOf<ChildNode>;\n\n    // If part way through a text node, split it.\n    if (startContainer instanceof Text) {\n        const parent = startContainer.parentNode!;\n        children = parent.childNodes;\n        if (startOffset === startContainer.length) {\n            startOffset = Array.from(children).indexOf(startContainer) + 1;\n            if (range.collapsed) {\n                endContainer = parent;\n                endOffset = startOffset;\n            }\n        } else {\n            if (startOffset) {\n                const afterSplit = startContainer.splitText(startOffset);\n                if (endContainer === startContainer) {\n                    endOffset -= startOffset;\n                    endContainer = afterSplit;\n                } else if (endContainer === parent) {\n                    endOffset += 1;\n                }\n                startContainer = afterSplit;\n            }\n            startOffset = Array.from(children).indexOf(\n                startContainer as ChildNode,\n            );\n        }\n        startContainer = parent;\n    } else {\n        children = startContainer.childNodes;\n    }\n\n    const childCount = children.length;\n\n    if (startOffset === childCount) {\n        startContainer.appendChild(node);\n    } else {\n        startContainer.insertBefore(node, children[startOffset]);\n    }\n\n    if (startContainer === endContainer) {\n        endOffset += children.length - childCount;\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\n/**\n * Removes the contents of the range and returns it as a DocumentFragment.\n * The range at the end will be at the same position, with the edges just\n * before/after the split. If the start/end have the same parents, it will\n * be collapsed.\n */\nconst extractContentsOfRange = (\n    range: Range,\n    common: Node | null,\n    root: Element,\n): DocumentFragment => {\n    const frag = document.createDocumentFragment();\n    if (range.collapsed) {\n        return frag;\n    }\n\n    if (!common) {\n        common = range.commonAncestorContainer;\n    }\n    if (common instanceof Text) {\n        common = common.parentNode!;\n    }\n\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n\n    let endContainer = split(range.endContainer, range.endOffset, common, root);\n    let endOffset = 0;\n\n    let node = split(startContainer, startOffset, common, root);\n    while (node && node !== endContainer) {\n        const next = node.nextSibling;\n        frag.appendChild(node);\n        node = next;\n    }\n\n    // Merge text nodes if adjacent\n    if (startContainer instanceof Text && endContainer instanceof Text) {\n        startContainer.appendData(endContainer.data);\n        detach(endContainer);\n        endContainer = startContainer;\n        endOffset = startOffset;\n    }\n\n    range.setStart(startContainer, startOffset);\n    if (endContainer) {\n        range.setEnd(endContainer, endOffset);\n    } else {\n        // endContainer will be null if at end of parent's child nodes list.\n        range.setEnd(common, common.childNodes.length);\n    }\n\n    fixCursor(common);\n\n    return frag;\n};\n\n/**\n * Returns the next/prev node that's part of the same inline content.\n */\nconst getAdjacentInlineNode = (\n    iterator: TreeIterator<Node>,\n    method: 'nextNode' | 'previousPONode',\n    node: Node,\n): Node | null => {\n    iterator.currentNode = node;\n    let nextNode: Node | null;\n    while ((nextNode = iterator[method]())) {\n        if (nextNode instanceof Text || isLeaf(nextNode)) {\n            return nextNode;\n        }\n        if (!isInline(nextNode)) {\n            return null;\n        }\n    }\n    return null;\n};\n\nconst deleteContentsOfRange = (\n    range: Range,\n    root: Element,\n): DocumentFragment => {\n    const startBlock = getStartBlockOfRange(range, root);\n    let endBlock = getEndBlockOfRange(range, root);\n    const needsMerge = startBlock !== endBlock;\n\n    // Move boundaries up as much as possible without exiting block,\n    // to reduce need to split.\n    if (startBlock && endBlock) {\n        moveRangeBoundariesDownTree(range);\n        moveRangeBoundariesUpTree(range, startBlock, endBlock, root);\n    }\n\n    // Remove selected range\n    const frag = extractContentsOfRange(range, null, root);\n\n    // Move boundaries back down tree as far as possible.\n    moveRangeBoundariesDownTree(range);\n\n    // If we split into two different blocks, merge the blocks.\n    if (needsMerge) {\n        // endBlock will have been split, so need to refetch\n        endBlock = getEndBlockOfRange(range, root);\n        if (startBlock && endBlock && startBlock !== endBlock) {\n            mergeWithBlock(startBlock, endBlock, range, root);\n        }\n    }\n\n    // Ensure block has necessary children\n    if (startBlock) {\n        fixCursor(startBlock);\n    }\n\n    // Ensure root has a block-level element in it.\n    const child = root.firstChild;\n    if (!child || child.nodeName === 'BR') {\n        fixCursor(root);\n        if (root.firstChild) {\n            range.selectNodeContents(root.firstChild);\n        }\n    }\n\n    range.collapse(true);\n\n    // Now we may need to swap a space for a nbsp if the browser is going\n    // to swallow it due to HTML whitespace rules:\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    const iterator = new TreeIterator(root, SHOW_ELEMENT_OR_TEXT);\n\n    // Find the character after cursor point\n    let afterNode: Node | null = startContainer;\n    let afterOffset = startOffset;\n    if (!(afterNode instanceof Text) || afterOffset === afterNode.data.length) {\n        afterNode = getAdjacentInlineNode(iterator, 'nextNode', afterNode);\n        afterOffset = 0;\n    }\n\n    // Find the character before cursor point\n    let beforeNode: Node | null = startContainer;\n    let beforeOffset = startOffset - 1;\n    if (!(beforeNode instanceof Text) || beforeOffset === -1) {\n        beforeNode = getAdjacentInlineNode(\n            iterator,\n            'previousPONode',\n            afterNode ||\n                (startContainer instanceof Text\n                    ? startContainer\n                    : startContainer.childNodes[startOffset] || startContainer),\n        );\n        if (beforeNode instanceof Text) {\n            beforeOffset = beforeNode.data.length;\n        }\n    }\n\n    // If range starts at block boundary and character after cursor point\n    // is a space, replace with nbsp\n    let node = null;\n    let offset = 0;\n    if (\n        afterNode instanceof Text &&\n        afterNode.data.charAt(afterOffset) === ' ' &&\n        rangeDoesStartAtBlockBoundary(range, root)\n    ) {\n        node = afterNode;\n        offset = afterOffset;\n    } else if (\n        beforeNode instanceof Text &&\n        beforeNode.data.charAt(beforeOffset) === ' '\n    ) {\n        // If character before cursor point is a space, replace with nbsp\n        // if either:\n        // a) There is a space after it; or\n        // b) The point after is the end of the block\n        if (\n            (afterNode instanceof Text &&\n                afterNode.data.charAt(afterOffset) === ' ') ||\n            rangeDoesEndAtBlockBoundary(range, root)\n        ) {\n            node = beforeNode;\n            offset = beforeOffset;\n        }\n    }\n    if (node) {\n        node.replaceData(offset, 1, '\u00A0'); // nbsp\n    }\n    // Range needs to be put back in place\n    range.setStart(startContainer, startOffset);\n    range.collapse(true);\n\n    return frag;\n};\n\n// Contents of range will be deleted.\n// After method, range will be around inserted content\nconst insertTreeFragmentIntoRange = (\n    range: Range,\n    frag: DocumentFragment,\n    root: Element,\n): void => {\n    const firstInFragIsInline = frag.firstChild && isInline(frag.firstChild);\n    let node: Node | null;\n\n    // Fixup content: ensure no top-level inline, and add cursor fix elements.\n    fixContainer(frag, root);\n    node = frag;\n    while ((node = getNextBlock(node, root))) {\n        fixCursor(node);\n    }\n\n    // Delete any selected content.\n    if (!range.collapsed) {\n        deleteContentsOfRange(range, root);\n    }\n\n    // Move range down into text nodes.\n    moveRangeBoundariesDownTree(range);\n    range.collapse(false); // collapse to end\n\n    // Where will we split up to? First blockquote parent, otherwise root.\n    const stopPoint =\n        getNearest(range.endContainer, root, 'BLOCKQUOTE') || root;\n\n    // Merge the contents of the first block in the frag with the focused block.\n    // If there are contents in the block after the focus point, collect this\n    // up to insert in the last block later. This preserves the style that was\n    // present in this bit of the page.\n    //\n    // If the block being inserted into is empty though, replace it instead of\n    // merging if the fragment had block contents.\n    // e.g. <blockquote><p>Foo</p></blockquote>\n    // This seems a reasonable approximation of user intent.\n    let block = getStartBlockOfRange(range, root);\n    let blockContentsAfterSplit: DocumentFragment | null = null;\n    const firstBlockInFrag = getNextBlock(frag, frag);\n    const replaceBlock = !firstInFragIsInline && !!block && isEmptyBlock(block);\n    if (\n        block &&\n        firstBlockInFrag &&\n        !replaceBlock &&\n        // Don't merge table cells or PRE elements into block\n        !getNearest(firstBlockInFrag, frag, 'PRE') &&\n        !getNearest(firstBlockInFrag, frag, 'TABLE')\n    ) {\n        moveRangeBoundariesUpTree(range, block, block, root);\n        range.collapse(true); // collapse to start\n        let container = range.endContainer;\n        let offset = range.endOffset;\n        // Remove trailing <br> \u2013 we don't want this considered content to be\n        // inserted again later\n        cleanupBRs(block as HTMLElement, root, false);\n        if (isInline(container)) {\n            // Split up to block parent.\n            const nodeAfterSplit = split(\n                container,\n                offset,\n                getPreviousBlock(container, root) || root,\n                root,\n            ) as Node;\n            container = nodeAfterSplit.parentNode!;\n            offset = Array.from(container.childNodes).indexOf(\n                nodeAfterSplit as ChildNode,\n            );\n        }\n        if (/*isBlock( container ) && */ offset !== getLength(container)) {\n            // Collect any inline contents of the block after the range point\n            blockContentsAfterSplit = document.createDocumentFragment();\n            while ((node = container.childNodes[offset])) {\n                blockContentsAfterSplit.appendChild(node);\n            }\n        }\n        // And merge the first block in.\n        mergeWithBlock(container, firstBlockInFrag, range, root);\n\n        // And where we will insert\n        offset =\n            Array.from(container.parentNode!.childNodes).indexOf(\n                container as ChildNode,\n            ) + 1;\n        container = container.parentNode!;\n        range.setEnd(container, offset);\n    }\n\n    // Is there still any content in the fragment?\n    if (getLength(frag)) {\n        if (replaceBlock && block) {\n            range.setEndBefore(block);\n            range.collapse(false);\n            detach(block);\n        }\n        moveRangeBoundariesUpTree(range, stopPoint, stopPoint, root);\n        // Now split after block up to blockquote (if a parent) or root\n        let nodeAfterSplit = split(\n            range.endContainer,\n            range.endOffset,\n            stopPoint,\n            root,\n        ) as Node | null;\n        const nodeBeforeSplit = nodeAfterSplit\n            ? nodeAfterSplit.previousSibling\n            : stopPoint.lastChild;\n        stopPoint.insertBefore(frag, nodeAfterSplit);\n        if (nodeAfterSplit) {\n            range.setEndBefore(nodeAfterSplit);\n        } else {\n            range.setEnd(stopPoint, getLength(stopPoint));\n        }\n        block = getEndBlockOfRange(range, root);\n\n        // Get a reference that won't be invalidated if we merge containers.\n        moveRangeBoundariesDownTree(range);\n        const container = range.endContainer;\n        const offset = range.endOffset;\n\n        // Merge inserted containers with edges of split\n        if (nodeAfterSplit && isContainer(nodeAfterSplit)) {\n            mergeContainers(nodeAfterSplit, root);\n        }\n        nodeAfterSplit = nodeBeforeSplit && nodeBeforeSplit.nextSibling;\n        if (nodeAfterSplit && isContainer(nodeAfterSplit)) {\n            mergeContainers(nodeAfterSplit, root);\n        }\n        range.setEnd(container, offset);\n    }\n\n    // Insert inline content saved from before.\n    if (blockContentsAfterSplit && block) {\n        const tempRange = range.cloneRange();\n        fixCursor(blockContentsAfterSplit);\n        mergeWithBlock(block, blockContentsAfterSplit, tempRange, root);\n        range.setEnd(tempRange.endContainer, tempRange.endOffset);\n    }\n    moveRangeBoundariesDownTree(range);\n};\n\n// ---\n\nexport {\n    createRange,\n    deleteContentsOfRange,\n    extractContentsOfRange,\n    insertNodeInRange,\n    insertTreeFragmentIntoRange,\n};\n", "import { SHOW_ELEMENT_OR_TEXT, TreeIterator } from '../node/TreeIterator';\nimport { isNodeContainedInRange } from './Boundaries';\nimport { isInline } from '../node/Category';\n\n// ---\n\nconst getTextContentsOfRange = (range: Range) => {\n    if (range.collapsed) {\n        return '';\n    }\n    const startContainer = range.startContainer;\n    const endContainer = range.endContainer;\n    const walker = new TreeIterator<Element | Text>(\n        range.commonAncestorContainer,\n        SHOW_ELEMENT_OR_TEXT,\n        (node) => {\n            return isNodeContainedInRange(range, node, true);\n        },\n    );\n    walker.currentNode = startContainer;\n\n    let node: Node | null = startContainer;\n    let textContent = '';\n    let addedTextInBlock = false;\n    let value: string;\n\n    if (\n        (!(node instanceof Element) && !(node instanceof Text)) ||\n        !walker.filter(node)\n    ) {\n        node = walker.nextNode();\n    }\n\n    while (node) {\n        if (node instanceof Text) {\n            value = node.data;\n            if (value && /\\S/.test(value)) {\n                if (node === endContainer) {\n                    value = value.slice(0, range.endOffset);\n                }\n                if (node === startContainer) {\n                    value = value.slice(range.startOffset);\n                }\n                textContent += value;\n                addedTextInBlock = true;\n            }\n        } else if (\n            node.nodeName === 'BR' ||\n            (addedTextInBlock && !isInline(node))\n        ) {\n            textContent += '\\n';\n            addedTextInBlock = false;\n        }\n        node = walker.nextNode();\n    }\n    // Replace nbsp with regular space;\n    // eslint-disable-next-line no-irregular-whitespace\n    textContent = textContent.replace(/\u00A0/g, ' ');\n\n    return textContent;\n};\n\n// ---\n\nexport { getTextContentsOfRange };\n", "import { isWin, isGecko, isLegacyEdge, notWS } from './Constants';\nimport { createElement, detach } from './node/Node';\nimport { getStartBlockOfRange, getEndBlockOfRange } from './range/Block';\nimport { createRange, deleteContentsOfRange } from './range/InsertDelete';\nimport {\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n} from './range/Boundaries';\n\nimport type { Squire } from './Editor';\nimport { getTextContentsOfRange } from './range/Contents';\n\n// ---\n\nconst indexOf = Array.prototype.indexOf;\n\nconst extractRangeToClipboard = (\n    event: ClipboardEvent,\n    range: Range,\n    root: HTMLElement,\n    removeRangeFromDocument: boolean,\n    toCleanHTML: null | ((html: string) => string),\n    toPlainText: null | ((html: string) => string),\n    plainTextOnly: boolean,\n): boolean => {\n    // Edge only seems to support setting plain text as of 2016-03-11.\n    const clipboardData = event.clipboardData;\n    if (isLegacyEdge || !clipboardData) {\n        return false;\n    }\n    // First get the plain text version from the range (unless we have a custom\n    // HTML -> Text conversion fn)\n    let text = toPlainText ? '' : getTextContentsOfRange(range);\n\n    // Clipboard content should include all parents within block, or all\n    // parents up to root if selection across blocks\n    const startBlock = getStartBlockOfRange(range, root);\n    const endBlock = getEndBlockOfRange(range, root);\n    let copyRoot = root;\n\n    // If the content is not in well-formed blocks, the start and end block\n    // may be the same, but actually the range goes outside it. Must check!\n    if (\n        startBlock === endBlock &&\n        startBlock?.contains(range.commonAncestorContainer)\n    ) {\n        copyRoot = startBlock;\n    }\n\n    // Extract the contents\n    let contents: Node;\n    if (removeRangeFromDocument) {\n        contents = deleteContentsOfRange(range, root);\n    } else {\n        // Clone range to mutate, then move up as high as possible without\n        // passing the copy root node.\n        range = range.cloneRange();\n        moveRangeBoundariesDownTree(range);\n        moveRangeBoundariesUpTree(range, copyRoot, copyRoot, root);\n        contents = range.cloneContents();\n    }\n\n    // Add any other parents not in extracted content, up to copy root\n    let parent = range.commonAncestorContainer;\n    if (parent instanceof Text) {\n        parent = parent.parentNode!;\n    }\n    while (parent && parent !== copyRoot) {\n        const newContents = parent.cloneNode(false);\n        newContents.appendChild(contents);\n        contents = newContents;\n        parent = parent.parentNode!;\n    }\n\n    // Get HTML version of data\n    let html: string | undefined;\n    if (\n        contents.childNodes.length === 1 &&\n        contents.childNodes[0] instanceof Text\n    ) {\n        // Replace nbsp with regular space;\n        // eslint-disable-next-line no-irregular-whitespace\n        text = contents.childNodes[0].data.replace(/\u00A0/g, ' ');\n        plainTextOnly = true;\n    } else {\n        const node = createElement('DIV') as HTMLDivElement;\n        node.appendChild(contents);\n        html = node.innerHTML;\n        if (toCleanHTML) {\n            html = toCleanHTML(html);\n        }\n    }\n\n    // Get Text version of data if converting from HTML\n    if (toPlainText && html !== undefined) {\n        text = toPlainText(html);\n    }\n\n    // Firefox (and others?) returns unix line endings (\\n) even on Windows.\n    // If on Windows, normalise to \\r\\n, since Notepad and some other crappy\n    // apps do not understand just \\n.\n    if (isWin) {\n        text = text.replace(/\\r?\\n/g, '\\r\\n');\n    }\n\n    // Set clipboard data\n    if (!plainTextOnly && html && text !== html) {\n        clipboardData.setData('text/html', html);\n    }\n    clipboardData.setData('text/plain', text);\n    event.preventDefault();\n\n    return true;\n};\n\n// ---\n\nconst _onCut = function (this: Squire, event: ClipboardEvent): void {\n    const range: Range = this.getSelection();\n    const root: HTMLElement = this._root;\n\n    // Nothing to do\n    if (range.collapsed) {\n        event.preventDefault();\n        return;\n    }\n\n    // Save undo checkpoint\n    this.saveUndoState(range);\n\n    const handled = extractRangeToClipboard(\n        event,\n        range,\n        root,\n        true,\n        this._config.willCutCopy,\n        this._config.toPlainText,\n        false,\n    );\n    if (!handled) {\n        setTimeout(() => {\n            try {\n                // If all content removed, ensure div at start of root.\n                this._ensureBottomLine();\n            } catch (error) {\n                this._config.didError(error);\n            }\n        }, 0);\n    }\n\n    this.setSelection(range);\n};\n\nconst _onCopy = function (this: Squire, event: ClipboardEvent): void {\n    extractRangeToClipboard(\n        event,\n        this.getSelection(),\n        this._root,\n        false,\n        this._config.willCutCopy,\n        this._config.toPlainText,\n        false,\n    );\n};\n\n// Need to monitor for shift key like this, as event.shiftKey is not available\n// in paste event.\nconst _monitorShiftKey = function (this: Squire, event: KeyboardEvent): void {\n    this._isShiftDown = event.shiftKey;\n};\n\nconst _onPaste = function (this: Squire, event: ClipboardEvent): void {\n    const clipboardData = event.clipboardData;\n    const items = clipboardData?.items;\n    const choosePlain: boolean | undefined = this._isShiftDown;\n    let hasRTF = false;\n    let hasImage = false;\n    let plainItem: null | DataTransferItem = null;\n    let htmlItem: null | DataTransferItem = null;\n\n    // Current HTML5 Clipboard interface\n    // ---------------------------------\n    // https://html.spec.whatwg.org/multipage/interaction.html\n    if (items) {\n        let l = items.length;\n        while (l--) {\n            const item = items[l];\n            const type = item.type;\n            if (type === 'text/html') {\n                htmlItem = item;\n                // iOS copy URL gives you type text/uri-list which is just a list\n                // of 1 or more URLs separated by new lines. Can just treat as\n                // plain text.\n            } else if (type === 'text/plain' || type === 'text/uri-list') {\n                plainItem = item;\n            } else if (type === 'text/rtf') {\n                hasRTF = true;\n            } else if (/^image\\/.*/.test(type)) {\n                hasImage = true;\n            }\n        }\n\n        // Treat image paste as a drop of an image file. When you copy\n        // an image in Chrome/Firefox (at least), it copies the image data\n        // but also an HTML version (referencing the original URL of the image)\n        // and a plain text version.\n        //\n        // However, when you copy in Excel, you get html, rtf, text, image;\n        // in this instance you want the html version! So let's try using\n        // the presence of text/rtf as an indicator to choose the html version\n        // over the image.\n        if (hasImage && !(hasRTF && htmlItem)) {\n            event.preventDefault();\n            this.fireEvent('pasteImage', {\n                clipboardData,\n            });\n            return;\n        }\n\n        // Edge only provides access to plain text as of 2016-03-11 and gives no\n        // indication there should be an HTML part. However, it does support\n        // access to image data, so we check for that first. Otherwise though,\n        // fall through to fallback clipboard handling methods\n        if (!isLegacyEdge) {\n            event.preventDefault();\n            if (htmlItem && (!choosePlain || !plainItem)) {\n                htmlItem.getAsString((html) => {\n                    this.insertHTML(html, true);\n                });\n            } else if (plainItem) {\n                plainItem.getAsString((text) => {\n                    // If we have a selection and text is solely a URL,\n                    // just make the text a link.\n                    let isLink = false;\n                    const range = this.getSelection();\n                    if (!range.collapsed && notWS.test(range.toString())) {\n                        const match = this.linkRegExp.exec(text);\n                        isLink = !!match && match[0].length === text.length;\n                    }\n                    if (isLink) {\n                        this.makeLink(text);\n                    } else {\n                        this.insertPlainText(text, true);\n                    }\n                });\n            }\n            return;\n        }\n    }\n\n    // Old interface\n    // -------------\n\n    // Safari (and indeed many other OS X apps) copies stuff as text/rtf\n    // rather than text/html; even from a webpage in Safari. The only way\n    // to get an HTML version is to fallback to letting the browser insert\n    // the content. Same for getting image data. *Sigh*.\n    //\n    // Firefox is even worse: it doesn't even let you know that there might be\n    // an RTF version on the clipboard, but it will also convert to HTML if you\n    // let the browser insert the content. I've filed\n    // https://bugzilla.mozilla.org/show_bug.cgi?id=1254028\n    const types = clipboardData?.types;\n    if (\n        !isLegacyEdge &&\n        types &&\n        (indexOf.call(types, 'text/html') > -1 ||\n            (!isGecko &&\n                indexOf.call(types, 'text/plain') > -1 &&\n                indexOf.call(types, 'text/rtf') < 0))\n    ) {\n        event.preventDefault();\n        // Abiword on Linux copies a plain text and html version, but the HTML\n        // version is the empty string! So always try to get HTML, but if none,\n        // insert plain text instead. On iOS, Facebook (and possibly other\n        // apps?) copy links as type text/uri-list, but also insert a **blank**\n        // text/plain item onto the clipboard. Why? Who knows.\n        let data;\n        if (!choosePlain && (data = clipboardData.getData('text/html'))) {\n            this.insertHTML(data, true);\n        } else if (\n            (data = clipboardData.getData('text/plain')) ||\n            (data = clipboardData.getData('text/uri-list'))\n        ) {\n            this.insertPlainText(data, true);\n        }\n        return;\n    }\n\n    // No interface. Includes all versions of IE :(\n    // --------------------------------------------\n\n    const body = document.body;\n    const range = this.getSelection();\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    const endContainer = range.endContainer;\n    const endOffset = range.endOffset;\n\n    // We need to position the pasteArea in the visible portion of the screen\n    // to stop the browser auto-scrolling.\n    let pasteArea: Element = createElement('DIV', {\n        contenteditable: 'true',\n        style: 'position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;',\n    });\n    body.appendChild(pasteArea);\n    range.selectNodeContents(pasteArea);\n    this.setSelection(range);\n\n    // A setTimeout of 0 means this is added to the back of the\n    // single javascript thread, so it will be executed after the\n    // paste event.\n    setTimeout(() => {\n        try {\n            // Get the pasted content and clean\n            let html = '';\n            let next: Element = pasteArea;\n            let first: Node | null;\n\n            // #88: Chrome can apparently split the paste area if certain\n            // content is inserted; gather them all up.\n            while ((pasteArea = next)) {\n                next = pasteArea.nextSibling as Element;\n                detach(pasteArea);\n                // Safari and IE like putting extra divs around things.\n                first = pasteArea.firstChild;\n                if (\n                    first &&\n                    first === pasteArea.lastChild &&\n                    first instanceof HTMLDivElement\n                ) {\n                    pasteArea = first;\n                }\n                html += pasteArea.innerHTML;\n            }\n\n            this.setSelection(\n                createRange(\n                    startContainer,\n                    startOffset,\n                    endContainer,\n                    endOffset,\n                ),\n            );\n\n            if (html) {\n                this.insertHTML(html, true);\n            }\n        } catch (error) {\n            this._config.didError(error);\n        }\n    }, 0);\n};\n\n// On Windows you can drag an drop text. We can't handle this ourselves, because\n// as far as I can see, there's no way to get the drop insertion point. So just\n// save an undo state and hope for the best.\nconst _onDrop = function (this: Squire, event: DragEvent): void {\n    // it's possible for dataTransfer to be null, let's avoid it.\n    if (!event.dataTransfer) {\n        return;\n    }\n    const types = event.dataTransfer.types;\n    let l = types.length;\n    let hasPlain = false;\n    let hasHTML = false;\n    while (l--) {\n        switch (types[l]) {\n            case 'text/plain':\n                hasPlain = true;\n                break;\n            case 'text/html':\n                hasHTML = true;\n                break;\n            default:\n                return;\n        }\n    }\n    if (hasHTML || (hasPlain && this.saveUndoState)) {\n        this.saveUndoState();\n    }\n};\n\n// ---\n\nexport {\n    extractRangeToClipboard,\n    _onCut,\n    _onCopy,\n    _monitorShiftKey,\n    _onPaste,\n    _onDrop,\n};\n", "import type { Squire } from '../Editor';\n\n// ---\n\nconst Enter = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    event.preventDefault();\n    self.splitBlock(event.shiftKey, range);\n};\n\n// ---\n\nexport { Enter };\n", "import { ZWS } from '../Constants';\nimport { getPreviousBlock } from '../node/Block';\nimport { isInline, isBlock } from '../node/Category';\nimport { fixCursor } from '../node/MergeSplit';\nimport { createElement, detach, getNearest } from '../node/Node';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\n// If you delete the content inside a span with a font styling, Webkit will\n// replace it with a <font> tag (!). If you delete all the text inside a\n// link in Opera, it won't delete the link. Let's make things consistent. If\n// you delete all text inside an inline tag, remove the inline tag.\nconst afterDelete = (self: Squire, range?: Range): void => {\n    try {\n        if (!range) {\n            range = self.getSelection();\n        }\n        let node = range!.startContainer;\n        // Climb the tree from the focus point while we are inside an empty\n        // inline element\n        if (node instanceof Text) {\n            node = node.parentNode!;\n        }\n        let parent = node;\n        while (\n            isInline(parent) &&\n            (!parent.textContent || parent.textContent === ZWS)\n        ) {\n            node = parent;\n            parent = node.parentNode!;\n        }\n        // If focused in empty inline element\n        if (node !== parent) {\n            // Move focus to just before empty inline(s)\n            range!.setStart(\n                parent,\n                Array.from(parent.childNodes as NodeListOf<Node>).indexOf(node),\n            );\n            range!.collapse(true);\n            // Remove empty inline(s)\n            parent.removeChild(node);\n            // Fix cursor in block\n            if (!isBlock(parent)) {\n                parent = getPreviousBlock(parent, self._root) || self._root;\n            }\n            fixCursor(parent);\n            // Move cursor into text node\n            moveRangeBoundariesDownTree(range!);\n        }\n        // If you delete the last character in the sole <div> in Chrome,\n        // it removes the div and replaces it with just a <br> inside the\n        // root. Detach the <br>; the _ensureBottomLine call will insert a new\n        // block.\n        if (\n            node === self._root &&\n            (node = node.firstChild!) &&\n            node.nodeName === 'BR'\n        ) {\n            detach(node);\n        }\n        self._ensureBottomLine();\n        self.setSelection(range);\n        self._updatePath(range, true);\n    } catch (error) {\n        self._config.didError(error);\n    }\n};\n\nconst detachUneditableNode = (node: Node, root: Element): void => {\n    let parent: Node | null;\n    while ((parent = node.parentNode)) {\n        if (parent === root || (parent as HTMLElement).isContentEditable) {\n            break;\n        }\n        node = parent;\n    }\n    detach(node);\n};\n\n// ---\n\nconst linkifyText = (self: Squire, textNode: Text, offset: number): void => {\n    if (getNearest(textNode, self._root, 'A')) {\n        return;\n    }\n    const data = textNode.data || '';\n    const searchFrom =\n        Math.max(\n            data.lastIndexOf(' ', offset - 1),\n            data.lastIndexOf('\u00A0', offset - 1),\n        ) + 1;\n    const searchText = data.slice(searchFrom, offset);\n    const match = self.linkRegExp.exec(searchText);\n    if (match) {\n        // Record an undo point\n        const selection = self.getSelection();\n        self._docWasChanged();\n        self._recordUndoState(selection);\n        self._getRangeAndRemoveBookmark(selection);\n\n        const index = searchFrom + match.index;\n        const endIndex = index + match[0].length;\n        const needsSelectionUpdate = selection.startContainer === textNode;\n        const newSelectionOffset = selection.startOffset - endIndex;\n        if (index) {\n            textNode = textNode.splitText(index);\n        }\n\n        const defaultAttributes = self._config.tagAttributes.a;\n        const link = createElement(\n            'A',\n            Object.assign(\n                {\n                    href: match[1]\n                        ? /^(?:ht|f)tps?:/i.test(match[1])\n                            ? match[1]\n                            : 'http://' + match[1]\n                        : 'mailto:' + match[0],\n                },\n                defaultAttributes,\n            ),\n        );\n        link.textContent = data.slice(index, endIndex);\n        textNode.parentNode!.insertBefore(link, textNode);\n        textNode.data = data.slice(endIndex);\n\n        if (needsSelectionUpdate) {\n            selection.setStart(textNode, newSelectionOffset);\n            selection.setEnd(textNode, newSelectionOffset);\n        }\n        self.setSelection(selection);\n    }\n};\n\n// ---\n\nexport { afterDelete, detachUneditableNode, linkifyText };\n", "import type { Squire } from '../Editor';\nimport { getPreviousBlock } from '../node/Block';\nimport {\n    fixContainer,\n    mergeContainers,\n    mergeWithBlock,\n} from '../node/MergeSplit';\nimport { getNearest } from '../node/Node';\nimport {\n    getStartBlockOfRange,\n    rangeDoesStartAtBlockBoundary,\n} from '../range/Block';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport { afterDelete, detachUneditableNode } from './KeyHelpers';\n\n// ---\n\nconst Backspace = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root: Element = self._root;\n    self._removeZWS();\n    // Record undo checkpoint.\n    self.saveUndoState(range);\n    if (!range.collapsed) {\n        // If not collapsed, delete contents\n        event.preventDefault();\n        deleteContentsOfRange(range, root);\n        afterDelete(self, range);\n    } else if (rangeDoesStartAtBlockBoundary(range, root)) {\n        // If at beginning of block, merge with previous\n        event.preventDefault();\n        const startBlock = getStartBlockOfRange(range, root);\n        if (!startBlock) {\n            return;\n        }\n        let current = startBlock;\n        // In case inline data has somehow got between blocks.\n        fixContainer(current.parentNode!, root);\n        // Now get previous block\n        const previous = getPreviousBlock(current, root);\n        // Must not be at the very beginning of the text area.\n        if (previous) {\n            // If not editable, just delete whole block.\n            if (!(previous as HTMLElement).isContentEditable) {\n                detachUneditableNode(previous, root);\n                return;\n            }\n            // Otherwise merge.\n            mergeWithBlock(previous, current, range, root);\n            // If deleted line between containers, merge newly adjacent\n            // containers.\n            current = previous.parentNode as HTMLElement;\n            while (current !== root && !current.nextSibling) {\n                current = current.parentNode as HTMLElement;\n            }\n            if (\n                current !== root &&\n                (current = current.nextSibling as HTMLElement)\n            ) {\n                mergeContainers(current, root);\n            }\n            self.setSelection(range);\n            // If at very beginning of text area, allow backspace\n            // to break lists/blockquote.\n        } else if (current) {\n            if (\n                getNearest(current, root, 'UL') ||\n                getNearest(current, root, 'OL')\n            ) {\n                // Break list\n                self.decreaseListLevel(range);\n                return;\n            } else if (getNearest(current, root, 'BLOCKQUOTE')) {\n                // Break blockquote\n                self.removeQuote(range);\n                return;\n            }\n            self.setSelection(range);\n            self._updatePath(range, true);\n        }\n    } else {\n        // If deleting text inside a link that looks like a URL, delink.\n        // This is to allow you to easily correct auto-linked text.\n        moveRangeBoundariesDownTree(range);\n        const text = range.startContainer;\n        const offset = range.startOffset;\n        const a = text.parentNode;\n        if (\n            text instanceof Text &&\n            a instanceof HTMLAnchorElement &&\n            offset &&\n            a.href.includes(text.data)\n        ) {\n            text.deleteData(offset - 1, 1);\n            self.setSelection(range);\n            self.removeLink();\n            event.preventDefault();\n        } else {\n            // Otherwise, leave to browser but check afterwards whether it has\n            // left behind an empty inline tag.\n            self.setSelection(range);\n            setTimeout(() => {\n                afterDelete(self);\n            }, 0);\n        }\n    }\n};\n\n// ---\n\nexport { Backspace };\n", "import { getNextBlock } from '../node/Block';\nimport {\n    fixContainer,\n    mergeWithBlock,\n    mergeContainers,\n} from '../node/MergeSplit';\nimport { detach } from '../node/Node';\nimport {\n    rangeDoesEndAtBlockBoundary,\n    getStartBlockOfRange,\n} from '../range/Block';\nimport {\n    moveRangeBoundariesUpTree,\n    moveRangeBoundariesDownTree,\n} from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport { afterDelete, detachUneditableNode } from './KeyHelpers';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\nconst Delete = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    let current: Node | null;\n    let next: Node | null;\n    let originalRange: Range;\n    let cursorContainer: Node;\n    let cursorOffset: number;\n    let nodeAfterCursor: Node;\n    self._removeZWS();\n    // Record undo checkpoint.\n    self.saveUndoState(range);\n    // If not collapsed, delete contents\n    if (!range.collapsed) {\n        event.preventDefault();\n        deleteContentsOfRange(range, root);\n        afterDelete(self, range);\n        // If at end of block, merge next into this block\n    } else if (rangeDoesEndAtBlockBoundary(range, root)) {\n        event.preventDefault();\n        current = getStartBlockOfRange(range, root);\n        if (!current) {\n            return;\n        }\n        // In case inline data has somehow got between blocks.\n        fixContainer(current.parentNode!, root);\n        // Now get next block\n        next = getNextBlock(current, root);\n        // Must not be at the very end of the text area.\n        if (next) {\n            // If not editable, just delete whole block.\n            if (!(next as HTMLElement).isContentEditable) {\n                detachUneditableNode(next, root);\n                return;\n            }\n            // Otherwise merge.\n            mergeWithBlock(current, next, range, root);\n            // If deleted line between containers, merge newly adjacent\n            // containers.\n            next = current.parentNode!;\n            while (next !== root && !next.nextSibling) {\n                next = next.parentNode!;\n            }\n            if (next !== root && (next = next.nextSibling)) {\n                mergeContainers(next, root);\n            }\n            self.setSelection(range);\n            self._updatePath(range, true);\n        }\n        // Otherwise, leave to browser but check afterwards whether it has\n        // left behind an empty inline tag.\n    } else {\n        // But first check if the cursor is just before an IMG tag. If so,\n        // delete it ourselves, because the browser won't if it is not\n        // inline.\n        originalRange = range.cloneRange();\n        moveRangeBoundariesUpTree(range, root, root, root);\n        cursorContainer = range.endContainer;\n        cursorOffset = range.endOffset;\n        if (cursorContainer instanceof Element) {\n            nodeAfterCursor = cursorContainer.childNodes[cursorOffset];\n            if (nodeAfterCursor && nodeAfterCursor.nodeName === 'IMG') {\n                event.preventDefault();\n                detach(nodeAfterCursor);\n                moveRangeBoundariesDownTree(range);\n                afterDelete(self, range);\n                return;\n            }\n        }\n        self.setSelection(originalRange);\n        setTimeout(() => {\n            afterDelete(self);\n        }, 0);\n    }\n};\n\n// ---\n\nexport { Delete };\n", "import {\n    rangeDoesStartAtBlockBoundary,\n    getStartBlockOfRange,\n} from '../range/Block';\nimport { getNearest } from '../node/Node';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\nconst Tab = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    self._removeZWS();\n    // If no selection and at start of block\n    if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) {\n        let node: Node = getStartBlockOfRange(range, root)!;\n        // Iterate through the block's parents\n        let parent: Node | null;\n        while ((parent = node.parentNode)) {\n            // If we find a UL or OL (so are in a list, node must be an LI)\n            if (parent.nodeName === 'UL' || parent.nodeName === 'OL') {\n                // Then increase the list level\n                event.preventDefault();\n                self.increaseListLevel(range);\n                break;\n            }\n            node = parent;\n        }\n    }\n};\n\nconst ShiftTab = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    self._removeZWS();\n    // If no selection and at start of block\n    if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) {\n        // Break list\n        const node = range.startContainer;\n        if (getNearest(node, root, 'UL') || getNearest(node, root, 'OL')) {\n            event.preventDefault();\n            self.decreaseListLevel(range);\n        }\n    }\n};\n\n// ---\n\nexport { Tab, ShiftTab };\n", "import { detach, getLength } from '../node/Node';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\n\nimport type { Squire } from '../Editor';\nimport { linkifyText } from './KeyHelpers';\nimport {\n    getStartBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n} from '../range/Block';\nimport { SHOW_TEXT, TreeIterator } from '../node/TreeIterator';\nimport { ZWS } from '../Constants';\n\n// ---\n\nconst Space = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    let node: Node | null;\n    const root = self._root;\n    self._recordUndoState(range);\n    self._getRangeAndRemoveBookmark(range);\n\n    // Delete the selection if not collapsed\n    if (!range.collapsed) {\n        deleteContentsOfRange(range, root);\n        self._ensureBottomLine();\n        self.setSelection(range);\n        self._updatePath(range, true);\n    } else if (rangeDoesEndAtBlockBoundary(range, root)) {\n        const block = getStartBlockOfRange(range, root);\n        if (block && block.nodeName !== 'PRE') {\n            const text = block.textContent?.trimEnd().replace(ZWS, '');\n            if (text === '*' || text === '1.') {\n                event.preventDefault();\n                self.insertPlainText(' ', false);\n                self._docWasChanged();\n                self.saveUndoState(range);\n                const walker = new TreeIterator<Text>(block, SHOW_TEXT);\n                let textNode: Text | null;\n                while ((textNode = walker.nextNode())) {\n                    detach(textNode);\n                }\n                if (text === '*') {\n                    self.makeUnorderedList();\n                } else {\n                    self.makeOrderedList();\n                }\n                return;\n            }\n        }\n    }\n\n    // If the cursor is at the end of a link (<a>foo|</a>) then move it\n    // outside of the link (<a>foo</a>|) so that the space is not part of\n    // the link text.\n    node = range.endContainer;\n    if (range.endOffset === getLength(node)) {\n        do {\n            if (node.nodeName === 'A') {\n                range.setStartAfter(node);\n                break;\n            }\n        } while (\n            !node.nextSibling &&\n            (node = node.parentNode) &&\n            node !== root\n        );\n    }\n\n    // Linkify text\n    if (self._config.addLinks) {\n        const linkRange = range.cloneRange();\n        moveRangeBoundariesDownTree(linkRange);\n        const textNode = linkRange.startContainer as Text;\n        const offset = linkRange.startOffset;\n        setTimeout(() => {\n            linkifyText(self, textNode, offset);\n        }, 0);\n    }\n\n    self.setSelection(range);\n};\n\n// ---\n\nexport { Space };\n", "import {\n    isMac,\n    isWin,\n    isIOS,\n    ctrlKey,\n    supportsInputEvents,\n} from '../Constants';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport type { Squire } from '../Editor';\nimport { Enter } from './Enter';\nimport { Backspace } from './Backspace';\nimport { Delete } from './Delete';\nimport { ShiftTab, Tab } from './Tab';\nimport { Space } from './Space';\nimport { rangeDoesEndAtBlockBoundary } from '../range/Block';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\n\n// ---\n\nconst _onKey = function (this: Squire, event: KeyboardEvent): void {\n    // Ignore key events where event.isComposing, to stop us from blatting\n    // Kana-Kanji conversion\n    if (event.defaultPrevented || event.isComposing) {\n        return;\n    }\n\n    // We need to apply the Backspace/delete handlers regardless of\n    // control key modifiers.\n    let key = event.key;\n    let modifiers = '';\n    const code = event.code;\n    // If pressing a number key + Shift, make sure we handle it as the number\n    // key and not whatever different character the shift might turn it into.\n    if (/^Digit\\d$/.test(code)) {\n        key = code.slice(-1);\n    }\n    if (key !== 'Backspace' && key !== 'Delete') {\n        if (event.altKey) {\n            modifiers += 'Alt-';\n        }\n        if (event.ctrlKey) {\n            modifiers += 'Ctrl-';\n        }\n        if (event.metaKey) {\n            modifiers += 'Meta-';\n        }\n        if (event.shiftKey) {\n            modifiers += 'Shift-';\n        }\n    }\n    // However, on Windows, Shift-Delete is apparently \"cut\" (WTF right?), so\n    // we want to let the browser handle Shift-Delete in this situation.\n    if (isWin && event.shiftKey && key === 'Delete') {\n        modifiers += 'Shift-';\n    }\n    key = modifiers + key;\n\n    const range: Range = this.getSelection();\n    if (this._keyHandlers[key]) {\n        this._keyHandlers[key](this, event, range);\n    } else if (\n        !range.collapsed &&\n        !event.ctrlKey &&\n        !event.metaKey &&\n        key.length === 1\n    ) {\n        // Record undo checkpoint.\n        this.saveUndoState(range);\n        // Delete the selection\n        deleteContentsOfRange(range, this._root);\n        this._ensureBottomLine();\n        this.setSelection(range);\n        this._updatePath(range, true);\n    }\n};\n\n// ---\n\ntype KeyHandler = (self: Squire, event: KeyboardEvent, range: Range) => void;\n\nconst keyHandlers: Record<string, KeyHandler> = {\n    'Backspace': Backspace,\n    'Delete': Delete,\n    'Tab': Tab,\n    'Shift-Tab': ShiftTab,\n    ' ': Space,\n    'ArrowLeft'(self: Squire): void {\n        self._removeZWS();\n    },\n    'ArrowRight'(self: Squire, event: KeyboardEvent, range: Range): void {\n        self._removeZWS();\n        // Allow right arrow to always break out of <code> block.\n        const root = self.getRoot();\n        if (rangeDoesEndAtBlockBoundary(range, root)) {\n            moveRangeBoundariesDownTree(range);\n            let node: Node | null = range.endContainer;\n            do {\n                if (node.nodeName === 'CODE') {\n                    let next = node.nextSibling;\n                    if (!(next instanceof Text)) {\n                        const textNode = document.createTextNode('\u00A0'); // nbsp\n                        node.parentNode!.insertBefore(textNode, next);\n                        next = textNode;\n                    }\n                    range.setStart(next, 1);\n                    self.setSelection(range);\n                    event.preventDefault();\n                    break;\n                }\n            } while (\n                !node.nextSibling &&\n                (node = node.parentNode) &&\n                node !== root\n            );\n        }\n    },\n};\n\nif (!supportsInputEvents) {\n    keyHandlers.Enter = Enter;\n    keyHandlers['Shift-Enter'] = Enter;\n}\n\n// System standard for page up/down on Mac/iOS is to just scroll, not move the\n// cursor. On Linux/Windows, it should move the cursor, but some browsers don't\n// implement this natively. Override to support it.\nif (!isMac && !isIOS) {\n    keyHandlers.PageUp = (self: Squire) => {\n        self.moveCursorToStart();\n    };\n    keyHandlers.PageDown = (self: Squire) => {\n        self.moveCursorToEnd();\n    };\n}\n\n// ---\n\nconst mapKeyToFormat = (\n    tag: string,\n    remove?: { tag: string } | null,\n): KeyHandler => {\n    remove = remove || null;\n    return (self: Squire, event: Event) => {\n        event.preventDefault();\n        const range = self.getSelection();\n        if (self.hasFormat(tag, null, range)) {\n            self.changeFormat(null, { tag }, range);\n        } else {\n            self.changeFormat({ tag }, remove, range);\n        }\n    };\n};\n\nkeyHandlers[ctrlKey + 'b'] = mapKeyToFormat('B');\nkeyHandlers[ctrlKey + 'i'] = mapKeyToFormat('I');\nkeyHandlers[ctrlKey + 'u'] = mapKeyToFormat('U');\nkeyHandlers[ctrlKey + 'Shift-7'] = mapKeyToFormat('S');\nkeyHandlers[ctrlKey + 'Shift-5'] = mapKeyToFormat('SUB', { tag: 'SUP' });\nkeyHandlers[ctrlKey + 'Shift-6'] = mapKeyToFormat('SUP', { tag: 'SUB' });\n\nkeyHandlers[ctrlKey + 'Shift-8'] = (\n    self: Squire,\n    event: KeyboardEvent,\n): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (!/(?:^|>)UL/.test(path)) {\n        self.makeUnorderedList();\n    } else {\n        self.removeList();\n    }\n};\nkeyHandlers[ctrlKey + 'Shift-9'] = (\n    self: Squire,\n    event: KeyboardEvent,\n): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (!/(?:^|>)OL/.test(path)) {\n        self.makeOrderedList();\n    } else {\n        self.removeList();\n    }\n};\n\nkeyHandlers[ctrlKey + '['] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) {\n        self.decreaseQuoteLevel();\n    } else {\n        self.decreaseListLevel();\n    }\n};\nkeyHandlers[ctrlKey + ']'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) {\n        self.increaseQuoteLevel();\n    } else {\n        self.increaseListLevel();\n    }\n};\n\nkeyHandlers[ctrlKey + 'd'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    self.toggleCode();\n};\n\nkeyHandlers[ctrlKey + 'z'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    self.undo();\n};\nkeyHandlers[ctrlKey + 'y'] =\n    // Depending on platform, the Shift may cause the key to come through as\n    // upper case, but sometimes not. Just add both as shortcuts \u2014 the browser\n    // will only ever fire one or the other.\n    keyHandlers[ctrlKey + 'Shift-z'] =\n    keyHandlers[ctrlKey + 'Shift-Z'] =\n        (self: Squire, event: KeyboardEvent): void => {\n            event.preventDefault();\n            self.redo();\n        };\n\nexport { _onKey, keyHandlers };\n", "import {\n    TreeIterator,\n    SHOW_TEXT,\n    SHOW_ELEMENT_OR_TEXT,\n} from './node/TreeIterator';\nimport {\n    createElement,\n    detach,\n    empty,\n    getNearest,\n    hasTagAttributes,\n    replaceWith,\n} from './node/Node';\nimport {\n    isLeaf,\n    isInline,\n    resetNodeCategoryCache,\n    isContainer,\n    isBlock,\n} from './node/Category';\nimport { isLineBreak, removeZWS } from './node/Whitespace';\nimport {\n    moveRangeBoundariesDownTree,\n    isNodeContainedInRange,\n    moveRangeBoundaryOutOf,\n    moveRangeBoundariesUpTree,\n} from './range/Boundaries';\nimport {\n    createRange,\n    deleteContentsOfRange,\n    extractContentsOfRange,\n    insertNodeInRange,\n    insertTreeFragmentIntoRange,\n} from './range/InsertDelete';\nimport {\n    fixContainer,\n    fixCursor,\n    mergeContainers,\n    mergeInlines,\n    split,\n} from './node/MergeSplit';\nimport { getBlockWalker, getNextBlock, isEmptyBlock } from './node/Block';\nimport { cleanTree, cleanupBRs, escapeHTML, removeEmptyInlines } from './Clean';\nimport { cantFocusEmptyTextNodes, ZWS } from './Constants';\nimport {\n    expandRangeToBlockBoundaries,\n    getEndBlockOfRange,\n    getStartBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n    rangeDoesStartAtBlockBoundary,\n} from './range/Block';\nimport {\n    _monitorShiftKey,\n    _onCopy,\n    _onCut,\n    _onDrop,\n    _onPaste,\n} from './Clipboard';\nimport { keyHandlers, _onKey } from './keyboard/KeyHandlers';\nimport { linkifyText } from './keyboard/KeyHelpers';\nimport { getTextContentsOfRange } from './range/Contents';\n\ndeclare const DOMPurify: any;\n\n// ---\n\ntype EventHandler = { handleEvent: (e: Event) => void } | ((e: Event) => void);\n\ntype KeyHandlerFunction = (x: Squire, y: KeyboardEvent, z: Range) => void;\n\ntype TagAttributes = {\n    [key: string]: { [key: string]: string };\n};\n\ninterface SquireConfig {\n    blockTag: string;\n    blockAttributes: null | Record<string, string>;\n    tagAttributes: TagAttributes;\n    classNames: {\n        color: string;\n        fontFamily: string;\n        fontSize: string;\n        highlight: string;\n    };\n    undo: {\n        documentSizeThreshold: number;\n        undoLimit: number;\n    };\n    addLinks: boolean;\n    willCutCopy: null | ((html: string) => string);\n    toPlainText: null | ((html: string) => string);\n    sanitizeToDOMFragment: (html: string, editor: Squire) => DocumentFragment;\n    didError: (x: any) => void;\n}\n\n// ---\n\nclass Squire {\n    _root: HTMLElement;\n    _config: SquireConfig;\n\n    _isFocused: boolean;\n    _lastSelection: Range;\n    _willRestoreSelection: boolean;\n    _mayHaveZWS: boolean;\n\n    _lastAnchorNode: Node | null;\n    _lastFocusNode: Node | null;\n    _path: string;\n\n    _events: Map<string, Array<EventHandler>>;\n\n    _undoIndex: number;\n    _undoStack: Array<string>;\n    _undoStackLength: number;\n    _isInUndoState: boolean;\n    _ignoreChange: boolean;\n    _ignoreAllChanges: boolean;\n\n    _isShiftDown: boolean;\n    _keyHandlers: Record<string, KeyHandlerFunction>;\n\n    _mutation: MutationObserver;\n\n    constructor(root: HTMLElement, config?: Partial<SquireConfig>) {\n        this._root = root;\n\n        this._config = this._makeConfig(config);\n\n        this._isFocused = false;\n        this._lastSelection = createRange(root, 0);\n        this._willRestoreSelection = false;\n        this._mayHaveZWS = false;\n\n        this._lastAnchorNode = null;\n        this._lastFocusNode = null;\n        this._path = '';\n\n        this._events = new Map();\n\n        this._undoIndex = -1;\n        this._undoStack = [];\n        this._undoStackLength = 0;\n        this._isInUndoState = false;\n        this._ignoreChange = false;\n        this._ignoreAllChanges = false;\n\n        // Add event listeners\n        this.addEventListener('selectionchange', this._updatePathOnEvent);\n\n        // On blur, restore focus except if the user taps or clicks to focus a\n        // specific point. Can't actually use click event because focus happens\n        // before click, so use mousedown/touchstart\n        this.addEventListener('blur', this._enableRestoreSelection);\n        this.addEventListener('mousedown', this._disableRestoreSelection);\n        this.addEventListener('touchstart', this._disableRestoreSelection);\n        this.addEventListener('focus', this._restoreSelection);\n\n        // On blur, cleanup any ZWS/empty inlines\n        this.addEventListener('blur', this._removeZWS);\n\n        // Clipboard support\n        this._isShiftDown = false;\n        this.addEventListener('cut', _onCut as (e: Event) => void);\n        this.addEventListener('copy', _onCopy as (e: Event) => void);\n        this.addEventListener('paste', _onPaste as (e: Event) => void);\n        this.addEventListener('drop', _onDrop as (e: Event) => void);\n        this.addEventListener(\n            'keydown',\n            _monitorShiftKey as (e: Event) => void,\n        );\n        this.addEventListener('keyup', _monitorShiftKey as (e: Event) => void);\n\n        // Keyboard support\n        this.addEventListener('keydown', _onKey as (e: Event) => void);\n        this._keyHandlers = Object.create(keyHandlers);\n\n        const mutation = new MutationObserver(() => this._docWasChanged());\n        mutation.observe(root, {\n            childList: true,\n            attributes: true,\n            characterData: true,\n            subtree: true,\n        });\n        this._mutation = mutation;\n\n        // Make it editable\n        root.setAttribute('contenteditable', 'true');\n\n        // Modern browsers let you override their default content editable\n        // handling!\n        this.addEventListener(\n            'beforeinput',\n            this._beforeInput as (e: Event) => void,\n        );\n\n        this.setHTML('');\n    }\n\n    destroy(): void {\n        this._events.forEach((_, type) => {\n            this.removeEventListener(type);\n        });\n\n        this._mutation.disconnect();\n\n        this._undoIndex = -1;\n        this._undoStack = [];\n        this._undoStackLength = 0;\n    }\n\n    _makeConfig(userConfig?: object): SquireConfig {\n        const config = {\n            blockTag: 'DIV',\n            blockAttributes: null,\n            tagAttributes: {},\n            classNames: {\n                color: 'color',\n                fontFamily: 'font',\n                fontSize: 'size',\n                highlight: 'highlight',\n            },\n            undo: {\n                documentSizeThreshold: -1, // -1 means no threshold\n                undoLimit: -1, // -1 means no limit\n            },\n            addLinks: true,\n            willCutCopy: null,\n            toPlainText: null,\n            sanitizeToDOMFragment: (\n                html: string,\n                /* editor: Squire, */\n            ): DocumentFragment => {\n                const frag = DOMPurify.sanitize(html, {\n                    ALLOW_UNKNOWN_PROTOCOLS: true,\n                    WHOLE_DOCUMENT: false,\n                    RETURN_DOM: true,\n                    RETURN_DOM_FRAGMENT: true,\n                    FORCE_BODY: false,\n                });\n                return frag\n                    ? document.importNode(frag, true)\n                    : document.createDocumentFragment();\n            },\n            didError: (error: any): void => console.log(error),\n        };\n        if (userConfig) {\n            Object.assign(config, userConfig);\n            config.blockTag = config.blockTag.toUpperCase();\n        }\n\n        return config;\n    }\n\n    setKeyHandler(key: string, fn: KeyHandlerFunction) {\n        this._keyHandlers[key] = fn;\n        return this;\n    }\n\n    _beforeInput(event: InputEvent): void {\n        switch (event.inputType) {\n            case 'insertLineBreak':\n                event.preventDefault();\n                this.splitBlock(true);\n                break;\n            case 'insertParagraph':\n                event.preventDefault();\n                this.splitBlock(false);\n                break;\n            case 'insertOrderedList':\n                event.preventDefault();\n                this.makeOrderedList();\n                break;\n            case 'insertUnoderedList':\n                event.preventDefault();\n                this.makeUnorderedList();\n                break;\n            case 'historyUndo':\n                event.preventDefault();\n                this.undo();\n                break;\n            case 'historyRedo':\n                event.preventDefault();\n                this.redo();\n                break;\n            case 'formatBold':\n                event.preventDefault();\n                this.bold();\n                break;\n            case 'formaItalic':\n                event.preventDefault();\n                this.italic();\n                break;\n            case 'formatUnderline':\n                event.preventDefault();\n                this.underline();\n                break;\n            case 'formatStrikeThrough':\n                event.preventDefault();\n                this.strikethrough();\n                break;\n            case 'formatSuperscript':\n                event.preventDefault();\n                this.superscript();\n                break;\n            case 'formatSubscript':\n                event.preventDefault();\n                this.subscript();\n                break;\n            case 'formatJustifyFull':\n            case 'formatJustifyCenter':\n            case 'formatJustifyRight':\n            case 'formatJustifyLeft': {\n                event.preventDefault();\n                let alignment = event.inputType.slice(13).toLowerCase();\n                if (alignment === 'full') {\n                    alignment = 'justify';\n                }\n                this.setTextAlignment(alignment);\n                break;\n            }\n            case 'formatRemove':\n                event.preventDefault();\n                this.removeAllFormatting();\n                break;\n            case 'formatSetBlockTextDirection': {\n                event.preventDefault();\n                let dir = event.data;\n                if (dir === 'null') {\n                    dir = null;\n                }\n                this.setTextDirection(dir);\n                break;\n            }\n            case 'formatBackColor':\n                event.preventDefault();\n                this.setHighlightColor(event.data);\n                break;\n            case 'formatFontColor':\n                event.preventDefault();\n                this.setTextColor(event.data);\n                break;\n            case 'formatFontName':\n                event.preventDefault();\n                this.setFontFace(event.data);\n                break;\n        }\n    }\n\n    // --- Events\n\n    handleEvent(event: Event): void {\n        this.fireEvent(event.type, event);\n    }\n\n    fireEvent(type: string, detail?: Event | object): Squire {\n        let handlers = this._events.get(type);\n        // UI code, especially modal views, may be monitoring for focus events\n        // and immediately removing focus. In certain conditions, this can\n        // cause the focus event to fire after the blur event, which can cause\n        // an infinite loop. So we detect whether we're actually\n        // focused/blurred before firing.\n        if (/^(?:focus|blur)/.test(type)) {\n            const isFocused = this._root === document.activeElement;\n            if (type === 'focus') {\n                if (!isFocused || this._isFocused) {\n                    return this;\n                }\n                this._isFocused = true;\n            } else {\n                if (isFocused || !this._isFocused) {\n                    return this;\n                }\n                this._isFocused = false;\n            }\n        }\n        if (handlers) {\n            const event: Event =\n                detail instanceof Event\n                    ? detail\n                    : new CustomEvent(type, {\n                          detail,\n                      });\n            // Clone handlers array, so any handlers added/removed do not\n            // affect it.\n            handlers = handlers.slice();\n            for (const handler of handlers) {\n                try {\n                    if ('handleEvent' in handler) {\n                        handler.handleEvent(event);\n                    } else {\n                        handler.call(this, event);\n                    }\n                } catch (error) {\n                    this._config.didError(error);\n                }\n            }\n        }\n        return this;\n    }\n\n    /**\n     * Subscribing to these events won't automatically add a listener to the\n     * document node, since these events are fired in a custom manner by the\n     * editor code.\n     */\n    customEvents = new Set([\n        'pathChange',\n        'select',\n        'input',\n        'pasteImage',\n        'undoStateChange',\n    ]);\n\n    addEventListener(type: string, fn: EventHandler): Squire {\n        let handlers = this._events.get(type);\n        let target: Document | HTMLElement = this._root;\n        if (!handlers) {\n            handlers = [];\n            this._events.set(type, handlers);\n            if (!this.customEvents.has(type)) {\n                if (type === 'selectionchange') {\n                    target = document;\n                }\n                target.addEventListener(type, this, true);\n            }\n        }\n        handlers.push(fn);\n        return this;\n    }\n\n    removeEventListener(type: string, fn?: EventHandler): Squire {\n        const handlers = this._events.get(type);\n        let target: Document | HTMLElement = this._root;\n        if (handlers) {\n            if (fn) {\n                let l = handlers.length;\n                while (l--) {\n                    if (handlers[l] === fn) {\n                        handlers.splice(l, 1);\n                    }\n                }\n            } else {\n                handlers.length = 0;\n            }\n            if (!handlers.length) {\n                this._events.delete(type);\n                if (!this.customEvents.has(type)) {\n                    if (type === 'selectionchange') {\n                        target = document;\n                    }\n                    target.removeEventListener(type, this, true);\n                }\n            }\n        }\n        return this;\n    }\n\n    // --- Focus\n\n    focus(): Squire {\n        this._root.focus({ preventScroll: true });\n        return this;\n    }\n\n    blur(): Squire {\n        this._root.blur();\n        return this;\n    }\n\n    // --- Selection and bookmarking\n\n    _enableRestoreSelection(): void {\n        this._willRestoreSelection = true;\n    }\n\n    _disableRestoreSelection(): void {\n        this._willRestoreSelection = false;\n    }\n\n    _restoreSelection() {\n        if (this._willRestoreSelection) {\n            this.setSelection(this._lastSelection);\n        }\n    }\n\n    // ---\n\n    _removeZWS(): void {\n        if (!this._mayHaveZWS) {\n            return;\n        }\n        removeZWS(this._root);\n        this._mayHaveZWS = false;\n    }\n\n    // ---\n\n    startSelectionId = 'squire-selection-start';\n    endSelectionId = 'squire-selection-end';\n\n    _saveRangeToBookmark(range: Range): void {\n        let startNode = createElement('INPUT', {\n            id: this.startSelectionId,\n            type: 'hidden',\n        });\n        let endNode = createElement('INPUT', {\n            id: this.endSelectionId,\n            type: 'hidden',\n        });\n        let temp: HTMLElement;\n\n        insertNodeInRange(range, startNode);\n        range.collapse(false);\n        insertNodeInRange(range, endNode);\n\n        // In a collapsed range, the start is sometimes inserted after the end!\n        if (\n            startNode.compareDocumentPosition(endNode) &\n            Node.DOCUMENT_POSITION_PRECEDING\n        ) {\n            startNode.id = this.endSelectionId;\n            endNode.id = this.startSelectionId;\n            temp = startNode;\n            startNode = endNode;\n            endNode = temp;\n        }\n\n        range.setStartAfter(startNode);\n        range.setEndBefore(endNode);\n    }\n\n    _getRangeAndRemoveBookmark(range?: Range): Range | null {\n        const root = this._root;\n        const start = root.querySelector('#' + this.startSelectionId);\n        const end = root.querySelector('#' + this.endSelectionId);\n\n        if (start && end) {\n            let startContainer: Node = start.parentNode!;\n            let endContainer: Node = end.parentNode!;\n            const startOffset = Array.from(startContainer.childNodes).indexOf(\n                start,\n            );\n            let endOffset = Array.from(endContainer.childNodes).indexOf(end);\n\n            if (startContainer === endContainer) {\n                endOffset -= 1;\n            }\n\n            start.remove();\n            end.remove();\n\n            if (!range) {\n                range = document.createRange();\n            }\n            range.setStart(startContainer, startOffset);\n            range.setEnd(endContainer, endOffset);\n\n            // Merge any text nodes we split\n            mergeInlines(startContainer, range);\n            if (startContainer !== endContainer) {\n                mergeInlines(endContainer, range);\n            }\n\n            // If we didn't split a text node, we should move into any adjacent\n            // text node to current selection point\n            if (range.collapsed) {\n                startContainer = range.startContainer;\n                if (startContainer instanceof Text) {\n                    endContainer = startContainer.childNodes[range.startOffset];\n                    if (!endContainer || !(endContainer instanceof Text)) {\n                        endContainer =\n                            startContainer.childNodes[range.startOffset - 1];\n                    }\n                    if (endContainer && endContainer instanceof Text) {\n                        range.setStart(endContainer, 0);\n                        range.collapse(true);\n                    }\n                }\n            }\n        }\n        return range || null;\n    }\n\n    getSelection(): Range {\n        const selection = window.getSelection();\n        const root = this._root;\n        let range: Range | null = null;\n        // If not focused, always rely on cached selection; another function may\n        // have set it but the DOM is not modified until focus again\n        if (this._isFocused && selection && selection.rangeCount) {\n            range = selection.getRangeAt(0).cloneRange();\n            const startContainer = range.startContainer;\n            const endContainer = range.endContainer;\n            // FF can return the selection as being inside an <img>. WTF?\n            if (startContainer && isLeaf(startContainer)) {\n                range.setStartBefore(startContainer);\n            }\n            if (endContainer && isLeaf(endContainer)) {\n                range.setEndBefore(endContainer);\n            }\n        }\n        if (range && root.contains(range.commonAncestorContainer)) {\n            this._lastSelection = range;\n        } else {\n            range = this._lastSelection;\n            // Check the editor is in the live document; if not, the range has\n            // probably been rewritten by the browser and is bogus\n            if (!document.contains(range.commonAncestorContainer)) {\n                range = null;\n            }\n        }\n        if (!range) {\n            range = createRange(root.firstElementChild || root, 0);\n        }\n        return range;\n    }\n\n    setSelection(range: Range): Squire {\n        this._lastSelection = range;\n        // If we're setting selection, that automatically, and synchronously,\n        // triggers a focus event. So just store the selection and mark it as\n        // needing restore on focus.\n        if (!this._isFocused) {\n            this._enableRestoreSelection();\n        } else {\n            const selection = window.getSelection();\n            if (selection) {\n                if ('setBaseAndExtent' in Selection.prototype) {\n                    selection.setBaseAndExtent(\n                        range.startContainer,\n                        range.startOffset,\n                        range.endContainer,\n                        range.endOffset,\n                    );\n                } else {\n                    selection.removeAllRanges();\n                    selection.addRange(range);\n                }\n            }\n        }\n        return this;\n    }\n\n    // ---\n\n    _moveCursorTo(toStart: boolean): Squire {\n        const root = this._root;\n        const range = createRange(root, toStart ? 0 : root.childNodes.length);\n        moveRangeBoundariesDownTree(range);\n        this.setSelection(range);\n        return this;\n    }\n\n    moveCursorToStart(): Squire {\n        return this._moveCursorTo(true);\n    }\n\n    moveCursorToEnd(): Squire {\n        return this._moveCursorTo(false);\n    }\n\n    // ---\n\n    getCursorPosition(): DOMRect {\n        const range = this.getSelection();\n        let rect = range.getBoundingClientRect();\n        // If the range is outside of the viewport, some browsers at least\n        // will return 0 for all the values; need to get a DOM node to find\n        // the position instead.\n        if (rect && !rect.top) {\n            this._ignoreChange = true;\n            const node = createElement('SPAN');\n            node.textContent = ZWS;\n            insertNodeInRange(range, node);\n            rect = node.getBoundingClientRect();\n            const parent = node.parentNode!;\n            parent.removeChild(node);\n            mergeInlines(parent, range);\n        }\n        return rect;\n    }\n\n    // --- Path\n\n    getPath(): string {\n        return this._path;\n    }\n\n    _updatePathOnEvent(): void {\n        if (this._isFocused) {\n            this._updatePath(this.getSelection());\n        }\n    }\n\n    _updatePath(range: Range, force?: boolean): void {\n        const anchor = range.startContainer;\n        const focus = range.endContainer;\n        let newPath: string;\n        if (\n            force ||\n            anchor !== this._lastAnchorNode ||\n            focus !== this._lastFocusNode\n        ) {\n            this._lastAnchorNode = anchor;\n            this._lastFocusNode = focus;\n            newPath =\n                anchor && focus\n                    ? anchor === focus\n                        ? this._getPath(focus)\n                        : '(selection)'\n                    : '';\n            if (this._path !== newPath) {\n                this._path = newPath;\n                this.fireEvent('pathChange', {\n                    path: newPath,\n                });\n            }\n        }\n        this.fireEvent(range.collapsed ? 'cursor' : 'select', {\n            range: range,\n        });\n    }\n\n    _getPath(node: Node) {\n        const root = this._root;\n        const config = this._config;\n        let path = '';\n        if (node && node !== root) {\n            const parent = node.parentNode;\n            path = parent ? this._getPath(parent) : '';\n            if (node instanceof HTMLElement) {\n                const id = node.id;\n                const classList = node.classList;\n                const classNames = Array.from(classList).sort();\n                const dir = node.dir;\n                const styleNames = config.classNames;\n                path += (path ? '>' : '') + node.nodeName;\n                if (id) {\n                    path += '#' + id;\n                }\n                if (classNames.length) {\n                    path += '.';\n                    path += classNames.join('.');\n                }\n                if (dir) {\n                    path += '[dir=' + dir + ']';\n                }\n                if (classList.contains(styleNames.highlight)) {\n                    path +=\n                        '[backgroundColor=' +\n                        node.style.backgroundColor.replace(/ /g, '') +\n                        ']';\n                }\n                if (classList.contains(styleNames.color)) {\n                    path +=\n                        '[color=' + node.style.color.replace(/ /g, '') + ']';\n                }\n                if (classList.contains(styleNames.fontFamily)) {\n                    path +=\n                        '[fontFamily=' +\n                        node.style.fontFamily.replace(/ /g, '') +\n                        ']';\n                }\n                if (classList.contains(styleNames.fontSize)) {\n                    path += '[fontSize=' + node.style.fontSize + ']';\n                }\n            }\n        }\n        return path;\n    }\n\n    // --- History\n\n    modifyDocument(modificationFn: () => void): Squire {\n        const mutation = this._mutation;\n        if (mutation) {\n            if (mutation.takeRecords().length) {\n                this._docWasChanged();\n            }\n            mutation.disconnect();\n        }\n\n        this._ignoreAllChanges = true;\n        modificationFn();\n        this._ignoreAllChanges = false;\n\n        if (mutation) {\n            mutation.observe(this._root, {\n                childList: true,\n                attributes: true,\n                characterData: true,\n                subtree: true,\n            });\n            this._ignoreChange = false;\n        }\n\n        return this;\n    }\n\n    _docWasChanged(): void {\n        resetNodeCategoryCache();\n        this._mayHaveZWS = true;\n        if (this._ignoreAllChanges) {\n            return;\n        }\n\n        if (this._ignoreChange) {\n            this._ignoreChange = false;\n            return;\n        }\n        if (this._isInUndoState) {\n            this._isInUndoState = false;\n            this.fireEvent('undoStateChange', {\n                canUndo: true,\n                canRedo: false,\n            });\n        }\n        this.fireEvent('input');\n    }\n\n    /**\n     * Leaves bookmark.\n     */\n    _recordUndoState(range: Range, replace?: boolean): Squire {\n        const isInUndoState = this._isInUndoState;\n        if (!isInUndoState || replace) {\n            // Advance pointer to new position\n            let undoIndex = this._undoIndex + 1;\n            const undoStack = this._undoStack;\n            const undoConfig = this._config.undo;\n            const undoThreshold = undoConfig.documentSizeThreshold;\n            const undoLimit = undoConfig.undoLimit;\n\n            // Truncate stack if longer (i.e. if has been previously undone)\n            if (undoIndex < this._undoStackLength) {\n                undoStack.length = this._undoStackLength = undoIndex;\n            }\n\n            // Add bookmark\n            if (range) {\n                this._saveRangeToBookmark(range);\n            }\n\n            // Don't record if we're already in an undo state\n            if (isInUndoState) {\n                return this;\n            }\n\n            // Get data\n            const html = this._getRawHTML();\n\n            // If this document is above the configured size threshold,\n            // limit the number of saved undo states.\n            // Threshold is in bytes, JS uses 2 bytes per character\n            if (replace) {\n                undoIndex -= 1;\n            }\n            if (undoThreshold > -1 && html.length * 2 > undoThreshold) {\n                if (undoLimit > -1 && undoIndex > undoLimit) {\n                    undoStack.splice(0, undoIndex - undoLimit);\n                    undoIndex = undoLimit;\n                    this._undoStackLength = undoLimit;\n                }\n            }\n\n            // Save data\n            undoStack[undoIndex] = html;\n            this._undoIndex = undoIndex;\n            this._undoStackLength += 1;\n            this._isInUndoState = true;\n        }\n        return this;\n    }\n\n    saveUndoState(range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        this._recordUndoState(range, this._isInUndoState);\n        this._getRangeAndRemoveBookmark(range);\n\n        return this;\n    }\n\n    undo(): Squire {\n        // Sanity check: must not be at beginning of the history stack\n        if (this._undoIndex !== 0 || !this._isInUndoState) {\n            // Make sure any changes since last checkpoint are saved.\n            this._recordUndoState(this.getSelection(), false);\n            this._undoIndex -= 1;\n            this._setRawHTML(this._undoStack[this._undoIndex]);\n            const range = this._getRangeAndRemoveBookmark();\n            if (range) {\n                this.setSelection(range);\n            }\n            this._isInUndoState = true;\n            this.fireEvent('undoStateChange', {\n                canUndo: this._undoIndex !== 0,\n                canRedo: true,\n            });\n            this.fireEvent('input');\n        }\n        return this.focus();\n    }\n\n    redo(): Squire {\n        // Sanity check: must not be at end of stack and must be in an undo\n        // state.\n        const undoIndex = this._undoIndex;\n        const undoStackLength = this._undoStackLength;\n        if (undoIndex + 1 < undoStackLength && this._isInUndoState) {\n            this._undoIndex += 1;\n            this._setRawHTML(this._undoStack[this._undoIndex]);\n            const range = this._getRangeAndRemoveBookmark();\n            if (range) {\n                this.setSelection(range);\n            }\n            this.fireEvent('undoStateChange', {\n                canUndo: true,\n                canRedo: undoIndex + 2 < undoStackLength,\n            });\n            this.fireEvent('input');\n        }\n        return this.focus();\n    }\n\n    // --- Get and set data\n\n    getRoot(): HTMLElement {\n        return this._root;\n    }\n\n    _getRawHTML(): string {\n        return this._root.innerHTML;\n    }\n\n    _setRawHTML(html: string): Squire {\n        const root = this._root;\n        root.innerHTML = html;\n\n        let node: Element | null = root;\n        const child = node.firstChild;\n        if (!child || child.nodeName === 'BR') {\n            const block = this.createDefaultBlock();\n            if (child) {\n                node.replaceChild(block, child);\n            } else {\n                node.appendChild(block);\n            }\n        } else {\n            while ((node = getNextBlock(node, root))) {\n                fixCursor(node);\n            }\n        }\n\n        this._ignoreChange = true;\n\n        return this;\n    }\n\n    getHTML(withBookmark?: boolean): string {\n        let range: Range | undefined;\n        if (withBookmark) {\n            range = this.getSelection();\n            this._saveRangeToBookmark(range);\n        }\n        const html = this._getRawHTML().replace(/\\u200B/g, '');\n        if (withBookmark) {\n            this._getRangeAndRemoveBookmark(range);\n        }\n        return html;\n    }\n\n    setHTML(html: string): Squire {\n        // Parse HTML into DOM tree\n        const frag = this._config.sanitizeToDOMFragment(html, this);\n        const root = this._root;\n\n        // Fixup DOM tree\n        cleanTree(frag, this._config);\n        cleanupBRs(frag, root, false);\n        fixContainer(frag, root);\n\n        // Fix cursor\n        let node: DocumentFragment | HTMLElement | null = frag;\n        let child = node.firstChild;\n        if (!child || child.nodeName === 'BR') {\n            const block = this.createDefaultBlock();\n            if (child) {\n                node.replaceChild(block, child);\n            } else {\n                node.appendChild(block);\n            }\n        } else {\n            while ((node = getNextBlock(node, root))) {\n                fixCursor(node);\n            }\n        }\n\n        // Don't fire an input event\n        this._ignoreChange = true;\n\n        // Remove existing root children and insert new content\n        while ((child = root.lastChild)) {\n            root.removeChild(child);\n        }\n        root.appendChild(frag);\n\n        // Reset the undo stack\n        this._undoIndex = -1;\n        this._undoStack.length = 0;\n        this._undoStackLength = 0;\n        this._isInUndoState = false;\n\n        // Record undo state\n        const range =\n            this._getRangeAndRemoveBookmark() ||\n            createRange(root.firstElementChild || root, 0);\n        this.saveUndoState(range);\n\n        // Set inital selection\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    /**\n     * Insert HTML at the cursor location. If the selection is not collapsed\n     * insertTreeFragmentIntoRange will delete the selection so that it is\n     * replaced by the html being inserted.\n     */\n    insertHTML(html: string, isPaste?: boolean): Squire {\n        // Parse\n        const config = this._config;\n        let frag = config.sanitizeToDOMFragment(html, this);\n\n        // Record undo checkpoint\n        const range = this.getSelection();\n        this.saveUndoState(range);\n\n        try {\n            const root = this._root;\n\n            if (config.addLinks) {\n                this.addDetectedLinks(frag, frag);\n            }\n            cleanTree(frag, this._config);\n            cleanupBRs(frag, root, false);\n            removeEmptyInlines(frag);\n            frag.normalize();\n\n            let node: HTMLElement | DocumentFragment | null = frag;\n            while ((node = getNextBlock(node, frag))) {\n                fixCursor(node);\n            }\n\n            let doInsert = true;\n            if (isPaste) {\n                const event = new CustomEvent('willPaste', {\n                    cancelable: true,\n                    detail: {\n                        fragment: frag,\n                    },\n                });\n                this.fireEvent('willPaste', event);\n                frag = event.detail.fragment;\n                doInsert = !event.defaultPrevented;\n            }\n\n            if (doInsert) {\n                insertTreeFragmentIntoRange(range, frag, root);\n                range.collapse(false);\n\n                // After inserting the fragment, check whether the cursor is\n                // inside an <a> element and if so if there is an equivalent\n                // cursor position after the <a> element. If there is, move it\n                // there.\n                moveRangeBoundaryOutOf(range, 'A', root);\n\n                this._ensureBottomLine();\n            }\n\n            this.setSelection(range);\n            this._updatePath(range, true);\n            // Safari sometimes loses focus after paste. Weird.\n            if (isPaste) {\n                this.focus();\n            }\n        } catch (error) {\n            this._config.didError(error);\n        }\n        return this;\n    }\n\n    insertElement(el: Element, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        range.collapse(true);\n        if (isInline(el)) {\n            insertNodeInRange(range, el);\n            range.setStartAfter(el);\n        } else {\n            // Get containing block node.\n            const root = this._root;\n            const startNode: HTMLElement | null = getStartBlockOfRange(\n                range,\n                root,\n            );\n            let splitNode: Element | Node = startNode || root;\n\n            let nodeAfterSplit: Node | null = null;\n            // While at end of container node, move up DOM tree.\n            while (splitNode !== root && !splitNode.nextSibling) {\n                splitNode = splitNode.parentNode!;\n            }\n            // If in the middle of a container node, split up to root.\n            if (splitNode !== root) {\n                const parent = splitNode.parentNode!;\n                nodeAfterSplit = split(\n                    parent,\n                    splitNode.nextSibling,\n                    root,\n                    root,\n                ) as Node;\n            }\n\n            // If the startNode was empty remove it so that we don't end up\n            // with two blank lines.\n            if (startNode && isEmptyBlock(startNode)) {\n                detach(startNode);\n            }\n\n            // Insert element and blank line.\n            root.insertBefore(el, nodeAfterSplit);\n            const blankLine = this.createDefaultBlock();\n            root.insertBefore(blankLine, nodeAfterSplit);\n\n            // Move cursor to blank line after inserted element.\n            range.setStart(blankLine, 0);\n            range.setEnd(blankLine, 0);\n            moveRangeBoundariesDownTree(range);\n        }\n        this.focus();\n        this.setSelection(range);\n        this._updatePath(range);\n\n        return this;\n    }\n\n    insertImage(\n        src: string,\n        attributes: Record<string, string>,\n    ): HTMLImageElement {\n        const img = createElement(\n            'IMG',\n            Object.assign(\n                {\n                    src: src,\n                },\n                attributes,\n            ),\n        ) as HTMLImageElement;\n        this.insertElement(img);\n        return img;\n    }\n\n    insertPlainText(plainText: string, isPaste: boolean): Squire {\n        const range = this.getSelection();\n        if (\n            range.collapsed &&\n            getNearest(range.startContainer, this._root, 'PRE')\n        ) {\n            const startContainer: Node = range.startContainer;\n            let offset = range.startOffset;\n            let textNode: Text;\n            if (!startContainer || !(startContainer instanceof Text)) {\n                const text = document.createTextNode('');\n                startContainer.insertBefore(\n                    text,\n                    startContainer.childNodes[offset],\n                );\n                textNode = text;\n                offset = 0;\n            } else {\n                textNode = startContainer;\n            }\n            let doInsert = true;\n            if (isPaste) {\n                const event = new CustomEvent('willPaste', {\n                    cancelable: true,\n                    detail: {\n                        text: plainText,\n                    },\n                });\n                this.fireEvent('willPaste', event);\n                plainText = event.detail.text;\n                doInsert = !event.defaultPrevented;\n            }\n\n            if (doInsert) {\n                textNode.insertData(offset, plainText);\n                range.setStart(textNode, offset + plainText.length);\n                range.collapse(true);\n            }\n            this.setSelection(range);\n            return this;\n        }\n        const lines = plainText.split('\\n');\n        const config = this._config;\n        const tag = config.blockTag;\n        const attributes = config.blockAttributes;\n        const closeBlock = '</' + tag + '>';\n        let openBlock = '<' + tag;\n\n        for (const attr in attributes) {\n            openBlock += ' ' + attr + '=\"' + escapeHTML(attributes[attr]) + '\"';\n        }\n        openBlock += '>';\n\n        for (let i = 0, l = lines.length; i < l; i += 1) {\n            let line = lines[i];\n            line = escapeHTML(line).replace(/ (?=(?: |$))/g, '&nbsp;');\n            // We don't wrap the first line in the block, so if it gets inserted\n            // into a blank line it keeps that line's formatting.\n            // Wrap each line in <div></div>\n            if (i) {\n                line = openBlock + (line || '<BR>') + closeBlock;\n            }\n            lines[i] = line;\n        }\n        return this.insertHTML(lines.join(''), isPaste);\n    }\n\n    getSelectedText(range?: Range): string {\n        return getTextContentsOfRange(range || this.getSelection());\n    }\n\n    // --- Inline formatting\n\n    /**\n     * Extracts the font-family and font-size (if any) of the element\n     * holding the cursor. If there's a selection, returns an empty object.\n     */\n    getFontInfo(range?: Range): Record<string, string | undefined> {\n        const fontInfo = {\n            color: undefined,\n            backgroundColor: undefined,\n            fontFamily: undefined,\n            fontSize: undefined,\n        } as Record<string, string | undefined>;\n\n        if (!range) {\n            range = this.getSelection();\n        }\n        moveRangeBoundariesDownTree(range);\n\n        let seenAttributes = 0;\n        let element: Node | null = range.commonAncestorContainer;\n        if (range.collapsed || element instanceof Text) {\n            if (element instanceof Text) {\n                element = element.parentNode!;\n            }\n            while (seenAttributes < 4 && element) {\n                const style = (element as HTMLElement).style;\n                if (style) {\n                    const color = style.color;\n                    if (!fontInfo.color && color) {\n                        fontInfo.color = color;\n                        seenAttributes += 1;\n                    }\n                    const backgroundColor = style.backgroundColor;\n                    if (!fontInfo.backgroundColor && backgroundColor) {\n                        fontInfo.backgroundColor = backgroundColor;\n                        seenAttributes += 1;\n                    }\n                    const fontFamily = style.fontFamily;\n                    if (!fontInfo.fontFamily && fontFamily) {\n                        fontInfo.fontFamily = fontFamily;\n                        seenAttributes += 1;\n                    }\n                    const fontSize = style.fontSize;\n                    if (!fontInfo.fontSize && fontSize) {\n                        fontInfo.fontSize = fontSize;\n                        seenAttributes += 1;\n                    }\n                }\n                element = element.parentNode;\n            }\n        }\n        return fontInfo;\n    }\n\n    /**\n     * Looks for matching tag and attributes, so won't work if <strong>\n     * instead of <b> etc.\n     */\n    hasFormat(\n        tag: string,\n        attributes?: Record<string, string> | null,\n        range?: Range,\n    ): boolean {\n        // 1. Normalise the arguments and get selection\n        tag = tag.toUpperCase();\n        if (!attributes) {\n            attributes = {};\n        }\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Move range up one level in the DOM tree if at the edge of a text\n        // node, so we don't consider it included when it's not really.\n        if (\n            !range.collapsed &&\n            range.startContainer instanceof Text &&\n            range.startOffset === range.startContainer.length &&\n            range.startContainer.nextSibling\n        ) {\n            range.setStartBefore(range.startContainer.nextSibling);\n        }\n        if (\n            !range.collapsed &&\n            range.endContainer instanceof Text &&\n            range.endOffset === 0 &&\n            range.endContainer.previousSibling\n        ) {\n            range.setEndAfter(range.endContainer.previousSibling);\n        }\n\n        // If the common ancestor is inside the tag we require, we definitely\n        // have the format.\n        const root = this._root;\n        const common = range.commonAncestorContainer;\n        if (getNearest(common, root, tag, attributes)) {\n            return true;\n        }\n\n        // If common ancestor is a text node and doesn't have the format, we\n        // definitely don't have it.\n        if (common instanceof Text) {\n            return false;\n        }\n\n        // Otherwise, check each text node at least partially contained within\n        // the selection and make sure all of them have the format we want.\n        const walker = new TreeIterator<Text>(common, SHOW_TEXT, (node) => {\n            return isNodeContainedInRange(range!, node, true);\n        });\n\n        let seenNode = false;\n        let node: Node | null;\n        while ((node = walker.nextNode())) {\n            if (!getNearest(node, root, tag, attributes)) {\n                return false;\n            }\n            seenNode = true;\n        }\n\n        return seenNode;\n    }\n\n    changeFormat(\n        add: { tag: string; attributes?: Record<string, string> } | null,\n        remove?: { tag: string; attributes?: Record<string, string> } | null,\n        range?: Range,\n        partial?: boolean,\n    ): Squire {\n        // Normalise the arguments and get selection\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Save undo checkpoint\n        this.saveUndoState(range);\n\n        if (remove) {\n            range = this._removeFormat(\n                remove.tag.toUpperCase(),\n                remove.attributes || {},\n                range,\n                partial,\n            );\n        }\n        if (add) {\n            range = this._addFormat(\n                add.tag.toUpperCase(),\n                add.attributes || {},\n                range,\n            );\n        }\n\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    _addFormat(\n        tag: string,\n        attributes: Record<string, string> | null,\n        range: Range,\n    ): Range {\n        // If the range is collapsed we simply insert the node by wrapping\n        // it round the range and focus it.\n        const root = this._root;\n        if (range.collapsed) {\n            const el = fixCursor(createElement(tag, attributes));\n            insertNodeInRange(range, el);\n            const focusNode = el.firstChild || el;\n            // Focus after the ZWS if present\n            const focusOffset =\n                focusNode instanceof Text ? focusNode.length : 0;\n            range.setStart(focusNode, focusOffset);\n            range.collapse(true);\n\n            // Clean up any previous formats that may have been set on this\n            // block that are unused.\n            let block = el;\n            while (isInline(block)) {\n                block = block.parentNode!;\n            }\n            removeZWS(block, el);\n            // Otherwise we find all the textnodes in the range (splitting\n            // partially selected nodes) and if they're not already formatted\n            // correctly we wrap them in the appropriate tag.\n        } else {\n            // Create an iterator to walk over all the text nodes under this\n            // ancestor which are in the range and not already formatted\n            // correctly.\n            //\n            // In Blink/WebKit, empty blocks may have no text nodes, just a\n            // <br>. Therefore we wrap this in the tag as well, as this will\n            // then cause it to apply when the user types something in the\n            // block, which is presumably what was intended.\n            //\n            // IMG tags are included because we may want to create a link around\n            // them, and adding other styles is harmless.\n            const walker = new TreeIterator<Element | Text>(\n                range.commonAncestorContainer,\n                SHOW_ELEMENT_OR_TEXT,\n                (node: Node) => {\n                    return (\n                        (node instanceof Text ||\n                            node.nodeName === 'BR' ||\n                            node.nodeName === 'IMG') &&\n                        isNodeContainedInRange(range, node, true)\n                    );\n                },\n            );\n\n            // Start at the beginning node of the range and iterate through\n            // all the nodes in the range that need formatting.\n            let { startContainer, startOffset, endContainer, endOffset } =\n                range;\n\n            // Make sure we start with a valid node.\n            walker.currentNode = startContainer;\n            if (\n                (!(startContainer instanceof Element) &&\n                    !(startContainer instanceof Text)) ||\n                !walker.filter(startContainer)\n            ) {\n                const next = walker.nextNode();\n                // If there are no interesting nodes in the selection, abort\n                if (!next) {\n                    return range;\n                }\n                startContainer = next;\n                startOffset = 0;\n            }\n\n            do {\n                let node = walker.currentNode;\n                const needsFormat = !getNearest(node, root, tag, attributes);\n                if (needsFormat) {\n                    // <br> can never be a container node, so must have a text\n                    // node if node == (end|start)Container\n                    if (\n                        node === endContainer &&\n                        (node as Text).length > endOffset\n                    ) {\n                        (node as Text).splitText(endOffset);\n                    }\n                    if (node === startContainer && startOffset) {\n                        node = (node as Text).splitText(startOffset);\n                        if (endContainer === startContainer) {\n                            endContainer = node;\n                            endOffset -= startOffset;\n                        } else if (endContainer === startContainer.parentNode) {\n                            endOffset += 1;\n                        }\n                        startContainer = node;\n                        startOffset = 0;\n                    }\n                    const el = createElement(tag, attributes);\n                    replaceWith(node, el);\n                    el.appendChild(node);\n                }\n            } while (walker.nextNode());\n\n            // Now set the selection to as it was before\n            range = createRange(\n                startContainer,\n                startOffset,\n                endContainer,\n                endOffset,\n            );\n        }\n        return range;\n    }\n\n    _removeFormat(\n        tag: string,\n        attributes: Record<string, string>,\n        range: Range,\n        partial?: boolean,\n    ): Range {\n        // Add bookmark\n        this._saveRangeToBookmark(range);\n\n        // We need a node in the selection to break the surrounding\n        // formatted text.\n        let fixer: Node | Text | null | undefined;\n        if (range.collapsed) {\n            if (cantFocusEmptyTextNodes) {\n                fixer = document.createTextNode(ZWS);\n            } else {\n                fixer = document.createTextNode('');\n            }\n            insertNodeInRange(range, fixer!);\n        }\n\n        // Find block-level ancestor of selection\n        let root = range.commonAncestorContainer;\n        while (isInline(root)) {\n            root = root.parentNode!;\n        }\n\n        // Find text nodes inside formatTags that are not in selection and\n        // add an extra tag with the same formatting.\n        const startContainer = range.startContainer;\n        const startOffset = range.startOffset;\n        const endContainer = range.endContainer;\n        const endOffset = range.endOffset;\n        const toWrap: [Node, Node][] = [];\n        const examineNode = (node: Node, exemplar: Node) => {\n            // If the node is completely contained by the range then\n            // we're going to remove all formatting so ignore it.\n            if (isNodeContainedInRange(range, node, false)) {\n                return;\n            }\n\n            let child: Node;\n            let next: Node;\n\n            // If not at least partially contained, wrap entire contents\n            // in a clone of the tag we're removing and we're done.\n            if (!isNodeContainedInRange(range, node, true)) {\n                // Ignore bookmarks and empty text nodes\n                if (\n                    !(node instanceof HTMLInputElement) &&\n                    (!(node instanceof Text) || node.data)\n                ) {\n                    toWrap.push([exemplar, node]);\n                }\n                return;\n            }\n\n            // Split any partially selected text nodes.\n            if (node instanceof Text) {\n                if (node === endContainer && endOffset !== node.length) {\n                    toWrap.push([exemplar, node.splitText(endOffset)]);\n                }\n                if (node === startContainer && startOffset) {\n                    node.splitText(startOffset);\n                    toWrap.push([exemplar, node]);\n                }\n            } else {\n                // If not a text node, recurse onto all children.\n                // Beware, the tree may be rewritten with each call\n                // to examineNode, hence find the next sibling first.\n                for (child = node.firstChild!; child; child = next) {\n                    next = child.nextSibling!;\n                    examineNode(child, exemplar);\n                }\n            }\n        };\n        const formatTags = Array.from(\n            (root as Element).getElementsByTagName(tag),\n        ).filter((el: Node): boolean => {\n            return (\n                isNodeContainedInRange(range, el, true) &&\n                hasTagAttributes(el, tag, attributes)\n            );\n        });\n\n        if (!partial) {\n            formatTags.forEach((node: Node) => {\n                examineNode(node, node);\n            });\n        }\n\n        // Now wrap unselected nodes in the tag\n        toWrap.forEach(([el, node]) => {\n            el = el.cloneNode(false);\n            replaceWith(node, el);\n            el.appendChild(node);\n        });\n        // and remove old formatting tags.\n        formatTags.forEach((el: Element) => {\n            replaceWith(el, empty(el));\n        });\n\n        if (cantFocusEmptyTextNodes && fixer) {\n            // Clean up any previous ZWS in this block. They are not needed,\n            // and this works around a Chrome bug where it doesn't render the\n            // text in some situations with multiple ZWS(!)\n            fixer = fixer.parentNode;\n            let block = fixer;\n            while (block && isInline(block)) {\n                block = block.parentNode;\n            }\n            if (block) {\n                removeZWS(block, fixer);\n            }\n        }\n\n        // Merge adjacent inlines:\n        this._getRangeAndRemoveBookmark(range);\n        if (fixer) {\n            range.collapse(false);\n        }\n        mergeInlines(root, range);\n\n        return range;\n    }\n\n    // ---\n\n    bold(): Squire {\n        return this.changeFormat({ tag: 'B' });\n    }\n\n    removeBold(): Squire {\n        return this.changeFormat(null, { tag: 'B' });\n    }\n\n    italic(): Squire {\n        return this.changeFormat({ tag: 'I' });\n    }\n\n    removeItalic(): Squire {\n        return this.changeFormat(null, { tag: 'I' });\n    }\n\n    underline(): Squire {\n        return this.changeFormat({ tag: 'U' });\n    }\n\n    removeUnderline(): Squire {\n        return this.changeFormat(null, { tag: 'U' });\n    }\n\n    strikethrough(): Squire {\n        return this.changeFormat({ tag: 'S' });\n    }\n\n    removeStrikethrough(): Squire {\n        return this.changeFormat(null, { tag: 'S' });\n    }\n\n    subscript(): Squire {\n        return this.changeFormat({ tag: 'SUB' }, { tag: 'SUP' });\n    }\n\n    removeSubscript(): Squire {\n        return this.changeFormat(null, { tag: 'SUB' });\n    }\n\n    superscript(): Squire {\n        return this.changeFormat({ tag: 'SUP' }, { tag: 'SUB' });\n    }\n\n    removeSuperscript(): Squire {\n        return this.changeFormat(null, { tag: 'SUP' });\n    }\n\n    // ---\n\n    makeLink(url: string, attributes?: Record<string, string>): Squire {\n        const range = this.getSelection();\n        if (range.collapsed) {\n            let protocolEnd = url.indexOf(':') + 1;\n            if (protocolEnd) {\n                while (url[protocolEnd] === '/') {\n                    protocolEnd += 1;\n                }\n            }\n            insertNodeInRange(\n                range,\n                document.createTextNode(url.slice(protocolEnd)),\n            );\n        }\n        attributes = Object.assign(\n            {\n                href: url,\n            },\n            this._config.tagAttributes.a,\n            attributes,\n        );\n\n        return this.changeFormat(\n            {\n                tag: 'A',\n                attributes: attributes as Record<string, string>,\n            },\n            {\n                tag: 'A',\n            },\n            range,\n        );\n    }\n\n    removeLink(): Squire {\n        return this.changeFormat(\n            null,\n            {\n                tag: 'A',\n            },\n            this.getSelection(),\n            true,\n        );\n    }\n\n    /*\n    linkRegExp = new RegExp(\n        // Only look on boundaries\n        '\\\\b(?:' +\n        // Capture group 1: URLs\n        '(' +\n            // Add links to URLS\n            // Starts with:\n            '(?:' +\n                // http(s):// or ftp://\n                '(?:ht|f)tps?:\\\\/\\\\/' +\n                // or\n                '|' +\n                // www.\n                'www\\\\d{0,3}[.]' +\n                // or\n                '|' +\n                // foo90.com/\n                '[a-z0-9][a-z0-9.\\\\-]*[.][a-z]{2,}\\\\/' +\n            ')' +\n            // Then we get one or more:\n            '(?:' +\n                // Run of non-spaces, non ()<>\n                '[^\\\\s()<>]+' +\n                // or\n                '|' +\n                // balanced parentheses (one level deep only)\n                '\\\\([^\\\\s()<>]+\\\\)' +\n            ')+' +\n            // And we finish with\n            '(?:' +\n                // Not a space or punctuation character\n                '[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]' +\n                // or\n                '|' +\n                // Balanced parentheses.\n                '\\\\([^\\\\s()<>]+\\\\)' +\n            ')' +\n        // Capture group 2: Emails\n        ')|(' +\n            // Add links to emails\n            '[\\\\w\\\\-.%+]+@(?:[\\\\w\\\\-]+\\\\.)+[a-z]{2,}\\\\b' +\n            // Allow query parameters in the mailto: style\n            '(?:' +\n                '[?][^&?\\\\s]+=[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+' +\n                '(?:&[^&?\\\\s]+=[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+)*' +\n            ')?' +\n        '))',\n        'i'\n    );\n    */\n    linkRegExp =\n        /\\b(?:((?:(?:ht|f)tps?:\\/\\/|www\\d{0,3}[.]|[a-z0-9][a-z0-9.\\-]*[.][a-z]{2,}\\/)(?:[^\\s()<>]+|\\([^\\s()<>]+\\))+(?:[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]|\\([^\\s()<>]+\\)))|([\\w\\-.%+]+@(?:[\\w\\-]+\\.)+[a-z]{2,}\\b(?:[?][^&?\\s]+=[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+(?:&[^&?\\s]+=[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+)*)?))/i;\n\n    addDetectedLinks(\n        searchInNode: DocumentFragment | Node,\n        root?: DocumentFragment | HTMLElement,\n    ): Squire {\n        const walker = new TreeIterator<Text>(\n            searchInNode,\n            SHOW_TEXT,\n            (node) => !getNearest(node, root || this._root, 'A'),\n        );\n        const linkRegExp = this.linkRegExp;\n        const defaultAttributes = this._config.tagAttributes.a;\n        let node: Text | null;\n        while ((node = walker.nextNode())) {\n            const parent = node.parentNode!;\n            let data = node.data;\n            let match: RegExpExecArray | null;\n            while ((match = linkRegExp.exec(data))) {\n                const index = match.index;\n                const endIndex = index + match[0].length;\n                if (index) {\n                    parent.insertBefore(\n                        document.createTextNode(data.slice(0, index)),\n                        node,\n                    );\n                }\n                const child = createElement(\n                    'A',\n                    Object.assign(\n                        {\n                            href: match[1]\n                                ? /^(?:ht|f)tps?:/i.test(match[1])\n                                    ? match[1]\n                                    : 'http://' + match[1]\n                                : 'mailto:' + match[0],\n                        },\n                        defaultAttributes,\n                    ),\n                );\n                child.textContent = data.slice(index, endIndex);\n                parent.insertBefore(child, node);\n                node.data = data = data.slice(endIndex);\n            }\n        }\n        return this;\n    }\n\n    // ---\n\n    setFontFace(name: string | null): Squire {\n        const className = this._config.classNames.fontFamily;\n        return this.changeFormat(\n            name\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'font-family: ' + name + ', sans-serif;',\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setFontSize(size: string | null): Squire {\n        const className = this._config.classNames.fontSize;\n        return this.changeFormat(\n            size\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style:\n                              'font-size: ' +\n                              (typeof size === 'number' ? size + 'px' : size),\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setTextColor(color: string | null): Squire {\n        const className = this._config.classNames.color;\n        return this.changeFormat(\n            color\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'color:' + color,\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setHighlightColor(color: string | null): Squire {\n        const className = this._config.classNames.highlight;\n        return this.changeFormat(\n            color\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'background-color:' + color,\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    // --- Block formatting\n\n    _ensureBottomLine(): void {\n        const root = this._root;\n        const last = root.lastElementChild;\n        if (\n            !last ||\n            last.nodeName !== this._config.blockTag ||\n            !isBlock(last)\n        ) {\n            root.appendChild(this.createDefaultBlock());\n        }\n    }\n\n    createDefaultBlock(children?: Node[]): HTMLElement {\n        const config = this._config;\n        return fixCursor(\n            createElement(config.blockTag, config.blockAttributes, children),\n        ) as HTMLElement;\n    }\n\n    tagAfterSplit: Record<string, string> = {\n        DT: 'DD',\n        DD: 'DT',\n        LI: 'LI',\n        PRE: 'PRE',\n    };\n\n    splitBlock(lineBreakOnly: boolean, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        const root = this._root;\n        let block: Node | Element | null;\n        let parent: Node | null;\n        let node: Node;\n        let nodeAfterSplit: Node;\n\n        // Save undo checkpoint and remove any zws so we don't think there's\n        // content in an empty block.\n        this._recordUndoState(range);\n        this._removeZWS();\n        this._getRangeAndRemoveBookmark(range);\n\n        // Selected text is overwritten, therefore delete the contents\n        // to collapse selection.\n        if (!range.collapsed) {\n            deleteContentsOfRange(range, root);\n        }\n\n        // Linkify text\n        if (this._config.addLinks) {\n            moveRangeBoundariesDownTree(range);\n            const textNode = range.startContainer as Text;\n            const offset = range.startOffset;\n            setTimeout(() => {\n                linkifyText(this, textNode, offset);\n            }, 0);\n        }\n\n        block = getStartBlockOfRange(range, root);\n\n        // Inside a PRE, insert literal newline, unless on blank line.\n        if (block && (parent = getNearest(block, root, 'PRE'))) {\n            moveRangeBoundariesDownTree(range);\n            node = range.startContainer;\n            const offset = range.startOffset;\n            if (!(node instanceof Text)) {\n                node = document.createTextNode('');\n                parent.insertBefore(node, parent.firstChild);\n            }\n            // If blank line: split and insert default block\n            if (\n                !lineBreakOnly &&\n                node instanceof Text &&\n                (node.data.charAt(offset - 1) === '\\n' ||\n                    rangeDoesStartAtBlockBoundary(range, root)) &&\n                (node.data.charAt(offset) === '\\n' ||\n                    rangeDoesEndAtBlockBoundary(range, root))\n            ) {\n                node.deleteData(offset && offset - 1, offset ? 2 : 1);\n                nodeAfterSplit = split(\n                    node,\n                    offset && offset - 1,\n                    root,\n                    root,\n                ) as Node;\n                node = nodeAfterSplit.previousSibling!;\n                if (!node.textContent) {\n                    detach(node);\n                }\n                node = this.createDefaultBlock();\n                nodeAfterSplit.parentNode!.insertBefore(node, nodeAfterSplit);\n                if (!nodeAfterSplit.textContent) {\n                    detach(nodeAfterSplit);\n                }\n                range.setStart(node, 0);\n            } else {\n                (node as Text).insertData(offset, '\\n');\n                fixCursor(parent);\n                // Firefox bug: if you set the selection in the text node after\n                // the new line, it draws the cursor before the line break still\n                // but if you set the selection to the equivalent position\n                // in the parent, it works.\n                if ((node as Text).length === offset + 1) {\n                    range.setStartAfter(node);\n                } else {\n                    range.setStart(node, offset + 1);\n                }\n            }\n            range.collapse(true);\n            this.setSelection(range);\n            this._updatePath(range, true);\n            this._docWasChanged();\n            return this;\n        }\n\n        // If this is a malformed bit of document or in a table;\n        // just play it safe and insert a <br>.\n        if (!block || lineBreakOnly || /^T[HD]$/.test(block.nodeName)) {\n            // If inside an <a>, move focus out\n            moveRangeBoundaryOutOf(range, 'A', root);\n            insertNodeInRange(range, createElement('BR'));\n            range.collapse(false);\n            this.setSelection(range);\n            this._updatePath(range, true);\n            return this;\n        }\n\n        // If in a list, we'll split the LI instead.\n        if ((parent = getNearest(block, root, 'LI'))) {\n            block = parent;\n        }\n\n        if (isEmptyBlock(block as Element)) {\n            if (\n                getNearest(block, root, 'UL') ||\n                getNearest(block, root, 'OL')\n            ) {\n                // Break list\n                this.decreaseListLevel(range);\n                return this;\n                // Break blockquote\n            } else if (getNearest(block, root, 'BLOCKQUOTE')) {\n                this.removeQuote(range);\n                return this;\n            }\n        }\n\n        // Otherwise, split at cursor point.\n        node = range.startContainer;\n        const offset = range.startOffset;\n        let splitTag = this.tagAfterSplit[block.nodeName];\n        nodeAfterSplit = split(\n            node,\n            offset,\n            block.parentNode!,\n            this._root,\n        ) as Node;\n\n        const config = this._config;\n        let splitProperties: Record<string, string> | null = null;\n        if (!splitTag) {\n            splitTag = config.blockTag;\n            splitProperties = config.blockAttributes;\n        }\n\n        // Make sure the new node is the correct type.\n        if (!hasTagAttributes(nodeAfterSplit, splitTag, splitProperties)) {\n            block = createElement(splitTag, splitProperties);\n            if ((nodeAfterSplit as HTMLElement).dir) {\n                (block as HTMLElement).dir = (\n                    nodeAfterSplit as HTMLElement\n                ).dir;\n            }\n            replaceWith(nodeAfterSplit, block);\n            block.appendChild(empty(nodeAfterSplit));\n            nodeAfterSplit = block;\n        }\n\n        // Clean up any empty inlines if we hit enter at the beginning of the\n        // block\n        removeZWS(block);\n        removeEmptyInlines(block);\n        fixCursor(block);\n\n        // Focus cursor\n        // If there's a <b>/<i> etc. at the beginning of the split\n        // make sure we focus inside it.\n        while (nodeAfterSplit instanceof Element) {\n            let child = nodeAfterSplit.firstChild;\n            let next;\n\n            // Don't continue links over a block break; unlikely to be the\n            // desired outcome.\n            if (\n                nodeAfterSplit.nodeName === 'A' &&\n                (!nodeAfterSplit.textContent ||\n                    nodeAfterSplit.textContent === ZWS)\n            ) {\n                child = document.createTextNode('') as Text;\n                replaceWith(nodeAfterSplit, child);\n                nodeAfterSplit = child;\n                break;\n            }\n\n            while (child && child instanceof Text && !child.data) {\n                next = child.nextSibling;\n                if (!next || next.nodeName === 'BR') {\n                    break;\n                }\n                detach(child);\n                child = next;\n            }\n\n            // 'BR's essentially don't count; they're a browser hack.\n            // If you try to select the contents of a 'BR', FF will not let\n            // you type anything!\n            if (!child || child.nodeName === 'BR' || child instanceof Text) {\n                break;\n            }\n            nodeAfterSplit = child;\n        }\n        range = createRange(nodeAfterSplit, 0);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    forEachBlock(\n        fn: (el: HTMLElement) => any,\n        mutates: boolean,\n        range?: Range,\n    ): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Save undo checkpoint\n        if (mutates) {\n            this.saveUndoState(range);\n        }\n\n        const root = this._root;\n        let start = getStartBlockOfRange(range, root);\n        const end = getEndBlockOfRange(range, root);\n        if (start && end) {\n            do {\n                if (fn(start) || start === end) {\n                    break;\n                }\n            } while ((start = getNextBlock(start, root)));\n        }\n\n        if (mutates) {\n            this.setSelection(range);\n            // Path may have changed\n            this._updatePath(range, true);\n        }\n        return this;\n    }\n\n    modifyBlocks(modify: (x: DocumentFragment) => Node, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // 1. Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        // 2. Expand range to block boundaries\n        const root = this._root;\n        expandRangeToBlockBoundaries(range, root);\n\n        // 3. Remove range.\n        moveRangeBoundariesUpTree(range, root, root, root);\n        const frag = extractContentsOfRange(range, root, root);\n\n        // 4. Modify tree of fragment and reinsert.\n        if (!range.collapsed) {\n            // After extracting contents, the range edges will still be at the\n            // level we began the spilt. We want to insert directly in the\n            // root, so move the range up there.\n            let node = range.endContainer;\n            if (node === root) {\n                range.collapse(false);\n            } else {\n                while (node.parentNode !== root) {\n                    node = node.parentNode!;\n                }\n                range.setStartBefore(node);\n                range.collapse(true);\n            }\n        }\n        insertNodeInRange(range, modify.call(this, frag));\n\n        // 5. Merge containers at edges\n        if (range.endOffset < range.endContainer.childNodes.length) {\n            mergeContainers(\n                range.endContainer.childNodes[range.endOffset],\n                root,\n            );\n        }\n        mergeContainers(\n            range.startContainer.childNodes[range.startOffset],\n            root,\n        );\n\n        // 6. Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    // ---\n\n    setTextAlignment(alignment: string): Squire {\n        this.forEachBlock((block: HTMLElement) => {\n            const className = block.className\n                .split(/\\s+/)\n                .filter((klass) => {\n                    return !!klass && !/^align/.test(klass);\n                })\n                .join(' ');\n            if (alignment) {\n                block.className = className + ' align-' + alignment;\n                block.style.textAlign = alignment;\n            } else {\n                block.className = className;\n                block.style.textAlign = '';\n            }\n        }, true);\n        return this.focus();\n    }\n\n    setTextDirection(direction: string | null): Squire {\n        this.forEachBlock((block: HTMLElement) => {\n            if (direction) {\n                block.dir = direction;\n            } else {\n                block.removeAttribute('dir');\n            }\n        }, true);\n        return this.focus();\n    }\n\n    // ---\n\n    _getListSelection(\n        range: Range,\n        root: Element,\n    ): [Node, Node | null, Node | null] | null {\n        let list: Node | null = range.commonAncestorContainer;\n        let startLi: Node | null = range.startContainer;\n        let endLi: Node | null = range.endContainer;\n        while (list && list !== root && !/^[OU]L$/.test(list.nodeName)) {\n            list = list.parentNode;\n        }\n        if (!list || list === root) {\n            return null;\n        }\n        if (startLi === list) {\n            startLi = startLi.childNodes[range.startOffset];\n        }\n        if (endLi === list) {\n            endLi = endLi.childNodes[range.endOffset];\n        }\n        while (startLi && startLi.parentNode !== list) {\n            startLi = startLi.parentNode;\n        }\n        while (endLi && endLi.parentNode !== list) {\n            endLi = endLi.parentNode;\n        }\n        return [list, startLi, endLi];\n    }\n\n    increaseListLevel(range?: Range) {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Get start+end li in single common ancestor\n        const root = this._root;\n        const listSelection = this._getListSelection(range, root);\n        if (!listSelection) {\n            return this.focus();\n        }\n        // eslint-disable-next-line prefer-const\n        let [list, startLi, endLi] = listSelection;\n        if (!startLi || startLi === list.firstChild) {\n            return this.focus();\n        }\n\n        // Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        // Increase list depth\n        const type = list.nodeName;\n        let newParent = startLi.previousSibling!;\n        let listAttrs: Record<string, string> | null;\n        let next: Node | null;\n        if (newParent.nodeName !== type) {\n            listAttrs = this._config.tagAttributes[type.toLowerCase()];\n            newParent = createElement(type, listAttrs);\n            list.insertBefore(newParent, startLi);\n        }\n        do {\n            next = startLi === endLi ? null : startLi.nextSibling;\n            newParent.appendChild(startLi);\n        } while ((startLi = next));\n        next = newParent.nextSibling;\n        if (next) {\n            mergeContainers(next, root);\n        }\n\n        // Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    decreaseListLevel(range?: Range) {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        const root = this._root;\n        const listSelection = this._getListSelection(range, root);\n        if (!listSelection) {\n            return this.focus();\n        }\n\n        // eslint-disable-next-line prefer-const\n        let [list, startLi, endLi] = listSelection;\n        if (!startLi) {\n            startLi = list.firstChild;\n        }\n        if (!endLi) {\n            endLi = list.lastChild!;\n        }\n\n        // Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        let next: Node | null;\n        let insertBefore: Node | null = null;\n        if (startLi) {\n            // Find the new parent list node\n            let newParent = list.parentNode!;\n\n            // Split list if necessary\n            insertBefore = !endLi.nextSibling\n                ? list.nextSibling\n                : (split(list, endLi.nextSibling, newParent, root) as Node);\n\n            if (newParent !== root && newParent.nodeName === 'LI') {\n                newParent = newParent.parentNode!;\n                while (insertBefore) {\n                    next = insertBefore.nextSibling;\n                    endLi.appendChild(insertBefore);\n                    insertBefore = next;\n                }\n                insertBefore = list.parentNode!.nextSibling;\n            }\n\n            const makeNotList = !/^[OU]L$/.test(newParent.nodeName);\n            do {\n                next = startLi === endLi ? null : startLi.nextSibling;\n                list.removeChild(startLi);\n                if (makeNotList && startLi.nodeName === 'LI') {\n                    startLi = this.createDefaultBlock([empty(startLi)]);\n                }\n                newParent.insertBefore(startLi!, insertBefore);\n            } while ((startLi = next));\n        }\n\n        if (!list.firstChild) {\n            detach(list);\n        }\n\n        if (insertBefore) {\n            mergeContainers(insertBefore, root);\n        }\n\n        // Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    _makeList(frag: DocumentFragment, type: string): DocumentFragment {\n        const walker = getBlockWalker(frag, this._root);\n        const tagAttributes = this._config.tagAttributes;\n        const listAttrs = tagAttributes[type.toLowerCase()];\n        const listItemAttrs = tagAttributes.li;\n        let node: Node | null;\n        while ((node = walker.nextNode())) {\n            if (node.parentNode! instanceof HTMLLIElement) {\n                node = node.parentNode!;\n                walker.currentNode = node.lastChild!;\n            }\n            if (!(node instanceof HTMLLIElement)) {\n                const newLi = createElement('LI', listItemAttrs);\n                if ((node as HTMLElement).dir) {\n                    newLi.dir = (node as HTMLElement).dir;\n                }\n\n                // Have we replaced the previous block with a new <ul>/<ol>?\n                const prev: ChildNode | null = node.previousSibling;\n                if (prev && prev.nodeName === type) {\n                    prev.appendChild(newLi);\n                    detach(node);\n                    // Otherwise, replace this block with the <ul>/<ol>\n                } else {\n                    replaceWith(node, createElement(type, listAttrs, [newLi]));\n                }\n                newLi.appendChild(empty(node));\n                walker.currentNode = newLi;\n            } else {\n                node = node.parentNode;\n                const tag = node!.nodeName;\n                if (tag !== type && /^[OU]L$/.test(tag)) {\n                    replaceWith(\n                        node!,\n                        createElement(type, listAttrs, [empty(node!)]),\n                    );\n                }\n            }\n        }\n        return frag;\n    }\n\n    makeUnorderedList(): Squire {\n        this.modifyBlocks((frag) => this._makeList(frag, 'UL'));\n        return this.focus();\n    }\n\n    makeOrderedList(): Squire {\n        this.modifyBlocks((frag) => this._makeList(frag, 'OL'));\n        return this.focus();\n    }\n\n    removeList(): Squire {\n        this.modifyBlocks((frag) => {\n            const lists = frag.querySelectorAll('UL, OL');\n            const items = frag.querySelectorAll('LI');\n            const root = this._root;\n            for (let i = 0, l = lists.length; i < l; i += 1) {\n                const list = lists[i];\n                const listFrag = empty(list);\n                fixContainer(listFrag, root);\n                replaceWith(list, listFrag);\n            }\n\n            for (let i = 0, l = items.length; i < l; i += 1) {\n                const item = items[i];\n                if (isBlock(item)) {\n                    replaceWith(item, this.createDefaultBlock([empty(item)]));\n                } else {\n                    fixContainer(item, root);\n                    replaceWith(item, empty(item));\n                }\n            }\n            return frag;\n        });\n        return this.focus();\n    }\n\n    // ---\n\n    increaseQuoteLevel(range?: Range): Squire {\n        this.modifyBlocks(\n            (frag) =>\n                createElement(\n                    'BLOCKQUOTE',\n                    this._config.tagAttributes.blockquote,\n                    [frag],\n                ),\n            range,\n        );\n        return this.focus();\n    }\n\n    decreaseQuoteLevel(range?: Range): Squire {\n        this.modifyBlocks((frag) => {\n            Array.from(frag.querySelectorAll('blockquote'))\n                .filter((el: Node) => {\n                    return !getNearest(el.parentNode, frag, 'BLOCKQUOTE');\n                })\n                .forEach((el: Node) => {\n                    replaceWith(el, empty(el));\n                });\n            return frag;\n        }, range);\n        return this.focus();\n    }\n\n    removeQuote(range?: Range): Squire {\n        this.modifyBlocks(\n            (/* frag */) =>\n                this.createDefaultBlock([\n                    createElement('INPUT', {\n                        id: this.startSelectionId,\n                        type: 'hidden',\n                    }),\n                    createElement('INPUT', {\n                        id: this.endSelectionId,\n                        type: 'hidden',\n                    }),\n                ]),\n            range,\n        );\n        return this.focus();\n    }\n\n    // ---\n\n    code(): Squire {\n        const range = this.getSelection();\n        if (range.collapsed || isContainer(range.commonAncestorContainer)) {\n            this.modifyBlocks((frag) => {\n                const root = this._root;\n                const output = document.createDocumentFragment();\n                const blockWalker = getBlockWalker(frag, root);\n                let node: Element | Text | null;\n                // 1. Extract inline content; drop all blocks and contains.\n                while ((node = blockWalker.nextNode())) {\n                    // 2. Replace <br> with \\n in content\n                    let nodes = node.querySelectorAll('BR');\n                    const brBreaksLine: boolean[] = [];\n                    let l = nodes.length;\n                    // Must calculate whether the <br> breaks a line first,\n                    // because if we have two <br>s next to each other, after\n                    // the first one is converted to a block split, the second\n                    // will be at the end of a block and therefore seem to not\n                    // be a line break. But in its original context it was, so\n                    // we should also convert it to a block split.\n                    for (let i = 0; i < l; i += 1) {\n                        brBreaksLine[i] = isLineBreak(nodes[i], false);\n                    }\n                    while (l--) {\n                        const br = nodes[l];\n                        if (!brBreaksLine[l]) {\n                            detach(br);\n                        } else {\n                            replaceWith(br, document.createTextNode('\\n'));\n                        }\n                    }\n                    // 3. Remove <code>; its format clashes with <pre>\n                    nodes = node.querySelectorAll('CODE');\n                    l = nodes.length;\n                    while (l--) {\n                        replaceWith(nodes[l], empty(nodes[l]));\n                    }\n                    if (output.childNodes.length) {\n                        output.appendChild(document.createTextNode('\\n'));\n                    }\n                    output.appendChild(empty(node));\n                }\n                // 4. Replace nbsp with regular sp\n                const textWalker = new TreeIterator<Text>(output, SHOW_TEXT);\n                while ((node = textWalker.nextNode())) {\n                    // eslint-disable-next-line no-irregular-whitespace\n                    node.data = node.data.replace(/\u00A0/g, ' '); // nbsp -> sp\n                }\n                output.normalize();\n                return fixCursor(\n                    createElement('PRE', this._config.tagAttributes.pre, [\n                        output,\n                    ]),\n                );\n            }, range);\n            this.focus();\n        } else {\n            this.changeFormat(\n                {\n                    tag: 'CODE',\n                    attributes: this._config.tagAttributes.code,\n                },\n                null,\n                range,\n            );\n        }\n        return this;\n    }\n\n    removeCode(): Squire {\n        const range = this.getSelection();\n        const ancestor = range.commonAncestorContainer;\n        const inPre = getNearest(ancestor, this._root, 'PRE');\n        if (inPre) {\n            this.modifyBlocks((frag) => {\n                const root = this._root;\n                const pres = frag.querySelectorAll('PRE');\n                let l = pres.length;\n                while (l--) {\n                    const pre = pres[l];\n                    const walker = new TreeIterator<Text>(pre, SHOW_TEXT);\n                    let node: Text | null;\n                    while ((node = walker.nextNode())) {\n                        let value = node.data;\n                        value = value.replace(/ (?= )/g, '\u00A0'); // sp -> nbsp\n                        const contents = document.createDocumentFragment();\n                        let index: number;\n                        while ((index = value.indexOf('\\n')) > -1) {\n                            contents.appendChild(\n                                document.createTextNode(value.slice(0, index)),\n                            );\n                            contents.appendChild(createElement('BR'));\n                            value = value.slice(index + 1);\n                        }\n                        node.parentNode!.insertBefore(contents, node);\n                        node.data = value;\n                    }\n                    fixContainer(pre, root);\n                    replaceWith(pre, empty(pre));\n                }\n                return frag;\n            }, range);\n            this.focus();\n        } else {\n            this.changeFormat(null, { tag: 'CODE' }, range);\n        }\n        return this;\n    }\n\n    toggleCode(): Squire {\n        if (this.hasFormat('PRE') || this.hasFormat('CODE')) {\n            this.removeCode();\n        } else {\n            this.code();\n        }\n        return this;\n    }\n\n    // ---\n\n    _removeFormatting(\n        root: DocumentFragment | Element,\n        clean: DocumentFragment | Element,\n    ): DocumentFragment | Element {\n        for (\n            let node = root.firstChild, next: ChildNode | null;\n            node;\n            node = next\n        ) {\n            next = node.nextSibling;\n            if (isInline(node)) {\n                if (\n                    node instanceof Text ||\n                    node.nodeName === 'BR' ||\n                    node.nodeName === 'IMG'\n                ) {\n                    clean.appendChild(node);\n                    continue;\n                }\n            } else if (isBlock(node)) {\n                clean.appendChild(\n                    this.createDefaultBlock([\n                        this._removeFormatting(\n                            node as Element,\n                            document.createDocumentFragment(),\n                        ),\n                    ]),\n                );\n                continue;\n            }\n            this._removeFormatting(node as Element, clean);\n        }\n        return clean;\n    }\n\n    removeAllFormatting(range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        if (range.collapsed) {\n            return this.focus();\n        }\n\n        const root = this._root;\n        let stopNode = range.commonAncestorContainer;\n        while (stopNode && !isBlock(stopNode)) {\n            stopNode = stopNode.parentNode!;\n        }\n        if (!stopNode) {\n            expandRangeToBlockBoundaries(range, root);\n            stopNode = root;\n        }\n        if (stopNode instanceof Text) {\n            return this.focus();\n        }\n\n        // Record undo point\n        this.saveUndoState(range);\n\n        // Avoid splitting where we're already at edges.\n        moveRangeBoundariesUpTree(range, stopNode, stopNode, root);\n\n        // Split the selection up to the block, or if whole selection in same\n        // block, expand range boundaries to ends of block and split up to root.\n        const startContainer = range.startContainer;\n        let startOffset = range.startOffset;\n        const endContainer = range.endContainer;\n        let endOffset = range.endOffset;\n\n        // Split end point first to avoid problems when end and start\n        // in same container.\n        const formattedNodes = document.createDocumentFragment();\n        const cleanNodes = document.createDocumentFragment();\n        const nodeAfterSplit = split(endContainer, endOffset, stopNode, root);\n        let nodeInSplit = split(startContainer, startOffset, stopNode, root);\n        let nextNode: ChildNode | null;\n\n        // Then replace contents in split with a cleaned version of the same:\n        // blocks become default blocks, text and leaf nodes survive, everything\n        // else is obliterated.\n        while (nodeInSplit !== nodeAfterSplit) {\n            nextNode = nodeInSplit!.nextSibling;\n            formattedNodes.appendChild(nodeInSplit!);\n            nodeInSplit = nextNode;\n        }\n        this._removeFormatting(formattedNodes, cleanNodes);\n        cleanNodes.normalize();\n        nodeInSplit = cleanNodes.firstChild;\n        nextNode = cleanNodes.lastChild;\n\n        // Restore selection\n        if (nodeInSplit) {\n            stopNode.insertBefore(cleanNodes, nodeAfterSplit);\n            const childNodes = Array.from(stopNode.childNodes) as Node[];\n            startOffset = childNodes.indexOf(nodeInSplit);\n            endOffset = nextNode ? childNodes.indexOf(nextNode) + 1 : 0;\n        } else if (nodeAfterSplit) {\n            const childNodes = Array.from(stopNode.childNodes) as Node[];\n            startOffset = childNodes.indexOf(nodeAfterSplit);\n            endOffset = startOffset;\n        }\n\n        // Merge text nodes at edges, if possible\n        range.setStart(stopNode, startOffset);\n        range.setEnd(stopNode, endOffset);\n        mergeInlines(stopNode, range);\n\n        // And move back down the tree\n        moveRangeBoundariesDownTree(range);\n\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n}\n\n// ---\n\nexport { Squire };\nexport type { SquireConfig };\n", "import { Squire } from './Editor';\n\n(window as any).Squire = Squire;\n"],
+  "mappings": "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",
   "names": ["always", "TreeIterator", "root", "nodeType", "filter", "node", "current", "ZWS", "ua", "isMac", "isWin", "isIOS", "isAndroid", "isGecko", "isLegacyEdge", "isWebKit", "ctrlKey", "cantFocusEmptyTextNodes", "supportsInputEvents", "notWS", "inlineNodeNames", "leafNodeNames", "UNKNOWN", "INLINE", "BLOCK", "CONTAINER", "cache", "resetNodeCategoryCache", "isLeaf", "node", "getNodeCategory", "nodeCategory", "isInline", "isBlock", "isContainer", "createElement", "tag", "props", "children", "el", "attr", "value", "node", "areAlike", "node2", "isLeaf", "hasTagAttributes", "attributes", "getNearest", "root", "getNodeBeforeOffset", "offset", "getNodeAfterOffset", "returnNode", "getLength", "empty", "frag", "child", "detach", "parent", "replaceWith", "notWSTextNode", "node", "notWS", "isLineBreak", "br", "isLBIfEmptyBlock", "block", "isInline", "walker", "TreeIterator", "removeZWS", "root", "keepNode", "textNode", "index", "ZWS", "parent", "getLength", "START_TO_START", "START_TO_END", "END_TO_END", "END_TO_START", "isNodeContainedInRange", "range", "node", "partial", "nodeRange", "nodeEndBeforeStart", "nodeStartAfterEnd", "nodeStartAfterStart", "nodeEndBeforeEnd", "moveRangeBoundariesDownTree", "startContainer", "startOffset", "endContainer", "endOffset", "child", "isLeaf", "textChild", "prev", "isLineBreak", "getLength", "moveRangeBoundariesUpTree", "startMax", "endMax", "root", "parent", "moveRangeBoundaryOutOf", "tag", "getNearest", "clone", "fixCursor", "node", "fixer", "isInline", "child", "cantFocusEmptyTextNodes", "ZWS", "createElement", "parent", "error", "fixContainer", "container", "root", "wrapper", "isBR", "isContainer", "split", "offset", "stopNode", "nodeAfterSplit", "clone", "next", "getNearest", "_mergeInlines", "fakeRange", "children", "l", "frags", "prev", "areAlike", "getLength", "detach", "empty", "frag", "mergeInlines", "range", "element", "mergeWithBlock", "block", "last", "mergeContainers", "first", "isListItem", "needsFix", "styleToSemantic", "createElement", "notWS", "classNames", "family", "size", "replaceStyles", "node", "_", "config", "style", "newTreeBottom", "newTreeTop", "attr", "converter", "css", "el", "empty", "replaceWith", "replaceWithTag", "tag", "parent", "attributes", "i", "l", "attribute", "fontSizes", "stylesRewriters", "font", "face", "color", "fontSpan", "sizeSpan", "colorSpan", "allowedBlock", "blacklist", "cleanTree", "preserveWS", "children", "nonInlineParent", "isInline", "walker", "TreeIterator", "child", "nodeName", "rewriter", "childLength", "data", "startsWithWS", "endsWithWS", "sibling", "removeEmptyInlines", "isLeaf", "cleanupBRs", "root", "keepForBlankLine", "brs", "brBreaksLine", "isLineBreak", "br", "fixContainer", "detach", "escapeHTML", "text", "getBlockWalker", "node", "root", "walker", "TreeIterator", "isBlock", "getPreviousBlock", "block", "getNextBlock", "isEmptyBlock", "getStartBlockOfRange", "range", "root", "container", "block", "isInline", "getPreviousBlock", "isBlock", "node", "getNodeBeforeOffset", "getNextBlock", "isNodeContainedInRange", "getEndBlockOfRange", "getNodeAfterOffset", "child", "isContent", "notWS", "rangeDoesStartAtBlockBoundary", "startContainer", "startOffset", "nodeAfterCursor", "text", "i", "ZWS", "contentWalker", "TreeIterator", "rangeDoesEndAtBlockBoundary", "endContainer", "endOffset", "currentNode", "length", "expandRangeToBlockBoundaries", "start", "end", "parent", "createRange", "startContainer", "startOffset", "endContainer", "endOffset", "range", "insertNodeInRange", "node", "children", "parent", "afterSplit", "childCount", "extractContentsOfRange", "common", "root", "frag", "split", "next", "detach", "fixCursor", "getAdjacentInlineNode", "iterator", "method", "nextNode", "isLeaf", "isInline", "deleteContentsOfRange", "startBlock", "getStartBlockOfRange", "endBlock", "getEndBlockOfRange", "needsMerge", "moveRangeBoundariesDownTree", "moveRangeBoundariesUpTree", "mergeWithBlock", "child", "TreeIterator", "afterNode", "afterOffset", "beforeNode", "beforeOffset", "offset", "rangeDoesStartAtBlockBoundary", "rangeDoesEndAtBlockBoundary", "insertTreeFragmentIntoRange", "firstInFragIsInline", "fixContainer", "getNextBlock", "stopPoint", "getNearest", "block", "blockContentsAfterSplit", "firstBlockInFrag", "replaceBlock", "isEmptyBlock", "container", "cleanupBRs", "nodeAfterSplit", "getPreviousBlock", "getLength", "nodeBeforeSplit", "isContainer", "mergeContainers", "tempRange", "getTextContentsOfRange", "range", "startContainer", "endContainer", "walker", "TreeIterator", "node", "isNodeContainedInRange", "textContent", "addedTextInBlock", "value", "isInline", "indexOf", "extractRangeToClipboard", "event", "range", "root", "removeRangeFromDocument", "toCleanHTML", "toPlainText", "plainTextOnly", "clipboardData", "isLegacyEdge", "text", "getTextContentsOfRange", "startBlock", "getStartBlockOfRange", "endBlock", "getEndBlockOfRange", "copyRoot", "contents", "deleteContentsOfRange", "moveRangeBoundariesDownTree", "moveRangeBoundariesUpTree", "parent", "newContents", "html", "node", "createElement", "isWin", "_onCut", "error", "_onCopy", "_monitorShiftKey", "_onPaste", "items", "choosePlain", "hasRTF", "hasImage", "plainItem", "htmlItem", "l", "item", "type", "isLink", "notWS", "match", "types", "isGecko", "data", "body", "startContainer", "startOffset", "endContainer", "endOffset", "pasteArea", "next", "first", "detach", "createRange", "_onDrop", "hasPlain", "hasHTML", "Enter", "self", "event", "range", "afterDelete", "self", "range", "node", "parent", "isInline", "ZWS", "isBlock", "getPreviousBlock", "fixCursor", "moveRangeBoundariesDownTree", "detach", "error", "detachUneditableNode", "root", "linkifyText", "textNode", "offset", "getNearest", "data", "searchFrom", "searchText", "match", "selection", "index", "endIndex", "needsSelectionUpdate", "newSelectionOffset", "defaultAttributes", "link", "createElement", "Backspace", "self", "event", "range", "root", "deleteContentsOfRange", "afterDelete", "rangeDoesStartAtBlockBoundary", "startBlock", "getStartBlockOfRange", "current", "fixContainer", "previous", "getPreviousBlock", "detachUneditableNode", "mergeWithBlock", "mergeContainers", "getNearest", "moveRangeBoundariesDownTree", "text", "offset", "a", "Delete", "self", "event", "range", "root", "current", "next", "originalRange", "cursorContainer", "cursorOffset", "nodeAfterCursor", "deleteContentsOfRange", "afterDelete", "rangeDoesEndAtBlockBoundary", "getStartBlockOfRange", "fixContainer", "getNextBlock", "detachUneditableNode", "mergeWithBlock", "mergeContainers", "moveRangeBoundariesUpTree", "detach", "moveRangeBoundariesDownTree", "Tab", "self", "event", "range", "root", "rangeDoesStartAtBlockBoundary", "node", "getStartBlockOfRange", "parent", "ShiftTab", "getNearest", "Space", "self", "event", "range", "_a", "node", "root", "deleteContentsOfRange", "rangeDoesEndAtBlockBoundary", "block", "getStartBlockOfRange", "text", "ZWS", "walker", "TreeIterator", "textNode", "detach", "getLength", "linkRange", "moveRangeBoundariesDownTree", "offset", "linkifyText", "_onKey", "event", "key", "modifiers", "code", "isWin", "range", "deleteContentsOfRange", "keyHandlers", "Backspace", "Delete", "Tab", "ShiftTab", "Space", "self", "root", "rangeDoesEndAtBlockBoundary", "moveRangeBoundariesDownTree", "node", "next", "textNode", "supportsInputEvents", "Enter", "isMac", "isIOS", "mapKeyToFormat", "tag", "remove", "ctrlKey", "path", "Squire", "root", "config", "createRange", "_onCut", "_onCopy", "_onPaste", "_onDrop", "_monitorShiftKey", "_onKey", "keyHandlers", "mutation", "_", "type", "userConfig", "html", "frag", "error", "key", "fn", "event", "alignment", "dir", "detail", "handlers", "isFocused", "handler", "target", "l", "removeZWS", "range", "startNode", "createElement", "endNode", "temp", "insertNodeInRange", "start", "end", "startContainer", "endContainer", "startOffset", "endOffset", "mergeInlines", "selection", "isLeaf", "toStart", "moveRangeBoundariesDownTree", "rect", "node", "ZWS", "parent", "force", "anchor", "focus", "newPath", "path", "id", "classList", "classNames", "styleNames", "modificationFn", "resetNodeCategoryCache", "replace", "isInUndoState", "undoIndex", "undoStack", "undoConfig", "undoThreshold", "undoLimit", "undoStackLength", "child", "block", "getNextBlock", "fixCursor", "withBookmark", "cleanTree", "cleanupBRs", "fixContainer", "isPaste", "removeEmptyInlines", "doInsert", "insertTreeFragmentIntoRange", "moveRangeBoundaryOutOf", "el", "isInline", "getStartBlockOfRange", "splitNode", "nodeAfterSplit", "split", "isEmptyBlock", "detach", "blankLine", "src", "attributes", "img", "plainText", "getNearest", "offset", "textNode", "text", "lines", "tag", "closeBlock", "openBlock", "attr", "escapeHTML", "i", "line", "getTextContentsOfRange", "fontInfo", "seenAttributes", "element", "style", "color", "backgroundColor", "fontFamily", "fontSize", "common", "walker", "TreeIterator", "isNodeContainedInRange", "seenNode", "add", "remove", "partial", "focusNode", "focusOffset", "next", "replaceWith", "fixer", "cantFocusEmptyTextNodes", "toWrap", "examineNode", "exemplar", "formatTags", "hasTagAttributes", "empty", "url", "protocolEnd", "searchInNode", "linkRegExp", "defaultAttributes", "data", "match", "index", "endIndex", "name", "className", "size", "last", "isBlock", "children", "lineBreakOnly", "deleteContentsOfRange", "linkifyText", "rangeDoesStartAtBlockBoundary", "rangeDoesEndAtBlockBoundary", "splitTag", "splitProperties", "mutates", "getEndBlockOfRange", "modify", "expandRangeToBlockBoundaries", "moveRangeBoundariesUpTree", "extractContentsOfRange", "mergeContainers", "klass", "direction", "list", "startLi", "endLi", "listSelection", "newParent", "listAttrs", "insertBefore", "makeNotList", "getBlockWalker", "tagAttributes", "listItemAttrs", "newLi", "prev", "lists", "items", "listFrag", "item", "isContainer", "output", "blockWalker", "nodes", "brBreaksLine", "isLineBreak", "br", "textWalker", "ancestor", "pres", "pre", "value", "contents", "clean", "stopNode", "formattedNodes", "cleanNodes", "nodeInSplit", "nextNode", "childNodes", "Squire"]
 }
diff --git a/dist/squire.mjs b/dist/squire.mjs
index 2a5cd2e..8bc53cf 100644
--- a/dist/squire.mjs
+++ b/dist/squire.mjs
@@ -1,11 +1,11 @@
-var it=()=>!0,T=class{constructor(t,e,n){this.root=t,this.currentNode=t,this.nodeType=e,this.filter=n||it}isAcceptableNode(t){let e=t.nodeType;return!!((e===Node.ELEMENT_NODE?1:e===Node.TEXT_NODE?4:0)&this.nodeType)&&this.filter(t)}nextNode(){let t=this.root,e=this.currentNode,n;for(;;){for(n=e.firstChild;!n&&e&&e!==t;)n=e.nextSibling,n||(e=e.parentNode);if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}previousNode(){let t=this.root,e=this.currentNode,n;for(;;){if(e===t)return null;if(n=e.previousSibling,n)for(;e=n.lastChild;)n=e;else n=e.parentNode;if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}previousPONode(){let t=this.root,e=this.currentNode,n;for(;;){for(n=e.lastChild;!n&&e&&e!==t;)n=e.previousSibling,n||(e=e.parentNode);if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}};var B="\u200B",J=navigator.userAgent,de=/Mac OS X/.test(J),fe=/Windows NT/.test(J),_e=/iP(?:ad|hone|od)/.test(J)||de&&!!navigator.maxTouchPoints,xt=/Android/.test(J),Fe=/Gecko\//.test(J),se=/Edge\//.test(J),st=!se&&/WebKit\//.test(J),k=de||_e?"Meta-":"Ctrl-",oe=st,He="onbeforeinput"in document&&"inputType"in new InputEvent("input"),I=/[^ \t\r\n]/;var at=/^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/,ct=new Set(["BR","HR","IFRAME","IMG","INPUT"]),dt=0,xe=1,Pe=2,Ue=3,ue=new WeakMap,qe=()=>{ue=new WeakMap},M=o=>ct.has(o.nodeName),be=o=>{switch(o.nodeType){case 3:return xe;case 1:case 11:if(ue.has(o))return ue.get(o);break;default:return dt}let t;return Array.from(o.childNodes).every(N)?at.test(o.nodeName)?t=xe:t=Pe:t=Ue,ue.set(o,t),t},N=o=>be(o)===xe,P=o=>be(o)===Pe,Q=o=>be(o)===Ue;var p=(o,t,e)=>{let n=document.createElement(o);if(t instanceof Array&&(e=t,t=null),t)for(let i in t){let s=t[i];s!==void 0&&n.setAttribute(i,s)}return e&&e.forEach(i=>n.appendChild(i)),n},Le=(o,t)=>M(o)||o.nodeType!==t.nodeType||o.nodeName!==t.nodeName?!1:o instanceof HTMLElement&&t instanceof HTMLElement?o.nodeName!=="A"&&o.className===t.className&&o.style.cssText===t.style.cssText:!0,he=(o,t,e)=>{if(o.nodeName!==t)return!1;for(let n in e)if(!("getAttribute"in o)||o.getAttribute(n)!==e[n])return!1;return!0},S=(o,t,e,n)=>{for(;o&&o!==t;){if(he(o,e,n))return o;o=o.parentNode}return null},me=(o,t)=>{let e=o.childNodes;for(;t&&o instanceof Element;)o=e[t-1],e=o.childNodes,t=e.length;return o},Re=(o,t)=>{let e=o;if(e instanceof Element){let n=e.childNodes;if(t<n.length)e=n[t];else{for(;e&&!e.nextSibling;)e=e.parentNode;e&&(e=e.nextSibling)}}return e},y=o=>o instanceof Element||o instanceof DocumentFragment?o.childNodes.length:o instanceof CharacterData?o.length:0,C=o=>{let t=document.createDocumentFragment(),e=o.firstChild;for(;e;)t.appendChild(e),e=o.firstChild;return t},E=o=>{let t=o.parentNode;return t&&t.removeChild(o),o},R=(o,t)=>{let e=o.parentNode;e&&e.replaceChild(t,o)};var ft=o=>o instanceof Element?o.nodeName==="BR":I.test(o.data),ee=(o,t)=>{let e=o.parentNode;for(;N(e);)e=e.parentNode;let n=new T(e,5,ft);return n.currentNode=o,!!n.nextNode()||t&&!n.previousNode()},re=(o,t)=>{let e=new T(o,4),n,i;for(;n=e.nextNode();)for(;(i=n.data.indexOf(B))>-1&&(!t||n.parentNode!==t);)if(n.length===1){let s=n,r=s.parentNode;for(;r&&(r.removeChild(s),e.currentNode=r,!(!N(r)||y(r)));)s=r,r=s.parentNode;break}else n.deleteData(i,1)};var ut=0,ht=1,mt=2,pt=3,K=(o,t,e)=>{let n=document.createRange();if(n.selectNode(t),e){let i=o.compareBoundaryPoints(pt,n)>-1,s=o.compareBoundaryPoints(ht,n)<1;return!i&&!s}else{let i=o.compareBoundaryPoints(ut,n)<1,s=o.compareBoundaryPoints(mt,n)>-1;return i&&s}},_=o=>{let{startContainer:t,startOffset:e,endContainer:n,endOffset:i}=o;for(;!(t instanceof Text);){let s=t.childNodes[e];if(!s||M(s)){if(e&&(s=t.childNodes[e-1],s instanceof Text)){let r=s,l;for(;!r.length&&(l=r.previousSibling)&&l instanceof Text;)r.remove(),r=l;t=r,e=r.data.length}break}t=s,e=0}if(i)for(;!(n instanceof Text);){let s=n.childNodes[i-1];if(!s||M(s)){if(s&&s.nodeName==="BR"&&!ee(s,!1)){i-=1;continue}break}n=s,i=y(n)}else for(;!(n instanceof Text);){let s=n.firstChild;if(!s||M(s))break;n=s}o.setStart(t,e),o.setEnd(n,i)},q=(o,t,e,n)=>{let i=o.startContainer,s=o.startOffset,r=o.endContainer,l=o.endOffset,a;for(t||(t=o.commonAncestorContainer),e||(e=t);!s&&i!==t&&i!==n;)a=i.parentNode,s=Array.from(a.childNodes).indexOf(i),i=a;for(;!(r===e||r===n||(r.nodeType!==3&&r.childNodes[l]&&r.childNodes[l].nodeName==="BR"&&!ee(r.childNodes[l],!1)&&(l+=1),l!==y(r)));)a=r.parentNode,l=Array.from(a.childNodes).indexOf(r)+1,r=a;o.setStart(i,s),o.setEnd(r,l)},Oe=(o,t,e)=>{let n=S(o.endContainer,e,t);if(n&&(n=n.parentNode)){let i=o.cloneRange();q(i,n,n,e),i.endContainer===n&&(o.setStart(i.endContainer,i.endOffset),o.setEnd(i.endContainer,i.endOffset))}return o};var x=o=>{let t=null;if(o instanceof Text)return o;if(N(o)){let e=o.firstChild;if(oe)for(;e&&e instanceof Text&&!e.data;)o.removeChild(e),e=o.firstChild;e||(oe?t=document.createTextNode(B):t=document.createTextNode(""))}else if((o instanceof Element||o instanceof DocumentFragment)&&!o.querySelector("BR")){t=p("BR");let e=o,n;for(;(n=e.lastElementChild)&&!N(n);)e=n;o=e}if(t)try{o.appendChild(t)}catch{}return o},D=(o,t)=>{let e=null;return Array.from(o.childNodes).forEach(n=>{let i=n.nodeName==="BR";!i&&N(n)?(e||(e=p("DIV")),e.appendChild(n)):(i||e)&&(e||(e=p("DIV")),x(e),i?o.replaceChild(e,n):o.insertBefore(e,n),e=null),Q(n)&&D(n,t)}),e&&o.appendChild(x(e)),o},w=(o,t,e,n)=>{if(o instanceof Text&&o!==e){if(typeof t!="number")throw new Error("Offset must be a number to split text node!");if(!o.parentNode)throw new Error("Cannot split text node with no parent!");return w(o.parentNode,o.splitText(t),e,n)}let i=typeof t=="number"?t<o.childNodes.length?o.childNodes[t]:null:t,s=o.parentNode;if(!s||o===e||!(o instanceof Element))return i;let r=o.cloneNode(!1);for(;i;){let l=i.nextSibling;r.appendChild(i),i=l}return o instanceof HTMLOListElement&&S(o,n,"BLOCKQUOTE")&&(r.start=(+o.start||1)+o.childNodes.length-1),x(o),x(r),s.insertBefore(r,o.nextSibling),w(s,r,e,n)},We=(o,t)=>{let e=o.childNodes,n=e.length,i=[];for(;n--;){let s=e[n],r=n?e[n-1]:null;if(r&&N(s)&&Le(s,r))t.startContainer===s&&(t.startContainer=r,t.startOffset+=y(r)),t.endContainer===s&&(t.endContainer=r,t.endOffset+=y(r)),t.startContainer===o&&(t.startOffset>n?t.startOffset-=1:t.startOffset===n&&(t.startContainer=r,t.startOffset=y(r))),t.endContainer===o&&(t.endOffset>n?t.endOffset-=1:t.endOffset===n&&(t.endContainer=r,t.endOffset=y(r))),E(s),s instanceof Text?r.appendData(s.data):i.push(C(s));else if(s instanceof Element){let l;for(;l=i.pop();)s.appendChild(l);We(s,t)}}},te=(o,t)=>{let e=o instanceof Text?o.parentNode:o;if(e instanceof Element){let n={startContainer:t.startContainer,startOffset:t.startOffset,endContainer:t.endContainer,endOffset:t.endOffset};We(e,n),t.setStart(n.startContainer,n.startOffset),t.setEnd(n.endContainer,n.endOffset)}},Y=(o,t,e,n)=>{let i=t,s,r;for(;(s=i.parentNode)&&s!==n&&s instanceof Element&&s.childNodes.length===1;)i=s;E(i),r=o.childNodes.length;let l=o.lastChild;l&&l.nodeName==="BR"&&(o.removeChild(l),r-=1),o.appendChild(C(t)),e.setStart(o,r),e.collapse(!0),te(o,e)},F=(o,t)=>{let e=o.previousSibling,n=o.firstChild,i=o.nodeName==="LI";if(!(i&&(!n||!/^[OU]L$/.test(n.nodeName)))){if(e&&Le(e,o)){if(!Q(e))if(i){let r=p("DIV");r.appendChild(C(e)),e.appendChild(r)}else return;E(o);let s=!Q(o);e.appendChild(C(o)),s&&D(e,t),n&&F(n,t)}else if(i){let s=p("DIV");o.insertBefore(s,n),x(s)}}};var Ke={"font-weight":{regexp:/^bold|^700/i,replace(){return p("B")}},"font-style":{regexp:/^italic/i,replace(){return p("I")}},"font-family":{regexp:I,replace(o,t){return p("SPAN",{class:o.fontFamily,style:"font-family:"+t})}},"font-size":{regexp:I,replace(o,t){return p("SPAN",{class:o.fontSize,style:"font-size:"+t})}},"text-decoration":{regexp:/^underline/i,replace(){return p("U")}}},Nt=(o,t,e)=>{let n=o.style,i,s;for(let r in Ke){let l=Ke[r],a=n.getPropertyValue(r);if(a&&l.regexp.test(a)){let d=l.replace(e.classNames,a);if(d.nodeName===o.nodeName&&d.className===o.className)continue;s||(s=d),i&&i.appendChild(d),i=d,o.style.removeProperty(r)}}return s&&i&&(i.appendChild(C(o)),o.style.cssText?o.appendChild(s):R(o,s)),i||o},pe=o=>(t,e)=>{let n=p(o),i=t.attributes;for(let s=0,r=i.length;s<r;s+=1){let l=i[s];n.setAttribute(l.name,l.value)}return e.replaceChild(n,t),n.appendChild(C(t)),n},St={1:"10",2:"13",3:"16",4:"18",5:"24",6:"32",7:"48"},gt={STRONG:pe("B"),EM:pe("I"),INS:pe("U"),STRIKE:pe("S"),SPAN:Nt,FONT:(o,t,e)=>{let n=o,i=n.face,s=n.size,r=n.color,l=e.classNames,a,d,c,f,u;return i&&(a=p("SPAN",{class:l.fontFamily,style:"font-family:"+i}),u=a,f=a),s&&(d=p("SPAN",{class:l.fontSize,style:"font-size:"+St[s]+"px"}),u||(u=d),f&&f.appendChild(d),f=d),r&&/^#?([\dA-F]{3}){1,2}$/i.test(r)&&(r.charAt(0)!=="#"&&(r="#"+r),c=p("SPAN",{class:l.color,style:"color:"+r}),u||(u=c),f&&f.appendChild(c),f=c),(!u||!f)&&(u=f=p("SPAN")),t.replaceChild(u,n),f.appendChild(C(n)),f},TT:(o,t,e)=>{let n=p("SPAN",{class:e.classNames.fontFamily,style:'font-family:menlo,consolas,"courier new",monospace'});return t.replaceChild(n,o),n.appendChild(C(o)),n}},Et=/^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/,Tt=/^(?:HEAD|META|STYLE)/,Ne=(o,t,e)=>{let n=o.childNodes,i=o;for(;N(i);)i=i.parentNode;let s=new T(i,5);for(let r=0,l=n.length;r<l;r+=1){let a=n[r],d=a.nodeName,c=gt[d];if(a instanceof HTMLElement){let f=a.childNodes.length;if(c)a=c(a,o,t);else if(Tt.test(d)){o.removeChild(a),r-=1,l-=1;continue}else if(!Et.test(d)&&!N(a)){r-=1,l+=f-1,o.replaceChild(C(a),a);continue}f&&Ne(a,t,e||d==="PRE")}else{if(a instanceof Text){let f=a.data,u=!I.test(f.charAt(0)),m=!I.test(f.charAt(f.length-1));if(e||!u&&!m)continue;if(u){s.currentNode=a;let h;for(;(h=s.previousPONode())&&!(h.nodeName==="IMG"||h instanceof Text&&I.test(h.data));)if(!N(h)){h=null;break}f=f.replace(/^[ \t\r\n]+/g,h?" ":"")}if(m){s.currentNode=a;let h;for(;(h=s.nextNode())&&!(h.nodeName==="IMG"||h instanceof Text&&I.test(h.data));)if(!N(h)){h=null;break}f=f.replace(/[ \t\r\n]+$/g,h?" ":"")}if(f){a.data=f;continue}}o.removeChild(a),r-=1,l-=1}}return o},Se=o=>{let t=o.childNodes,e=t.length;for(;e--;){let n=t[e];n instanceof Element&&!M(n)?(Se(n),N(n)&&!n.firstChild&&o.removeChild(n)):n instanceof Text&&!n.data&&o.removeChild(n)}},le=(o,t,e)=>{let n=o.querySelectorAll("BR"),i=[],s=n.length;for(let r=0;r<s;r+=1)i[r]=ee(n[r],e);for(;s--;){let r=n[s],l=r.parentNode;l&&(i[s]?N(l)||D(l,t):E(r))}},ke=o=>o.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;").split('"').join("&quot;");var ae=(o,t)=>{let e=new T(t,1,P);return e.currentNode=o,e},z=(o,t)=>{let e=ae(o,t).previousNode();return e!==t?e:null},W=(o,t)=>{let e=ae(o,t).nextNode();return e!==t?e:null},ce=o=>!o.textContent&&!o.querySelector("IMG");var L=(o,t)=>{let e=o.startContainer,n;if(N(e))n=z(e,t);else if(e!==t&&e instanceof HTMLElement&&P(e))n=e;else{let i=me(e,o.startOffset);n=W(i,t)}return n&&K(o,n,!0)?n:null},G=(o,t)=>{let e=o.endContainer,n;if(N(e))n=z(e,t);else if(e!==t&&e instanceof HTMLElement&&P(e))n=e;else{let i=Re(e,o.endOffset);if(!i||!t.contains(i)){i=t;let s;for(;s=i.lastChild;)i=s}n=z(i,t)}return n&&K(o,n,!0)?n:null},ze=o=>o instanceof Text?I.test(o.data):o.nodeName==="IMG",X=(o,t)=>{let e=o.startContainer,n=o.startOffset,i;if(e instanceof Text){let l=e.data;for(let a=n;a>0;a-=1)if(l.charAt(a-1)!==B)return!1;i=e}else if(i=Re(e,n),i&&!t.contains(i)&&(i=null),!i&&(i=me(e,n),i instanceof Text&&i.length))return!1;let s=L(o,t);if(!s)return!1;let r=new T(s,5,ze);return r.currentNode=i,!r.previousNode()},Z=(o,t)=>{let e=o.endContainer,n=o.endOffset,i;if(e instanceof Text){let l=e.data,a=l.length;for(let d=n;d<a;d+=1)if(l.charAt(d)!==B)return!1;i=e}else i=me(e,n);let s=G(o,t);if(!s)return!1;let r=new T(s,5,ze);return r.currentNode=i,!r.nextNode()},ye=(o,t)=>{let e=L(o,t),n=G(o,t),i;e&&n&&(i=e.parentNode,o.setStart(i,Array.from(i.childNodes).indexOf(e)),i=n.parentNode,o.setEnd(i,Array.from(i.childNodes).indexOf(n)+1))};function $(o,t,e,n){let i=document.createRange();return i.setStart(o,t),e&&typeof n=="number"?i.setEnd(e,n):i.setEnd(o,t),i}var j=(o,t)=>{let{startContainer:e,startOffset:n,endContainer:i,endOffset:s}=o,r;if(e instanceof Text){let a=e.parentNode;if(r=a.childNodes,n===e.length)n=Array.from(r).indexOf(e)+1,o.collapsed&&(i=a,s=n);else{if(n){let d=e.splitText(n);i===e?(s-=n,i=d):i===a&&(s+=1),e=d}n=Array.from(r).indexOf(e)}e=a}else r=e.childNodes;let l=r.length;n===l?e.appendChild(t):e.insertBefore(t,r[n]),e===i&&(s+=r.length-l),o.setStart(e,n),o.setEnd(i,s)},Be=(o,t,e)=>{let n=document.createDocumentFragment();if(o.collapsed)return n;t||(t=o.commonAncestorContainer),t instanceof Text&&(t=t.parentNode);let i=o.startContainer,s=o.startOffset,r=w(o.endContainer,o.endOffset,t,e),l=0,a=w(i,s,t,e);for(;a&&a!==r;){let d=a.nextSibling;n.appendChild(a),a=d}return i instanceof Text&&r instanceof Text&&(i.appendData(r.data),E(r),r=i,l=s),o.setStart(i,s),r?o.setEnd(r,l):o.setEnd(t,t.childNodes.length),x(t),n},Ge=(o,t,e)=>{o.currentNode=e;let n;for(;n=o[t]();){if(n instanceof Text||M(n))return n;if(!N(n))return null}return null},H=(o,t)=>{let e=L(o,t),n=G(o,t),i=e!==n;e&&n&&(_(o),q(o,e,n,t));let s=Be(o,null,t);_(o),i&&(n=G(o,t),e&&n&&e!==n&&Y(e,n,o,t)),e&&x(e);let r=t.firstChild;(!r||r.nodeName==="BR")&&(x(t),t.firstChild&&o.selectNodeContents(t.firstChild)),o.collapse(!0);let l=o.startContainer,a=o.startOffset,d=new T(t,5),c=l,f=a;(!(c instanceof Text)||f===c.data.length)&&(c=Ge(d,"nextNode",c),f=0);let u=l,m=a-1;(!(u instanceof Text)||m===-1)&&(u=Ge(d,"previousPONode",c||(l instanceof Text?l:l.childNodes[a]||l)),u instanceof Text&&(m=u.data.length));let h=null,g=0;return c instanceof Text&&c.data.charAt(f)===" "&&X(o,t)?(h=c,g=f):u instanceof Text&&u.data.charAt(m)===" "&&(c instanceof Text&&c.data.charAt(f)===" "||Z(o,t))&&(h=u,g=m),h&&h.replaceData(g,1,"\xA0"),o.setStart(l,a),o.collapse(!0),s},Ze=(o,t,e)=>{let n=t.firstChild&&N(t.firstChild),i;for(D(t,e),i=t;i=W(i,e);)x(i);o.collapsed||H(o,e),_(o),o.collapse(!1);let s=S(o.endContainer,e,"BLOCKQUOTE")||e,r=L(o,e),l=null,a=W(t,t),d=!n&&!!r&&ce(r);if(r&&a&&!d&&!S(a,t,"PRE")&&!S(a,t,"TABLE")){q(o,r,r,e),o.collapse(!0);let c=o.endContainer,f=o.endOffset;if(le(r,e,!1),N(c)){let u=w(c,f,z(c,e)||e,e);c=u.parentNode,f=Array.from(c.childNodes).indexOf(u)}if(f!==y(c))for(l=document.createDocumentFragment();i=c.childNodes[f];)l.appendChild(i);Y(c,a,o,e),f=Array.from(c.parentNode.childNodes).indexOf(c)+1,c=c.parentNode,o.setEnd(c,f)}if(y(t)){d&&r&&(o.setEndBefore(r),o.collapse(!1),E(r)),q(o,s,s,e);let c=w(o.endContainer,o.endOffset,s,e),f=c?c.previousSibling:s.lastChild;s.insertBefore(t,c),c?o.setEndBefore(c):o.setEnd(s,y(s)),r=G(o,e),_(o);let u=o.endContainer,m=o.endOffset;c&&Q(c)&&F(c,e),c=f&&f.nextSibling,c&&Q(c)&&F(c,e),o.setEnd(u,m)}if(l&&r){let c=o.cloneRange();x(l),Y(r,l,c,e),o.setEnd(c.endContainer,c.endOffset)}_(o)};var ge=o=>{if(o.collapsed)return"";let t=o.startContainer,e=o.endContainer,n=new T(o.commonAncestorContainer,5,a=>K(o,a,!0));n.currentNode=t;let i=t,s="",r=!1,l;for((!(i instanceof Element)&&!(i instanceof Text)||!n.filter(i))&&(i=n.nextNode());i;)i instanceof Text?(l=i.data,l&&/\S/.test(l)&&(i===e&&(l=l.slice(0,o.endOffset)),i===t&&(l=l.slice(o.startOffset)),s+=l,r=!0)):(i.nodeName==="BR"||r&&!N(i))&&(s+=`
-`,r=!1),i=n.nextNode();return s=s.replace(/ /g," "),s};var De=Array.prototype.indexOf,je=(o,t,e,n,i,s,r)=>{let l=o.clipboardData;if(se||!l)return!1;let a=s?"":ge(t),d=L(t,e),c=G(t,e),f=e;d===c&&d?.contains(t.commonAncestorContainer)&&(f=d);let u;n?u=H(t,e):(t=t.cloneRange(),_(t),q(t,f,f,e),u=t.cloneContents());let m=t.commonAncestorContainer;for(m instanceof Text&&(m=m.parentNode);m&&m!==f;){let g=m.cloneNode(!1);g.appendChild(u),u=g,m=m.parentNode}let h;if(u.childNodes.length===1&&u.childNodes[0]instanceof Text)a=u.childNodes[0].data.replace(/ /g," "),r=!0;else{let g=p("DIV");g.appendChild(u),h=g.innerHTML,i&&(h=i(h))}return s&&h!==void 0&&(a=s(h)),fe&&(a=a.replace(/\r?\n/g,`\r
-`)),!r&&h&&a!==h&&l.setData("text/html",h),l.setData("text/plain",a),o.preventDefault(),!0},Qe=function(o){let t=this.getSelection(),e=this._root;if(t.collapsed){o.preventDefault();return}this.saveUndoState(t),je(o,t,e,!0,this._config.willCutCopy,this._config.toPlainText,!1)||setTimeout(()=>{try{this._ensureBottomLine()}catch(i){this._config.didError(i)}},0),this.setSelection(t)},Xe=function(o){je(o,this.getSelection(),this._root,!1,this._config.willCutCopy,this._config.toPlainText,!1)},Ae=function(o){this._isShiftDown=o.shiftKey},$e=function(o){let t=o.clipboardData,e=t?.items,n=this._isShiftDown,i=!1,s=!1,r=null,l=null;if(e){let b=e.length;for(;b--;){let O=e[b],A=O.type;A==="text/html"?l=O:A==="text/plain"||A==="text/uri-list"?r=O:A==="text/rtf"?i=!0:/^image\/.*/.test(A)&&(s=!0)}if(s&&!(i&&l)){o.preventDefault(),this.fireEvent("pasteImage",{clipboardData:t});return}if(!se){o.preventDefault(),l&&(!n||!r)?l.getAsString(O=>{this.insertHTML(O,!0)}):r&&r.getAsString(O=>{let A=!1,Ie=this.getSelection();if(!Ie.collapsed&&I.test(Ie.toString())){let Me=this.linkRegExp.exec(O);A=!!Me&&Me[0].length===O.length}A?this.makeLink(O):this.insertPlainText(O,!0)});return}}let a=t?.types;if(!se&&a&&(De.call(a,"text/html")>-1||!Fe&&De.call(a,"text/plain")>-1&&De.call(a,"text/rtf")<0)){o.preventDefault();let b;!n&&(b=t.getData("text/html"))?this.insertHTML(b,!0):((b=t.getData("text/plain"))||(b=t.getData("text/uri-list")))&&this.insertPlainText(b,!0);return}let d=document.body,c=this.getSelection(),f=c.startContainer,u=c.startOffset,m=c.endContainer,h=c.endOffset,g=p("DIV",{contenteditable:"true",style:"position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;"});d.appendChild(g),c.selectNodeContents(g),this.setSelection(c),setTimeout(()=>{try{let b="",O=g,A;for(;g=O;)O=g.nextSibling,E(g),A=g.firstChild,A&&A===g.lastChild&&A instanceof HTMLDivElement&&(g=A),b+=g.innerHTML;this.setSelection($(f,u,m,h)),b&&this.insertHTML(b,!0)}catch(b){this._config.didError(b)}},0)},Ve=function(o){if(!o.dataTransfer)return;let t=o.dataTransfer.types,e=t.length,n=!1,i=!1;for(;e--;)switch(t[e]){case"text/plain":n=!0;break;case"text/html":i=!0;break;default:return}(i||n&&this.saveUndoState)&&this.saveUndoState()};var we=(o,t,e)=>{t.preventDefault(),o.splitBlock(t.shiftKey,e)};var ne=(o,t)=>{try{t||(t=o.getSelection());let e=t.startContainer;e instanceof Text&&(e=e.parentNode);let n=e;for(;N(n)&&(!n.textContent||n.textContent===B);)e=n,n=e.parentNode;e!==n&&(t.setStart(n,Array.from(n.childNodes).indexOf(e)),t.collapse(!0),n.removeChild(e),P(n)||(n=z(n,o._root)||o._root),x(n),_(t)),e===o._root&&(e=e.firstChild)&&e.nodeName==="BR"&&E(e),o._ensureBottomLine(),o.setSelection(t),o._updatePath(t,!0)}catch(e){o._config.didError(e)}},Ee=(o,t)=>{let e;for(;(e=o.parentNode)&&!(e===t||e.isContentEditable);)o=e;E(o)},Te=(o,t,e)=>{if(S(t,o._root,"A"))return;let n=t.data||"",i=Math.max(n.lastIndexOf(" ",e-1),n.lastIndexOf("\xA0",e-1))+1,s=n.slice(i,e),r=o.linkRegExp.exec(s);if(r){let l=o.getSelection();o._docWasChanged(),o._recordUndoState(l),o._getRangeAndRemoveBookmark(l);let a=i+r.index,d=a+r[0].length,c=l.startContainer===t,f=l.startOffset-d;a&&(t=t.splitText(a));let u=o._config.tagAttributes.a,m=p("A",Object.assign({href:r[1]?/^(?:ht|f)tps?:/i.test(r[1])?r[1]:"http://"+r[1]:"mailto:"+r[0]},u));m.textContent=n.slice(a,d),t.parentNode.insertBefore(m,t),t.data=n.slice(d),c&&(l.setStart(t,f),l.setEnd(t,f)),o.setSelection(l)}};var Ye=(o,t,e)=>{let n=o._root;if(o._removeZWS(),o.saveUndoState(e),!e.collapsed)t.preventDefault(),H(e,n),ne(o,e);else if(X(e,n)){t.preventDefault();let i=L(e,n);if(!i)return;let s=i;D(s.parentNode,n);let r=z(s,n);if(r){if(!r.isContentEditable){Ee(r,n);return}for(Y(r,s,e,n),s=r.parentNode;s!==n&&!s.nextSibling;)s=s.parentNode;s!==n&&(s=s.nextSibling)&&F(s,n),o.setSelection(e)}else if(s){if(S(s,n,"UL")||S(s,n,"OL")){o.decreaseListLevel(e);return}else if(S(s,n,"BLOCKQUOTE")){o.removeQuote(e);return}o.setSelection(e),o._updatePath(e,!0)}}else{_(e);let i=e.startContainer,s=e.startOffset,r=i.parentNode;i instanceof Text&&r instanceof HTMLAnchorElement&&s&&r.href.includes(i.data)?(i.deleteData(s-1,1),o.setSelection(e),o.removeLink(),t.preventDefault()):(o.setSelection(e),setTimeout(()=>{ne(o)},0))}};var Je=(o,t,e)=>{let n=o._root,i,s,r,l,a,d;if(o._removeZWS(),o.saveUndoState(e),!e.collapsed)t.preventDefault(),H(e,n),ne(o,e);else if(Z(e,n)){if(t.preventDefault(),i=L(e,n),!i)return;if(D(i.parentNode,n),s=W(i,n),s){if(!s.isContentEditable){Ee(s,n);return}for(Y(i,s,e,n),s=i.parentNode;s!==n&&!s.nextSibling;)s=s.parentNode;s!==n&&(s=s.nextSibling)&&F(s,n),o.setSelection(e),o._updatePath(e,!0)}}else{if(r=e.cloneRange(),q(e,n,n,n),l=e.endContainer,a=e.endOffset,l instanceof Element&&(d=l.childNodes[a],d&&d.nodeName==="IMG")){t.preventDefault(),E(d),_(e),ne(o,e);return}o.setSelection(r),setTimeout(()=>{ne(o)},0)}};var et=(o,t,e)=>{let n=o._root;if(o._removeZWS(),e.collapsed&&X(e,n)){let i=L(e,n),s;for(;s=i.parentNode;){if(s.nodeName==="UL"||s.nodeName==="OL"){t.preventDefault(),o.increaseListLevel(e);break}i=s}}},tt=(o,t,e)=>{let n=o._root;if(o._removeZWS(),e.collapsed&&X(e,n)){let i=e.startContainer;(S(i,n,"UL")||S(i,n,"OL"))&&(t.preventDefault(),o.decreaseListLevel(e))}};var nt=(o,t,e)=>{let n,i=o._root;if(o._recordUndoState(e),o._getRangeAndRemoveBookmark(e),!e.collapsed)H(e,i),o._ensureBottomLine(),o.setSelection(e),o._updatePath(e,!0);else if(Z(e,i)){let s=L(e,i);if(s&&s.nodeName!=="PRE"){let r=s.textContent?.trimEnd().replace(B,"");if(r==="*"||r==="1."){t.preventDefault(),o.insertPlainText(" ",!1),o._docWasChanged(),o.saveUndoState(e);let l=new T(s,4),a;for(;a=l.nextNode();)E(a);r==="*"?o.makeUnorderedList():o.makeOrderedList();return}}}if(n=e.endContainer,e.endOffset===y(n))do if(n.nodeName==="A"){e.setStartAfter(n);break}while(!n.nextSibling&&(n=n.parentNode)&&n!==i);if(o._config.addLinks){let s=e.cloneRange();_(s);let r=s.startContainer,l=s.startOffset;setTimeout(()=>{Te(o,r,l)},0)}o.setSelection(e)};var ot=function(o){if(o.defaultPrevented||o.isComposing)return;let t=o.key,e="",n=o.code;/^Digit\d$/.test(n)&&(t=n.slice(-1)),t!=="Backspace"&&t!=="Delete"&&(o.altKey&&(e+="Alt-"),o.ctrlKey&&(e+="Ctrl-"),o.metaKey&&(e+="Meta-"),o.shiftKey&&(e+="Shift-")),fe&&o.shiftKey&&t==="Delete"&&(e+="Shift-"),t=e+t;let i=this.getSelection();this._keyHandlers[t]?this._keyHandlers[t](this,o,i):!i.collapsed&&!o.ctrlKey&&!o.metaKey&&t.length===1&&(this.saveUndoState(i),H(i,this._root),this._ensureBottomLine(),this.setSelection(i),this._updatePath(i,!0))},v={Backspace:Ye,Delete:Je,Tab:et,"Shift-Tab":tt," ":nt,ArrowLeft(o){o._removeZWS()},ArrowRight(o,t,e){o._removeZWS();let n=o.getRoot();if(Z(e,n)){_(e);let i=e.endContainer;do if(i.nodeName==="CODE"){let s=i.nextSibling;if(!(s instanceof Text)){let r=document.createTextNode("\xA0");i.parentNode.insertBefore(r,s),s=r}e.setStart(s,1),o.setSelection(e),t.preventDefault();break}while(!i.nextSibling&&(i=i.parentNode)&&i!==n)}}};He||(v.Enter=we,v["Shift-Enter"]=we);!de&&!_e&&(v.PageUp=o=>{o.moveCursorToStart()},v.PageDown=o=>{o.moveCursorToEnd()});var ie=(o,t)=>(t=t||null,(e,n)=>{n.preventDefault();let i=e.getSelection();e.hasFormat(o,null,i)?e.changeFormat(null,{tag:o},i):e.changeFormat({tag:o},t,i)});v[k+"b"]=ie("B");v[k+"i"]=ie("I");v[k+"u"]=ie("U");v[k+"Shift-7"]=ie("S");v[k+"Shift-5"]=ie("SUB",{tag:"SUP"});v[k+"Shift-6"]=ie("SUP",{tag:"SUB"});v[k+"Shift-8"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)UL/.test(e)?o.removeList():o.makeUnorderedList()};v[k+"Shift-9"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)OL/.test(e)?o.removeList():o.makeOrderedList()};v[k+"["]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)BLOCKQUOTE/.test(e)||!/(?:^|>)[OU]L/.test(e)?o.decreaseQuoteLevel():o.decreaseListLevel()};v[k+"]"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)BLOCKQUOTE/.test(e)||!/(?:^|>)[OU]L/.test(e)?o.increaseQuoteLevel():o.increaseListLevel()};v[k+"d"]=(o,t)=>{t.preventDefault(),o.toggleCode()};v[k+"z"]=(o,t)=>{t.preventDefault(),o.undo()};v[k+"y"]=v[k+"Shift-z"]=v[k+"Shift-Z"]=(o,t)=>{t.preventDefault(),o.redo()};var Ce=class{constructor(t,e){this.customEvents=new Set(["pathChange","select","input","pasteImage","undoStateChange"]);this.startSelectionId="squire-selection-start";this.endSelectionId="squire-selection-end";this.linkRegExp=/\b(?:((?:(?:ht|f)tps?:\/\/|www\d{0,3}[.]|[a-z0-9][a-z0-9.\-]*[.][a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:[^\s?&`!()\[\]{};:'".,<>«»“”‘’]|\([^\s()<>]+\)))|([\w\-.%+]+@(?:[\w\-]+\.)+[a-z]{2,}\b(?:[?][^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+(?:&[^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+)*)?))/i;this.tagAfterSplit={DT:"DD",DD:"DT",LI:"LI",PRE:"PRE"};this._root=t,this._config=this._makeConfig(e),this._isFocused=!1,this._lastSelection=$(t,0),this._willRestoreSelection=!1,this._mayHaveZWS=!1,this._lastAnchorNode=null,this._lastFocusNode=null,this._path="",this._events=new Map,this._undoIndex=-1,this._undoStack=[],this._undoStackLength=0,this._isInUndoState=!1,this._ignoreChange=!1,this._ignoreAllChanges=!1,this.addEventListener("selectionchange",this._updatePathOnEvent),this.addEventListener("blur",this._enableRestoreSelection),this.addEventListener("mousedown",this._disableRestoreSelection),this.addEventListener("touchstart",this._disableRestoreSelection),this.addEventListener("focus",this._restoreSelection),this._isShiftDown=!1,this.addEventListener("cut",Qe),this.addEventListener("copy",Xe),this.addEventListener("paste",$e),this.addEventListener("drop",Ve),this.addEventListener("keydown",Ae),this.addEventListener("keyup",Ae),this.addEventListener("keydown",ot),this._keyHandlers=Object.create(v);let n=new MutationObserver(()=>this._docWasChanged());n.observe(t,{childList:!0,attributes:!0,characterData:!0,subtree:!0}),this._mutation=n,t.setAttribute("contenteditable","true"),this.addEventListener("beforeinput",this._beforeInput),this.setHTML("")}destroy(){this._events.forEach((t,e)=>{this.removeEventListener(e)}),this._mutation.disconnect(),this._undoIndex=-1,this._undoStack=[],this._undoStackLength=0}_makeConfig(t){let e={blockTag:"DIV",blockAttributes:null,tagAttributes:{},classNames:{color:"color",fontFamily:"font",fontSize:"size",highlight:"highlight"},undo:{documentSizeThreshold:-1,undoLimit:-1},addLinks:!0,willCutCopy:null,toPlainText:null,sanitizeToDOMFragment:n=>{let i=DOMPurify.sanitize(n,{ALLOW_UNKNOWN_PROTOCOLS:!0,WHOLE_DOCUMENT:!1,RETURN_DOM:!0,RETURN_DOM_FRAGMENT:!0,FORCE_BODY:!1});return i?document.importNode(i,!0):document.createDocumentFragment()},didError:n=>console.log(n)};return t&&(Object.assign(e,t),e.blockTag=e.blockTag.toUpperCase()),e}setKeyHandler(t,e){return this._keyHandlers[t]=e,this}_beforeInput(t){switch(t.inputType){case"insertLineBreak":t.preventDefault(),this.splitBlock(!0);break;case"insertParagraph":t.preventDefault(),this.splitBlock(!1);break;case"insertOrderedList":t.preventDefault(),this.makeOrderedList();break;case"insertUnoderedList":t.preventDefault(),this.makeUnorderedList();break;case"historyUndo":t.preventDefault(),this.undo();break;case"historyRedo":t.preventDefault(),this.redo();break;case"formatBold":t.preventDefault(),this.bold();break;case"formaItalic":t.preventDefault(),this.italic();break;case"formatUnderline":t.preventDefault(),this.underline();break;case"formatStrikeThrough":t.preventDefault(),this.strikethrough();break;case"formatSuperscript":t.preventDefault(),this.superscript();break;case"formatSubscript":t.preventDefault(),this.subscript();break;case"formatJustifyFull":case"formatJustifyCenter":case"formatJustifyRight":case"formatJustifyLeft":{t.preventDefault();let e=t.inputType.slice(13).toLowerCase();e==="full"&&(e="justify"),this.setTextAlignment(e);break}case"formatRemove":t.preventDefault(),this.removeAllFormatting();break;case"formatSetBlockTextDirection":{t.preventDefault();let e=t.data;e==="null"&&(e=null),this.setTextDirection(e);break}case"formatBackColor":t.preventDefault(),this.setHighlightColor(t.data);break;case"formatFontColor":t.preventDefault(),this.setTextColor(t.data);break;case"formatFontName":t.preventDefault(),this.setFontFace(t.data);break}}handleEvent(t){this.fireEvent(t.type,t)}fireEvent(t,e){let n=this._events.get(t);if(/^(?:focus|blur)/.test(t)){let i=this._root===document.activeElement;if(t==="focus"){if(!i||this._isFocused)return this;this._isFocused=!0}else{if(i||!this._isFocused)return this;this._isFocused=!1}}if(n){let i=e instanceof Event?e:new CustomEvent(t,{detail:e});n=n.slice();for(let s of n)try{"handleEvent"in s?s.handleEvent(i):s.call(this,i)}catch(r){this._config.didError(r)}}return this}addEventListener(t,e){let n=this._events.get(t),i=this._root;return n||(n=[],this._events.set(t,n),this.customEvents.has(t)||(t==="selectionchange"&&(i=document),i.addEventListener(t,this,!0))),n.push(e),this}removeEventListener(t,e){let n=this._events.get(t),i=this._root;if(n){if(e){let s=n.length;for(;s--;)n[s]===e&&n.splice(s,1)}else n.length=0;n.length||(this._events.delete(t),this.customEvents.has(t)||(t==="selectionchange"&&(i=document),i.removeEventListener(t,this,!0)))}return this}focus(){return this._root.focus({preventScroll:!0}),this}blur(){return this._root.blur(),this}_enableRestoreSelection(){this._willRestoreSelection=!0}_disableRestoreSelection(){this._willRestoreSelection=!1}_restoreSelection(){this._willRestoreSelection&&this.setSelection(this._lastSelection)}_removeZWS(){this._mayHaveZWS&&(re(this._root),this._mayHaveZWS=!1)}_saveRangeToBookmark(t){let e=p("INPUT",{id:this.startSelectionId,type:"hidden"}),n=p("INPUT",{id:this.endSelectionId,type:"hidden"}),i;j(t,e),t.collapse(!1),j(t,n),e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING&&(e.id=this.endSelectionId,n.id=this.startSelectionId,i=e,e=n,n=i),t.setStartAfter(e),t.setEndBefore(n)}_getRangeAndRemoveBookmark(t){let e=this._root,n=e.querySelector("#"+this.startSelectionId),i=e.querySelector("#"+this.endSelectionId);if(n&&i){let s=n.parentNode,r=i.parentNode,l=Array.from(s.childNodes).indexOf(n),a=Array.from(r.childNodes).indexOf(i);s===r&&(a-=1),n.remove(),i.remove(),t||(t=document.createRange()),t.setStart(s,l),t.setEnd(r,a),te(s,t),s!==r&&te(r,t),t.collapsed&&(s=t.startContainer,s instanceof Text&&(r=s.childNodes[t.startOffset],(!r||!(r instanceof Text))&&(r=s.childNodes[t.startOffset-1]),r&&r instanceof Text&&(t.setStart(r,0),t.collapse(!0))))}return t||null}getSelection(){let t=window.getSelection(),e=this._root,n=null;if(this._isFocused&&t&&t.rangeCount){n=t.getRangeAt(0).cloneRange();let i=n.startContainer,s=n.endContainer;i&&M(i)&&n.setStartBefore(i),s&&M(s)&&n.setEndBefore(s)}return n&&e.contains(n.commonAncestorContainer)?this._lastSelection=n:(n=this._lastSelection,document.contains(n.commonAncestorContainer)||(n=null)),n||(n=$(e.firstElementChild||e,0)),n}setSelection(t){if(this._lastSelection=t,!this._isFocused)this._enableRestoreSelection();else{let e=window.getSelection();e&&("setBaseAndExtent"in Selection.prototype?e.setBaseAndExtent(t.startContainer,t.startOffset,t.endContainer,t.endOffset):(e.removeAllRanges(),e.addRange(t)))}return this}_moveCursorTo(t){let e=this._root,n=$(e,t?0:e.childNodes.length);return _(n),this.setSelection(n),this}moveCursorToStart(){return this._moveCursorTo(!0)}moveCursorToEnd(){return this._moveCursorTo(!1)}getCursorPosition(){let t=this.getSelection(),e=t.getBoundingClientRect();if(e&&!e.top){this._ignoreChange=!0;let n=p("SPAN");n.textContent=B,j(t,n),e=n.getBoundingClientRect();let i=n.parentNode;i.removeChild(n),te(i,t)}return e}getPath(){return this._path}_updatePathOnEvent(){this._isFocused&&this._updatePath(this.getSelection())}_updatePath(t,e){let n=t.startContainer,i=t.endContainer,s;(e||n!==this._lastAnchorNode||i!==this._lastFocusNode)&&(this._lastAnchorNode=n,this._lastFocusNode=i,s=n&&i?n===i?this._getPath(i):"(selection)":"",this._path!==s&&(this._path=s,this.fireEvent("pathChange",{path:s}))),this.fireEvent(t.collapsed?"cursor":"select",{range:t})}_getPath(t){let e=this._root,n=this._config,i="";if(t&&t!==e){let s=t.parentNode;if(i=s?this._getPath(s):"",t instanceof HTMLElement){let r=t.id,l=t.classList,a=Array.from(l).sort(),d=t.dir,c=n.classNames;i+=(i?">":"")+t.nodeName,r&&(i+="#"+r),a.length&&(i+=".",i+=a.join(".")),d&&(i+="[dir="+d+"]"),l.contains(c.highlight)&&(i+="[backgroundColor="+t.style.backgroundColor.replace(/ /g,"")+"]"),l.contains(c.color)&&(i+="[color="+t.style.color.replace(/ /g,"")+"]"),l.contains(c.fontFamily)&&(i+="[fontFamily="+t.style.fontFamily.replace(/ /g,"")+"]"),l.contains(c.fontSize)&&(i+="[fontSize="+t.style.fontSize+"]")}}return i}modifyDocument(t){let e=this._mutation;return e&&(e.takeRecords().length&&this._docWasChanged(),e.disconnect()),this._ignoreAllChanges=!0,t(),this._ignoreAllChanges=!1,e&&(e.observe(this._root,{childList:!0,attributes:!0,characterData:!0,subtree:!0}),this._ignoreChange=!1),this}_docWasChanged(){if(qe(),this._mayHaveZWS=!0,!this._ignoreAllChanges){if(this._ignoreChange){this._ignoreChange=!1;return}this._isInUndoState&&(this._isInUndoState=!1,this.fireEvent("undoStateChange",{canUndo:!0,canRedo:!1})),this.fireEvent("input")}}_recordUndoState(t,e){let n=this._isInUndoState;if(!n||e){let i=this._undoIndex+1,s=this._undoStack,r=this._config.undo,l=r.documentSizeThreshold,a=r.undoLimit;if(i<this._undoStackLength&&(s.length=this._undoStackLength=i),t&&this._saveRangeToBookmark(t),n)return this;let d=this._getRawHTML();e&&(i-=1),l>-1&&d.length*2>l&&a>-1&&i>a&&(s.splice(0,i-a),i=a,this._undoStackLength=a),s[i]=d,this._undoIndex=i,this._undoStackLength+=1,this._isInUndoState=!0}return this}saveUndoState(t){return t||(t=this.getSelection()),this._recordUndoState(t,this._isInUndoState),this._getRangeAndRemoveBookmark(t),this}undo(){if(this._undoIndex!==0||!this._isInUndoState){this._recordUndoState(this.getSelection(),!1),this._undoIndex-=1,this._setRawHTML(this._undoStack[this._undoIndex]);let t=this._getRangeAndRemoveBookmark();t&&this.setSelection(t),this._isInUndoState=!0,this.fireEvent("undoStateChange",{canUndo:this._undoIndex!==0,canRedo:!0}),this.fireEvent("input")}return this.focus()}redo(){let t=this._undoIndex,e=this._undoStackLength;if(t+1<e&&this._isInUndoState){this._undoIndex+=1,this._setRawHTML(this._undoStack[this._undoIndex]);let n=this._getRangeAndRemoveBookmark();n&&this.setSelection(n),this.fireEvent("undoStateChange",{canUndo:!0,canRedo:t+2<e}),this.fireEvent("input")}return this.focus()}getRoot(){return this._root}_getRawHTML(){return this._root.innerHTML}_setRawHTML(t){let e=this._root;e.innerHTML=t;let n=e,i=n.firstChild;if(!i||i.nodeName==="BR"){let s=this.createDefaultBlock();i?n.replaceChild(s,i):n.appendChild(s)}else for(;n=W(n,e);)x(n);return this._ignoreChange=!0,this}getHTML(t){let e;t&&(e=this.getSelection(),this._saveRangeToBookmark(e));let n=this._getRawHTML().replace(/\u200B/g,"");return t&&this._getRangeAndRemoveBookmark(e),n}setHTML(t){let e=this._config.sanitizeToDOMFragment(t,this),n=this._root;Ne(e,this._config),le(e,n,!1),D(e,n);let i=e,s=i.firstChild;if(!s||s.nodeName==="BR"){let l=this.createDefaultBlock();s?i.replaceChild(l,s):i.appendChild(l)}else for(;i=W(i,n);)x(i);for(this._ignoreChange=!0;s=n.lastChild;)n.removeChild(s);n.appendChild(e),this._undoIndex=-1,this._undoStack.length=0,this._undoStackLength=0,this._isInUndoState=!1;let r=this._getRangeAndRemoveBookmark()||$(n.firstElementChild||n,0);return this.saveUndoState(r),this.setSelection(r),this._updatePath(r,!0),this}insertHTML(t,e){let n=this._config,i=n.sanitizeToDOMFragment(t,this),s=this.getSelection();this.saveUndoState(s);try{let r=this._root;n.addLinks&&this.addDetectedLinks(i,i),Ne(i,this._config),le(i,r,!1),Se(i),i.normalize();let l=i;for(;l=W(l,i);)x(l);let a=!0;if(e){let d=new CustomEvent("willPaste",{cancelable:!0,detail:{fragment:i}});this.fireEvent("willPaste",d),i=d.detail.fragment,a=!d.defaultPrevented}a&&(Ze(s,i,r),s.collapse(!1),Oe(s,"A",r),this._ensureBottomLine()),this.setSelection(s),this._updatePath(s,!0),e&&this.focus()}catch(r){this._config.didError(r)}return this}insertElement(t,e){if(e||(e=this.getSelection()),e.collapse(!0),N(t))j(e,t),e.setStartAfter(t);else{let n=this._root,i=L(e,n),s=i||n,r=null;for(;s!==n&&!s.nextSibling;)s=s.parentNode;if(s!==n){let a=s.parentNode;r=w(a,s.nextSibling,n,n)}i&&ce(i)&&E(i),n.insertBefore(t,r);let l=this.createDefaultBlock();n.insertBefore(l,r),e.setStart(l,0),e.setEnd(l,0),_(e)}return this.focus(),this.setSelection(e),this._updatePath(e),this}insertImage(t,e){let n=p("IMG",Object.assign({src:t},e));return this.insertElement(n),n}insertPlainText(t,e){let n=this.getSelection();if(n.collapsed&&S(n.startContainer,this._root,"PRE")){let c=n.startContainer,f=n.startOffset,u;if(!c||!(c instanceof Text)){let h=document.createTextNode("");c.insertBefore(h,c.childNodes[f]),u=h,f=0}else u=c;let m=!0;if(e){let h=new CustomEvent("willPaste",{cancelable:!0,detail:{text:t}});this.fireEvent("willPaste",h),t=h.detail.text,m=!h.defaultPrevented}return m&&(u.insertData(f,t),n.setStart(u,f+t.length),n.collapse(!0)),this.setSelection(n),this}let i=t.split(`
-`),s=this._config,r=s.blockTag,l=s.blockAttributes,a="</"+r+">",d="<"+r;for(let c in l)d+=" "+c+'="'+ke(l[c])+'"';d+=">";for(let c=0,f=i.length;c<f;c+=1){let u=i[c];u=ke(u).replace(/ (?=(?: |$))/g,"&nbsp;"),c&&(u=d+(u||"<BR>")+a),i[c]=u}return this.insertHTML(i.join(""),e)}getSelectedText(t){return ge(t||this.getSelection())}getFontInfo(t){let e={color:void 0,backgroundColor:void 0,fontFamily:void 0,fontSize:void 0};t||(t=this.getSelection());let n=0,i=t.commonAncestorContainer;if(t.collapsed||i instanceof Text)for(i instanceof Text&&(i=i.parentNode);n<4&&i;){let s=i.style;if(s){let r=s.color;!e.color&&r&&(e.color=r,n+=1);let l=s.backgroundColor;!e.backgroundColor&&l&&(e.backgroundColor=l,n+=1);let a=s.fontFamily;!e.fontFamily&&a&&(e.fontFamily=a,n+=1);let d=s.fontSize;!e.fontSize&&d&&(e.fontSize=d,n+=1)}i=i.parentNode}return e}hasFormat(t,e,n){t=t.toUpperCase(),e||(e={}),n||(n=this.getSelection()),!n.collapsed&&n.startContainer instanceof Text&&n.startOffset===n.startContainer.length&&n.startContainer.nextSibling&&n.setStartBefore(n.startContainer.nextSibling),!n.collapsed&&n.endContainer instanceof Text&&n.endOffset===0&&n.endContainer.previousSibling&&n.setEndAfter(n.endContainer.previousSibling);let i=this._root,s=n.commonAncestorContainer;if(S(s,i,t,e))return!0;if(s instanceof Text)return!1;let r=new T(s,4,d=>K(n,d,!0)),l=!1,a;for(;a=r.nextNode();){if(!S(a,i,t,e))return!1;l=!0}return l}changeFormat(t,e,n,i){return n||(n=this.getSelection()),this.saveUndoState(n),e&&(n=this._removeFormat(e.tag.toUpperCase(),e.attributes||{},n,i)),t&&(n=this._addFormat(t.tag.toUpperCase(),t.attributes||{},n)),this.setSelection(n),this._updatePath(n,!0),this.focus()}_addFormat(t,e,n){let i=this._root;if(n.collapsed){let s=x(p(t,e));j(n,s);let r=s.firstChild||s,l=r instanceof Text?r.length:0;n.setStart(r,l),n.collapse(!0);let a=s;for(;N(a);)a=a.parentNode;re(a,s)}else{let s=new T(n.commonAncestorContainer,5,c=>(c instanceof Text||c.nodeName==="BR"||c.nodeName==="IMG")&&K(n,c,!0)),{startContainer:r,startOffset:l,endContainer:a,endOffset:d}=n;if(s.currentNode=r,!(r instanceof Element)&&!(r instanceof Text)||!s.filter(r)){let c=s.nextNode();if(!c)return n;r=c,l=0}do{let c=s.currentNode;if(!S(c,i,t,e)){c===a&&c.length>d&&c.splitText(d),c===r&&l&&(c=c.splitText(l),a===r?(a=c,d-=l):a===r.parentNode&&(d+=1),r=c,l=0);let u=p(t,e);R(c,u),u.appendChild(c)}}while(s.nextNode());n=$(r,l,a,d)}return n}_removeFormat(t,e,n,i){this._saveRangeToBookmark(n);let s;n.collapsed&&(oe?s=document.createTextNode(B):s=document.createTextNode(""),j(n,s));let r=n.commonAncestorContainer;for(;N(r);)r=r.parentNode;let l=n.startContainer,a=n.startOffset,d=n.endContainer,c=n.endOffset,f=[],u=(h,g)=>{if(K(n,h,!1))return;let b,O;if(!K(n,h,!0)){!(h instanceof HTMLInputElement)&&(!(h instanceof Text)||h.data)&&f.push([g,h]);return}if(h instanceof Text)h===d&&c!==h.length&&f.push([g,h.splitText(c)]),h===l&&a&&(h.splitText(a),f.push([g,h]));else for(b=h.firstChild;b;b=O)O=b.nextSibling,u(b,g)},m=Array.from(r.getElementsByTagName(t)).filter(h=>K(n,h,!0)&&he(h,t,e));if(i||m.forEach(h=>{u(h,h)}),f.forEach(([h,g])=>{h=h.cloneNode(!1),R(g,h),h.appendChild(g)}),m.forEach(h=>{R(h,C(h))}),oe&&s){s=s.parentNode;let h=s;for(;h&&N(h);)h=h.parentNode;h&&re(h,s)}return this._getRangeAndRemoveBookmark(n),s&&n.collapse(!1),te(r,n),n}bold(){return this.changeFormat({tag:"B"})}removeBold(){return this.changeFormat(null,{tag:"B"})}italic(){return this.changeFormat({tag:"I"})}removeItalic(){return this.changeFormat(null,{tag:"I"})}underline(){return this.changeFormat({tag:"U"})}removeUnderline(){return this.changeFormat(null,{tag:"U"})}strikethrough(){return this.changeFormat({tag:"S"})}removeStrikethrough(){return this.changeFormat(null,{tag:"S"})}subscript(){return this.changeFormat({tag:"SUB"},{tag:"SUP"})}removeSubscript(){return this.changeFormat(null,{tag:"SUB"})}superscript(){return this.changeFormat({tag:"SUP"},{tag:"SUB"})}removeSuperscript(){return this.changeFormat(null,{tag:"SUP"})}makeLink(t,e){let n=this.getSelection();if(n.collapsed){let i=t.indexOf(":")+1;if(i)for(;t[i]==="/";)i+=1;j(n,document.createTextNode(t.slice(i)))}return e=Object.assign({href:t},this._config.tagAttributes.a,e),this.changeFormat({tag:"A",attributes:e},{tag:"A"},n)}removeLink(){return this.changeFormat(null,{tag:"A"},this.getSelection(),!0)}addDetectedLinks(t,e){let n=new T(t,4,l=>!S(l,e||this._root,"A")),i=this.linkRegExp,s=this._config.tagAttributes.a,r;for(;r=n.nextNode();){let l=r.parentNode,a=r.data,d;for(;d=i.exec(a);){let c=d.index,f=c+d[0].length;c&&l.insertBefore(document.createTextNode(a.slice(0,c)),r);let u=p("A",Object.assign({href:d[1]?/^(?:ht|f)tps?:/i.test(d[1])?d[1]:"http://"+d[1]:"mailto:"+d[0]},s));u.textContent=a.slice(c,f),l.insertBefore(u,r),r.data=a=a.slice(f)}}return this}setFontFace(t){let e=this._config.classNames.fontFamily;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"font-family: "+t+", sans-serif;"}}:null,{tag:"SPAN",attributes:{class:e}})}setFontSize(t){let e=this._config.classNames.fontSize;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"font-size: "+(typeof t=="number"?t+"px":t)}}:null,{tag:"SPAN",attributes:{class:e}})}setTextColor(t){let e=this._config.classNames.color;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"color:"+t}}:null,{tag:"SPAN",attributes:{class:e}})}setHighlightColor(t){let e=this._config.classNames.highlight;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"background-color:"+t}}:null,{tag:"SPAN",attributes:{class:e}})}_ensureBottomLine(){let t=this._root,e=t.lastElementChild;(!e||e.nodeName!==this._config.blockTag||!P(e))&&t.appendChild(this.createDefaultBlock())}createDefaultBlock(t){let e=this._config;return x(p(e.blockTag,e.blockAttributes,t))}splitBlock(t,e){e||(e=this.getSelection());let n=this._root,i,s,r,l;if(this._recordUndoState(e),this._removeZWS(),this._getRangeAndRemoveBookmark(e),e.collapsed||H(e,n),this._config.addLinks){_(e);let u=e.startContainer,m=e.startOffset;setTimeout(()=>{Te(this,u,m)},0)}if(i=L(e,n),i&&(s=S(i,n,"PRE"))){_(e),r=e.startContainer;let u=e.startOffset;return r instanceof Text||(r=document.createTextNode(""),s.insertBefore(r,s.firstChild)),!t&&r instanceof Text&&(r.data.charAt(u-1)===`
+var it=()=>!0,C=class{constructor(t,e,n){this.root=t,this.currentNode=t,this.nodeType=e,this.filter=n||it}isAcceptableNode(t){let e=t.nodeType;return!!((e===Node.ELEMENT_NODE?1:e===Node.TEXT_NODE?4:0)&this.nodeType)&&this.filter(t)}nextNode(){let t=this.root,e=this.currentNode,n;for(;;){for(n=e.firstChild;!n&&e&&e!==t;)n=e.nextSibling,n||(e=e.parentNode);if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}previousNode(){let t=this.root,e=this.currentNode,n;for(;;){if(e===t)return null;if(n=e.previousSibling,n)for(;e=n.lastChild;)n=e;else n=e.parentNode;if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}previousPONode(){let t=this.root,e=this.currentNode,n;for(;;){for(n=e.lastChild;!n&&e&&e!==t;)n=e.previousSibling,n||(e=e.parentNode);if(!n)return null;if(this.isAcceptableNode(n))return this.currentNode=n,n;e=n}}};var B="\u200B",J=navigator.userAgent,de=/Mac OS X/.test(J),fe=/Windows NT/.test(J),_e=/iP(?:ad|hone|od)/.test(J)||de&&!!navigator.maxTouchPoints,xt=/Android/.test(J),Fe=/Gecko\//.test(J),se=/Edge\//.test(J),st=!se&&/WebKit\//.test(J),k=de||_e?"Meta-":"Ctrl-",oe=st,He="onbeforeinput"in document&&"inputType"in new InputEvent("input"),I=/[^ \t\r\n]/;var at=/^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/,ct=new Set(["BR","HR","IFRAME","IMG","INPUT"]),dt=0,xe=1,Pe=2,Ue=3,ue=new WeakMap,qe=()=>{ue=new WeakMap},M=o=>ct.has(o.nodeName),be=o=>{switch(o.nodeType){case 3:return xe;case 1:case 11:if(ue.has(o))return ue.get(o);break;default:return dt}let t;return Array.from(o.childNodes).every(N)?at.test(o.nodeName)?t=xe:t=Pe:t=Ue,ue.set(o,t),t},N=o=>be(o)===xe,P=o=>be(o)===Pe,Q=o=>be(o)===Ue;var p=(o,t,e)=>{let n=document.createElement(o);if(t instanceof Array&&(e=t,t=null),t)for(let i in t){let s=t[i];s!==void 0&&n.setAttribute(i,s)}return e&&e.forEach(i=>n.appendChild(i)),n},Le=(o,t)=>M(o)||o.nodeType!==t.nodeType||o.nodeName!==t.nodeName?!1:o instanceof HTMLElement&&t instanceof HTMLElement?o.nodeName!=="A"&&o.className===t.className&&o.style.cssText===t.style.cssText:!0,he=(o,t,e)=>{if(o.nodeName!==t)return!1;for(let n in e)if(!("getAttribute"in o)||o.getAttribute(n)!==e[n])return!1;return!0},S=(o,t,e,n)=>{for(;o&&o!==t;){if(he(o,e,n))return o;o=o.parentNode}return null},me=(o,t)=>{let e=o.childNodes;for(;t&&o instanceof Element;)o=e[t-1],e=o.childNodes,t=e.length;return o},Re=(o,t)=>{let e=o;if(e instanceof Element){let n=e.childNodes;if(t<n.length)e=n[t];else{for(;e&&!e.nextSibling;)e=e.parentNode;e&&(e=e.nextSibling)}}return e},y=o=>o instanceof Element||o instanceof DocumentFragment?o.childNodes.length:o instanceof CharacterData?o.length:0,_=o=>{let t=document.createDocumentFragment(),e=o.firstChild;for(;e;)t.appendChild(e),e=o.firstChild;return t},E=o=>{let t=o.parentNode;return t&&t.removeChild(o),o},R=(o,t)=>{let e=o.parentNode;e&&e.replaceChild(t,o)};var ft=o=>o instanceof Element?o.nodeName==="BR":I.test(o.data),ee=(o,t)=>{let e=o.parentNode;for(;N(e);)e=e.parentNode;let n=new C(e,5,ft);return n.currentNode=o,!!n.nextNode()||t&&!n.previousNode()},re=(o,t)=>{let e=new C(o,4),n,i;for(;n=e.nextNode();)for(;(i=n.data.indexOf(B))>-1&&(!t||n.parentNode!==t);)if(n.length===1){let s=n,r=s.parentNode;for(;r&&(r.removeChild(s),e.currentNode=r,!(!N(r)||y(r)));)s=r,r=s.parentNode;break}else n.deleteData(i,1)};var ut=0,ht=1,mt=2,pt=3,K=(o,t,e)=>{let n=document.createRange();if(n.selectNode(t),e){let i=o.compareBoundaryPoints(pt,n)>-1,s=o.compareBoundaryPoints(ht,n)<1;return!i&&!s}else{let i=o.compareBoundaryPoints(ut,n)<1,s=o.compareBoundaryPoints(mt,n)>-1;return i&&s}},T=o=>{let{startContainer:t,startOffset:e,endContainer:n,endOffset:i}=o;for(;!(t instanceof Text);){let s=t.childNodes[e];if(!s||M(s)){if(e&&(s=t.childNodes[e-1],s instanceof Text)){let r=s,l;for(;!r.length&&(l=r.previousSibling)&&l instanceof Text;)r.remove(),r=l;t=r,e=r.data.length}break}t=s,e=0}if(i)for(;!(n instanceof Text);){let s=n.childNodes[i-1];if(!s||M(s)){if(s&&s.nodeName==="BR"&&!ee(s,!1)){i-=1;continue}break}n=s,i=y(n)}else for(;!(n instanceof Text);){let s=n.firstChild;if(!s||M(s))break;n=s}o.setStart(t,e),o.setEnd(n,i)},q=(o,t,e,n)=>{let i=o.startContainer,s=o.startOffset,r=o.endContainer,l=o.endOffset,a;for(t||(t=o.commonAncestorContainer),e||(e=t);!s&&i!==t&&i!==n;)a=i.parentNode,s=Array.from(a.childNodes).indexOf(i),i=a;for(;!(r===e||r===n||(r.nodeType!==3&&r.childNodes[l]&&r.childNodes[l].nodeName==="BR"&&!ee(r.childNodes[l],!1)&&(l+=1),l!==y(r)));)a=r.parentNode,l=Array.from(a.childNodes).indexOf(r)+1,r=a;o.setStart(i,s),o.setEnd(r,l)},Oe=(o,t,e)=>{let n=S(o.endContainer,e,t);if(n&&(n=n.parentNode)){let i=o.cloneRange();q(i,n,n,e),i.endContainer===n&&(o.setStart(i.endContainer,i.endOffset),o.setEnd(i.endContainer,i.endOffset))}return o};var x=o=>{let t=null;if(o instanceof Text)return o;if(N(o)){let e=o.firstChild;if(oe)for(;e&&e instanceof Text&&!e.data;)o.removeChild(e),e=o.firstChild;e||(oe?t=document.createTextNode(B):t=document.createTextNode(""))}else if((o instanceof Element||o instanceof DocumentFragment)&&!o.querySelector("BR")){t=p("BR");let e=o,n;for(;(n=e.lastElementChild)&&!N(n);)e=n;o=e}if(t)try{o.appendChild(t)}catch{}return o},D=(o,t)=>{let e=null;return Array.from(o.childNodes).forEach(n=>{let i=n.nodeName==="BR";!i&&N(n)?(e||(e=p("DIV")),e.appendChild(n)):(i||e)&&(e||(e=p("DIV")),x(e),i?o.replaceChild(e,n):o.insertBefore(e,n),e=null),Q(n)&&D(n,t)}),e&&o.appendChild(x(e)),o},w=(o,t,e,n)=>{if(o instanceof Text&&o!==e){if(typeof t!="number")throw new Error("Offset must be a number to split text node!");if(!o.parentNode)throw new Error("Cannot split text node with no parent!");return w(o.parentNode,o.splitText(t),e,n)}let i=typeof t=="number"?t<o.childNodes.length?o.childNodes[t]:null:t,s=o.parentNode;if(!s||o===e||!(o instanceof Element))return i;let r=o.cloneNode(!1);for(;i;){let l=i.nextSibling;r.appendChild(i),i=l}return o instanceof HTMLOListElement&&S(o,n,"BLOCKQUOTE")&&(r.start=(+o.start||1)+o.childNodes.length-1),x(o),x(r),s.insertBefore(r,o.nextSibling),w(s,r,e,n)},We=(o,t)=>{let e=o.childNodes,n=e.length,i=[];for(;n--;){let s=e[n],r=n?e[n-1]:null;if(r&&N(s)&&Le(s,r))t.startContainer===s&&(t.startContainer=r,t.startOffset+=y(r)),t.endContainer===s&&(t.endContainer=r,t.endOffset+=y(r)),t.startContainer===o&&(t.startOffset>n?t.startOffset-=1:t.startOffset===n&&(t.startContainer=r,t.startOffset=y(r))),t.endContainer===o&&(t.endOffset>n?t.endOffset-=1:t.endOffset===n&&(t.endContainer=r,t.endOffset=y(r))),E(s),s instanceof Text?r.appendData(s.data):i.push(_(s));else if(s instanceof Element){let l;for(;l=i.pop();)s.appendChild(l);We(s,t)}}},te=(o,t)=>{let e=o instanceof Text?o.parentNode:o;if(e instanceof Element){let n={startContainer:t.startContainer,startOffset:t.startOffset,endContainer:t.endContainer,endOffset:t.endOffset};We(e,n),t.setStart(n.startContainer,n.startOffset),t.setEnd(n.endContainer,n.endOffset)}},Y=(o,t,e,n)=>{let i=t,s,r;for(;(s=i.parentNode)&&s!==n&&s instanceof Element&&s.childNodes.length===1;)i=s;E(i),r=o.childNodes.length;let l=o.lastChild;l&&l.nodeName==="BR"&&(o.removeChild(l),r-=1),o.appendChild(_(t)),e.setStart(o,r),e.collapse(!0),te(o,e)},F=(o,t)=>{let e=o.previousSibling,n=o.firstChild,i=o.nodeName==="LI";if(!(i&&(!n||!/^[OU]L$/.test(n.nodeName)))){if(e&&Le(e,o)){if(!Q(e))if(i){let r=p("DIV");r.appendChild(_(e)),e.appendChild(r)}else return;E(o);let s=!Q(o);e.appendChild(_(o)),s&&D(e,t),n&&F(n,t)}else if(i){let s=p("DIV");o.insertBefore(s,n),x(s)}}};var Ke={"font-weight":{regexp:/^bold|^700/i,replace(){return p("B")}},"font-style":{regexp:/^italic/i,replace(){return p("I")}},"font-family":{regexp:I,replace(o,t){return p("SPAN",{class:o.fontFamily,style:"font-family:"+t})}},"font-size":{regexp:I,replace(o,t){return p("SPAN",{class:o.fontSize,style:"font-size:"+t})}},"text-decoration":{regexp:/^underline/i,replace(){return p("U")}}},Nt=(o,t,e)=>{let n=o.style,i,s;for(let r in Ke){let l=Ke[r],a=n.getPropertyValue(r);if(a&&l.regexp.test(a)){let d=l.replace(e.classNames,a);if(d.nodeName===o.nodeName&&d.className===o.className)continue;s||(s=d),i&&i.appendChild(d),i=d,o.style.removeProperty(r)}}return s&&i&&(i.appendChild(_(o)),o.style.cssText?o.appendChild(s):R(o,s)),i||o},pe=o=>(t,e)=>{let n=p(o),i=t.attributes;for(let s=0,r=i.length;s<r;s+=1){let l=i[s];n.setAttribute(l.name,l.value)}return e.replaceChild(n,t),n.appendChild(_(t)),n},St={1:"10",2:"13",3:"16",4:"18",5:"24",6:"32",7:"48"},gt={STRONG:pe("B"),EM:pe("I"),INS:pe("U"),STRIKE:pe("S"),SPAN:Nt,FONT:(o,t,e)=>{let n=o,i=n.face,s=n.size,r=n.color,l=e.classNames,a,d,c,f,u;return i&&(a=p("SPAN",{class:l.fontFamily,style:"font-family:"+i}),u=a,f=a),s&&(d=p("SPAN",{class:l.fontSize,style:"font-size:"+St[s]+"px"}),u||(u=d),f&&f.appendChild(d),f=d),r&&/^#?([\dA-F]{3}){1,2}$/i.test(r)&&(r.charAt(0)!=="#"&&(r="#"+r),c=p("SPAN",{class:l.color,style:"color:"+r}),u||(u=c),f&&f.appendChild(c),f=c),(!u||!f)&&(u=f=p("SPAN")),t.replaceChild(u,n),f.appendChild(_(n)),f},TT:(o,t,e)=>{let n=p("SPAN",{class:e.classNames.fontFamily,style:'font-family:menlo,consolas,"courier new",monospace'});return t.replaceChild(n,o),n.appendChild(_(o)),n}},Et=/^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/,Tt=/^(?:HEAD|META|STYLE)/,Ne=(o,t,e)=>{let n=o.childNodes,i=o;for(;N(i);)i=i.parentNode;let s=new C(i,5);for(let r=0,l=n.length;r<l;r+=1){let a=n[r],d=a.nodeName,c=gt[d];if(a instanceof HTMLElement){let f=a.childNodes.length;if(c)a=c(a,o,t);else if(Tt.test(d)){o.removeChild(a),r-=1,l-=1;continue}else if(!Et.test(d)&&!N(a)){r-=1,l+=f-1,o.replaceChild(_(a),a);continue}f&&Ne(a,t,e||d==="PRE")}else{if(a instanceof Text){let f=a.data,u=!I.test(f.charAt(0)),m=!I.test(f.charAt(f.length-1));if(e||!u&&!m)continue;if(u){s.currentNode=a;let h;for(;(h=s.previousPONode())&&!(h.nodeName==="IMG"||h instanceof Text&&I.test(h.data));)if(!N(h)){h=null;break}f=f.replace(/^[ \t\r\n]+/g,h?" ":"")}if(m){s.currentNode=a;let h;for(;(h=s.nextNode())&&!(h.nodeName==="IMG"||h instanceof Text&&I.test(h.data));)if(!N(h)){h=null;break}f=f.replace(/[ \t\r\n]+$/g,h?" ":"")}if(f){a.data=f;continue}}o.removeChild(a),r-=1,l-=1}}return o},Se=o=>{let t=o.childNodes,e=t.length;for(;e--;){let n=t[e];n instanceof Element&&!M(n)?(Se(n),N(n)&&!n.firstChild&&o.removeChild(n)):n instanceof Text&&!n.data&&o.removeChild(n)}},le=(o,t,e)=>{let n=o.querySelectorAll("BR"),i=[],s=n.length;for(let r=0;r<s;r+=1)i[r]=ee(n[r],e);for(;s--;){let r=n[s],l=r.parentNode;l&&(i[s]?N(l)||D(l,t):E(r))}},ke=o=>o.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;").split('"').join("&quot;");var ae=(o,t)=>{let e=new C(t,1,P);return e.currentNode=o,e},z=(o,t)=>{let e=ae(o,t).previousNode();return e!==t?e:null},W=(o,t)=>{let e=ae(o,t).nextNode();return e!==t?e:null},ce=o=>!o.textContent&&!o.querySelector("IMG");var L=(o,t)=>{let e=o.startContainer,n;if(N(e))n=z(e,t);else if(e!==t&&e instanceof HTMLElement&&P(e))n=e;else{let i=me(e,o.startOffset);n=W(i,t)}return n&&K(o,n,!0)?n:null},G=(o,t)=>{let e=o.endContainer,n;if(N(e))n=z(e,t);else if(e!==t&&e instanceof HTMLElement&&P(e))n=e;else{let i=Re(e,o.endOffset);if(!i||!t.contains(i)){i=t;let s;for(;s=i.lastChild;)i=s}n=z(i,t)}return n&&K(o,n,!0)?n:null},ze=o=>o instanceof Text?I.test(o.data):o.nodeName==="IMG",X=(o,t)=>{let e=o.startContainer,n=o.startOffset,i;if(e instanceof Text){let l=e.data;for(let a=n;a>0;a-=1)if(l.charAt(a-1)!==B)return!1;i=e}else if(i=Re(e,n),i&&!t.contains(i)&&(i=null),!i&&(i=me(e,n),i instanceof Text&&i.length))return!1;let s=L(o,t);if(!s)return!1;let r=new C(s,5,ze);return r.currentNode=i,!r.previousNode()},Z=(o,t)=>{let e=o.endContainer,n=o.endOffset,i;if(e instanceof Text){let l=e.data,a=l.length;for(let d=n;d<a;d+=1)if(l.charAt(d)!==B)return!1;i=e}else i=me(e,n);let s=G(o,t);if(!s)return!1;let r=new C(s,5,ze);return r.currentNode=i,!r.nextNode()},ye=(o,t)=>{let e=L(o,t),n=G(o,t),i;e&&n&&(i=e.parentNode,o.setStart(i,Array.from(i.childNodes).indexOf(e)),i=n.parentNode,o.setEnd(i,Array.from(i.childNodes).indexOf(n)+1))};function $(o,t,e,n){let i=document.createRange();return i.setStart(o,t),e&&typeof n=="number"?i.setEnd(e,n):i.setEnd(o,t),i}var j=(o,t)=>{let{startContainer:e,startOffset:n,endContainer:i,endOffset:s}=o,r;if(e instanceof Text){let a=e.parentNode;if(r=a.childNodes,n===e.length)n=Array.from(r).indexOf(e)+1,o.collapsed&&(i=a,s=n);else{if(n){let d=e.splitText(n);i===e?(s-=n,i=d):i===a&&(s+=1),e=d}n=Array.from(r).indexOf(e)}e=a}else r=e.childNodes;let l=r.length;n===l?e.appendChild(t):e.insertBefore(t,r[n]),e===i&&(s+=r.length-l),o.setStart(e,n),o.setEnd(i,s)},Be=(o,t,e)=>{let n=document.createDocumentFragment();if(o.collapsed)return n;t||(t=o.commonAncestorContainer),t instanceof Text&&(t=t.parentNode);let i=o.startContainer,s=o.startOffset,r=w(o.endContainer,o.endOffset,t,e),l=0,a=w(i,s,t,e);for(;a&&a!==r;){let d=a.nextSibling;n.appendChild(a),a=d}return i instanceof Text&&r instanceof Text&&(i.appendData(r.data),E(r),r=i,l=s),o.setStart(i,s),r?o.setEnd(r,l):o.setEnd(t,t.childNodes.length),x(t),n},Ge=(o,t,e)=>{o.currentNode=e;let n;for(;n=o[t]();){if(n instanceof Text||M(n))return n;if(!N(n))return null}return null},H=(o,t)=>{let e=L(o,t),n=G(o,t),i=e!==n;e&&n&&(T(o),q(o,e,n,t));let s=Be(o,null,t);T(o),i&&(n=G(o,t),e&&n&&e!==n&&Y(e,n,o,t)),e&&x(e);let r=t.firstChild;(!r||r.nodeName==="BR")&&(x(t),t.firstChild&&o.selectNodeContents(t.firstChild)),o.collapse(!0);let l=o.startContainer,a=o.startOffset,d=new C(t,5),c=l,f=a;(!(c instanceof Text)||f===c.data.length)&&(c=Ge(d,"nextNode",c),f=0);let u=l,m=a-1;(!(u instanceof Text)||m===-1)&&(u=Ge(d,"previousPONode",c||(l instanceof Text?l:l.childNodes[a]||l)),u instanceof Text&&(m=u.data.length));let h=null,g=0;return c instanceof Text&&c.data.charAt(f)===" "&&X(o,t)?(h=c,g=f):u instanceof Text&&u.data.charAt(m)===" "&&(c instanceof Text&&c.data.charAt(f)===" "||Z(o,t))&&(h=u,g=m),h&&h.replaceData(g,1,"\xA0"),o.setStart(l,a),o.collapse(!0),s},Ze=(o,t,e)=>{let n=t.firstChild&&N(t.firstChild),i;for(D(t,e),i=t;i=W(i,e);)x(i);o.collapsed||H(o,e),T(o),o.collapse(!1);let s=S(o.endContainer,e,"BLOCKQUOTE")||e,r=L(o,e),l=null,a=W(t,t),d=!n&&!!r&&ce(r);if(r&&a&&!d&&!S(a,t,"PRE")&&!S(a,t,"TABLE")){q(o,r,r,e),o.collapse(!0);let c=o.endContainer,f=o.endOffset;if(le(r,e,!1),N(c)){let u=w(c,f,z(c,e)||e,e);c=u.parentNode,f=Array.from(c.childNodes).indexOf(u)}if(f!==y(c))for(l=document.createDocumentFragment();i=c.childNodes[f];)l.appendChild(i);Y(c,a,o,e),f=Array.from(c.parentNode.childNodes).indexOf(c)+1,c=c.parentNode,o.setEnd(c,f)}if(y(t)){d&&r&&(o.setEndBefore(r),o.collapse(!1),E(r)),q(o,s,s,e);let c=w(o.endContainer,o.endOffset,s,e),f=c?c.previousSibling:s.lastChild;s.insertBefore(t,c),c?o.setEndBefore(c):o.setEnd(s,y(s)),r=G(o,e),T(o);let u=o.endContainer,m=o.endOffset;c&&Q(c)&&F(c,e),c=f&&f.nextSibling,c&&Q(c)&&F(c,e),o.setEnd(u,m)}if(l&&r){let c=o.cloneRange();x(l),Y(r,l,c,e),o.setEnd(c.endContainer,c.endOffset)}T(o)};var ge=o=>{if(o.collapsed)return"";let t=o.startContainer,e=o.endContainer,n=new C(o.commonAncestorContainer,5,a=>K(o,a,!0));n.currentNode=t;let i=t,s="",r=!1,l;for((!(i instanceof Element)&&!(i instanceof Text)||!n.filter(i))&&(i=n.nextNode());i;)i instanceof Text?(l=i.data,l&&/\S/.test(l)&&(i===e&&(l=l.slice(0,o.endOffset)),i===t&&(l=l.slice(o.startOffset)),s+=l,r=!0)):(i.nodeName==="BR"||r&&!N(i))&&(s+=`
+`,r=!1),i=n.nextNode();return s=s.replace(/ /g," "),s};var De=Array.prototype.indexOf,je=(o,t,e,n,i,s,r)=>{let l=o.clipboardData;if(se||!l)return!1;let a=s?"":ge(t),d=L(t,e),c=G(t,e),f=e;d===c&&d?.contains(t.commonAncestorContainer)&&(f=d);let u;n?u=H(t,e):(t=t.cloneRange(),T(t),q(t,f,f,e),u=t.cloneContents());let m=t.commonAncestorContainer;for(m instanceof Text&&(m=m.parentNode);m&&m!==f;){let g=m.cloneNode(!1);g.appendChild(u),u=g,m=m.parentNode}let h;if(u.childNodes.length===1&&u.childNodes[0]instanceof Text)a=u.childNodes[0].data.replace(/ /g," "),r=!0;else{let g=p("DIV");g.appendChild(u),h=g.innerHTML,i&&(h=i(h))}return s&&h!==void 0&&(a=s(h)),fe&&(a=a.replace(/\r?\n/g,`\r
+`)),!r&&h&&a!==h&&l.setData("text/html",h),l.setData("text/plain",a),o.preventDefault(),!0},Qe=function(o){let t=this.getSelection(),e=this._root;if(t.collapsed){o.preventDefault();return}this.saveUndoState(t),je(o,t,e,!0,this._config.willCutCopy,this._config.toPlainText,!1)||setTimeout(()=>{try{this._ensureBottomLine()}catch(i){this._config.didError(i)}},0),this.setSelection(t)},Xe=function(o){je(o,this.getSelection(),this._root,!1,this._config.willCutCopy,this._config.toPlainText,!1)},Ae=function(o){this._isShiftDown=o.shiftKey},$e=function(o){let t=o.clipboardData,e=t?.items,n=this._isShiftDown,i=!1,s=!1,r=null,l=null;if(e){let b=e.length;for(;b--;){let O=e[b],A=O.type;A==="text/html"?l=O:A==="text/plain"||A==="text/uri-list"?r=O:A==="text/rtf"?i=!0:/^image\/.*/.test(A)&&(s=!0)}if(s&&!(i&&l)){o.preventDefault(),this.fireEvent("pasteImage",{clipboardData:t});return}if(!se){o.preventDefault(),l&&(!n||!r)?l.getAsString(O=>{this.insertHTML(O,!0)}):r&&r.getAsString(O=>{let A=!1,Ie=this.getSelection();if(!Ie.collapsed&&I.test(Ie.toString())){let Me=this.linkRegExp.exec(O);A=!!Me&&Me[0].length===O.length}A?this.makeLink(O):this.insertPlainText(O,!0)});return}}let a=t?.types;if(!se&&a&&(De.call(a,"text/html")>-1||!Fe&&De.call(a,"text/plain")>-1&&De.call(a,"text/rtf")<0)){o.preventDefault();let b;!n&&(b=t.getData("text/html"))?this.insertHTML(b,!0):((b=t.getData("text/plain"))||(b=t.getData("text/uri-list")))&&this.insertPlainText(b,!0);return}let d=document.body,c=this.getSelection(),f=c.startContainer,u=c.startOffset,m=c.endContainer,h=c.endOffset,g=p("DIV",{contenteditable:"true",style:"position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;"});d.appendChild(g),c.selectNodeContents(g),this.setSelection(c),setTimeout(()=>{try{let b="",O=g,A;for(;g=O;)O=g.nextSibling,E(g),A=g.firstChild,A&&A===g.lastChild&&A instanceof HTMLDivElement&&(g=A),b+=g.innerHTML;this.setSelection($(f,u,m,h)),b&&this.insertHTML(b,!0)}catch(b){this._config.didError(b)}},0)},Ve=function(o){if(!o.dataTransfer)return;let t=o.dataTransfer.types,e=t.length,n=!1,i=!1;for(;e--;)switch(t[e]){case"text/plain":n=!0;break;case"text/html":i=!0;break;default:return}(i||n&&this.saveUndoState)&&this.saveUndoState()};var we=(o,t,e)=>{t.preventDefault(),o.splitBlock(t.shiftKey,e)};var ne=(o,t)=>{try{t||(t=o.getSelection());let e=t.startContainer;e instanceof Text&&(e=e.parentNode);let n=e;for(;N(n)&&(!n.textContent||n.textContent===B);)e=n,n=e.parentNode;e!==n&&(t.setStart(n,Array.from(n.childNodes).indexOf(e)),t.collapse(!0),n.removeChild(e),P(n)||(n=z(n,o._root)||o._root),x(n),T(t)),e===o._root&&(e=e.firstChild)&&e.nodeName==="BR"&&E(e),o._ensureBottomLine(),o.setSelection(t),o._updatePath(t,!0)}catch(e){o._config.didError(e)}},Ee=(o,t)=>{let e;for(;(e=o.parentNode)&&!(e===t||e.isContentEditable);)o=e;E(o)},Te=(o,t,e)=>{if(S(t,o._root,"A"))return;let n=t.data||"",i=Math.max(n.lastIndexOf(" ",e-1),n.lastIndexOf("\xA0",e-1))+1,s=n.slice(i,e),r=o.linkRegExp.exec(s);if(r){let l=o.getSelection();o._docWasChanged(),o._recordUndoState(l),o._getRangeAndRemoveBookmark(l);let a=i+r.index,d=a+r[0].length,c=l.startContainer===t,f=l.startOffset-d;a&&(t=t.splitText(a));let u=o._config.tagAttributes.a,m=p("A",Object.assign({href:r[1]?/^(?:ht|f)tps?:/i.test(r[1])?r[1]:"http://"+r[1]:"mailto:"+r[0]},u));m.textContent=n.slice(a,d),t.parentNode.insertBefore(m,t),t.data=n.slice(d),c&&(l.setStart(t,f),l.setEnd(t,f)),o.setSelection(l)}};var Ye=(o,t,e)=>{let n=o._root;if(o._removeZWS(),o.saveUndoState(e),!e.collapsed)t.preventDefault(),H(e,n),ne(o,e);else if(X(e,n)){t.preventDefault();let i=L(e,n);if(!i)return;let s=i;D(s.parentNode,n);let r=z(s,n);if(r){if(!r.isContentEditable){Ee(r,n);return}for(Y(r,s,e,n),s=r.parentNode;s!==n&&!s.nextSibling;)s=s.parentNode;s!==n&&(s=s.nextSibling)&&F(s,n),o.setSelection(e)}else if(s){if(S(s,n,"UL")||S(s,n,"OL")){o.decreaseListLevel(e);return}else if(S(s,n,"BLOCKQUOTE")){o.removeQuote(e);return}o.setSelection(e),o._updatePath(e,!0)}}else{T(e);let i=e.startContainer,s=e.startOffset,r=i.parentNode;i instanceof Text&&r instanceof HTMLAnchorElement&&s&&r.href.includes(i.data)?(i.deleteData(s-1,1),o.setSelection(e),o.removeLink(),t.preventDefault()):(o.setSelection(e),setTimeout(()=>{ne(o)},0))}};var Je=(o,t,e)=>{let n=o._root,i,s,r,l,a,d;if(o._removeZWS(),o.saveUndoState(e),!e.collapsed)t.preventDefault(),H(e,n),ne(o,e);else if(Z(e,n)){if(t.preventDefault(),i=L(e,n),!i)return;if(D(i.parentNode,n),s=W(i,n),s){if(!s.isContentEditable){Ee(s,n);return}for(Y(i,s,e,n),s=i.parentNode;s!==n&&!s.nextSibling;)s=s.parentNode;s!==n&&(s=s.nextSibling)&&F(s,n),o.setSelection(e),o._updatePath(e,!0)}}else{if(r=e.cloneRange(),q(e,n,n,n),l=e.endContainer,a=e.endOffset,l instanceof Element&&(d=l.childNodes[a],d&&d.nodeName==="IMG")){t.preventDefault(),E(d),T(e),ne(o,e);return}o.setSelection(r),setTimeout(()=>{ne(o)},0)}};var et=(o,t,e)=>{let n=o._root;if(o._removeZWS(),e.collapsed&&X(e,n)){let i=L(e,n),s;for(;s=i.parentNode;){if(s.nodeName==="UL"||s.nodeName==="OL"){t.preventDefault(),o.increaseListLevel(e);break}i=s}}},tt=(o,t,e)=>{let n=o._root;if(o._removeZWS(),e.collapsed&&X(e,n)){let i=e.startContainer;(S(i,n,"UL")||S(i,n,"OL"))&&(t.preventDefault(),o.decreaseListLevel(e))}};var nt=(o,t,e)=>{let n,i=o._root;if(o._recordUndoState(e),o._getRangeAndRemoveBookmark(e),!e.collapsed)H(e,i),o._ensureBottomLine(),o.setSelection(e),o._updatePath(e,!0);else if(Z(e,i)){let s=L(e,i);if(s&&s.nodeName!=="PRE"){let r=s.textContent?.trimEnd().replace(B,"");if(r==="*"||r==="1."){t.preventDefault(),o.insertPlainText(" ",!1),o._docWasChanged(),o.saveUndoState(e);let l=new C(s,4),a;for(;a=l.nextNode();)E(a);r==="*"?o.makeUnorderedList():o.makeOrderedList();return}}}if(n=e.endContainer,e.endOffset===y(n))do if(n.nodeName==="A"){e.setStartAfter(n);break}while(!n.nextSibling&&(n=n.parentNode)&&n!==i);if(o._config.addLinks){let s=e.cloneRange();T(s);let r=s.startContainer,l=s.startOffset;setTimeout(()=>{Te(o,r,l)},0)}o.setSelection(e)};var ot=function(o){if(o.defaultPrevented||o.isComposing)return;let t=o.key,e="",n=o.code;/^Digit\d$/.test(n)&&(t=n.slice(-1)),t!=="Backspace"&&t!=="Delete"&&(o.altKey&&(e+="Alt-"),o.ctrlKey&&(e+="Ctrl-"),o.metaKey&&(e+="Meta-"),o.shiftKey&&(e+="Shift-")),fe&&o.shiftKey&&t==="Delete"&&(e+="Shift-"),t=e+t;let i=this.getSelection();this._keyHandlers[t]?this._keyHandlers[t](this,o,i):!i.collapsed&&!o.ctrlKey&&!o.metaKey&&t.length===1&&(this.saveUndoState(i),H(i,this._root),this._ensureBottomLine(),this.setSelection(i),this._updatePath(i,!0))},v={Backspace:Ye,Delete:Je,Tab:et,"Shift-Tab":tt," ":nt,ArrowLeft(o){o._removeZWS()},ArrowRight(o,t,e){o._removeZWS();let n=o.getRoot();if(Z(e,n)){T(e);let i=e.endContainer;do if(i.nodeName==="CODE"){let s=i.nextSibling;if(!(s instanceof Text)){let r=document.createTextNode("\xA0");i.parentNode.insertBefore(r,s),s=r}e.setStart(s,1),o.setSelection(e),t.preventDefault();break}while(!i.nextSibling&&(i=i.parentNode)&&i!==n)}}};He||(v.Enter=we,v["Shift-Enter"]=we);!de&&!_e&&(v.PageUp=o=>{o.moveCursorToStart()},v.PageDown=o=>{o.moveCursorToEnd()});var ie=(o,t)=>(t=t||null,(e,n)=>{n.preventDefault();let i=e.getSelection();e.hasFormat(o,null,i)?e.changeFormat(null,{tag:o},i):e.changeFormat({tag:o},t,i)});v[k+"b"]=ie("B");v[k+"i"]=ie("I");v[k+"u"]=ie("U");v[k+"Shift-7"]=ie("S");v[k+"Shift-5"]=ie("SUB",{tag:"SUP"});v[k+"Shift-6"]=ie("SUP",{tag:"SUB"});v[k+"Shift-8"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)UL/.test(e)?o.removeList():o.makeUnorderedList()};v[k+"Shift-9"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)OL/.test(e)?o.removeList():o.makeOrderedList()};v[k+"["]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)BLOCKQUOTE/.test(e)||!/(?:^|>)[OU]L/.test(e)?o.decreaseQuoteLevel():o.decreaseListLevel()};v[k+"]"]=(o,t)=>{t.preventDefault();let e=o.getPath();/(?:^|>)BLOCKQUOTE/.test(e)||!/(?:^|>)[OU]L/.test(e)?o.increaseQuoteLevel():o.increaseListLevel()};v[k+"d"]=(o,t)=>{t.preventDefault(),o.toggleCode()};v[k+"z"]=(o,t)=>{t.preventDefault(),o.undo()};v[k+"y"]=v[k+"Shift-z"]=v[k+"Shift-Z"]=(o,t)=>{t.preventDefault(),o.redo()};var Ce=class{constructor(t,e){this.customEvents=new Set(["pathChange","select","input","pasteImage","undoStateChange"]);this.startSelectionId="squire-selection-start";this.endSelectionId="squire-selection-end";this.linkRegExp=/\b(?:((?:(?:ht|f)tps?:\/\/|www\d{0,3}[.]|[a-z0-9][a-z0-9.\-]*[.][a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:[^\s?&`!()\[\]{};:'".,<>«»“”‘’]|\([^\s()<>]+\)))|([\w\-.%+]+@(?:[\w\-]+\.)+[a-z]{2,}\b(?:[?][^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+(?:&[^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+)*)?))/i;this.tagAfterSplit={DT:"DD",DD:"DT",LI:"LI",PRE:"PRE"};this._root=t,this._config=this._makeConfig(e),this._isFocused=!1,this._lastSelection=$(t,0),this._willRestoreSelection=!1,this._mayHaveZWS=!1,this._lastAnchorNode=null,this._lastFocusNode=null,this._path="",this._events=new Map,this._undoIndex=-1,this._undoStack=[],this._undoStackLength=0,this._isInUndoState=!1,this._ignoreChange=!1,this._ignoreAllChanges=!1,this.addEventListener("selectionchange",this._updatePathOnEvent),this.addEventListener("blur",this._enableRestoreSelection),this.addEventListener("mousedown",this._disableRestoreSelection),this.addEventListener("touchstart",this._disableRestoreSelection),this.addEventListener("focus",this._restoreSelection),this.addEventListener("blur",this._removeZWS),this._isShiftDown=!1,this.addEventListener("cut",Qe),this.addEventListener("copy",Xe),this.addEventListener("paste",$e),this.addEventListener("drop",Ve),this.addEventListener("keydown",Ae),this.addEventListener("keyup",Ae),this.addEventListener("keydown",ot),this._keyHandlers=Object.create(v);let n=new MutationObserver(()=>this._docWasChanged());n.observe(t,{childList:!0,attributes:!0,characterData:!0,subtree:!0}),this._mutation=n,t.setAttribute("contenteditable","true"),this.addEventListener("beforeinput",this._beforeInput),this.setHTML("")}destroy(){this._events.forEach((t,e)=>{this.removeEventListener(e)}),this._mutation.disconnect(),this._undoIndex=-1,this._undoStack=[],this._undoStackLength=0}_makeConfig(t){let e={blockTag:"DIV",blockAttributes:null,tagAttributes:{},classNames:{color:"color",fontFamily:"font",fontSize:"size",highlight:"highlight"},undo:{documentSizeThreshold:-1,undoLimit:-1},addLinks:!0,willCutCopy:null,toPlainText:null,sanitizeToDOMFragment:n=>{let i=DOMPurify.sanitize(n,{ALLOW_UNKNOWN_PROTOCOLS:!0,WHOLE_DOCUMENT:!1,RETURN_DOM:!0,RETURN_DOM_FRAGMENT:!0,FORCE_BODY:!1});return i?document.importNode(i,!0):document.createDocumentFragment()},didError:n=>console.log(n)};return t&&(Object.assign(e,t),e.blockTag=e.blockTag.toUpperCase()),e}setKeyHandler(t,e){return this._keyHandlers[t]=e,this}_beforeInput(t){switch(t.inputType){case"insertLineBreak":t.preventDefault(),this.splitBlock(!0);break;case"insertParagraph":t.preventDefault(),this.splitBlock(!1);break;case"insertOrderedList":t.preventDefault(),this.makeOrderedList();break;case"insertUnoderedList":t.preventDefault(),this.makeUnorderedList();break;case"historyUndo":t.preventDefault(),this.undo();break;case"historyRedo":t.preventDefault(),this.redo();break;case"formatBold":t.preventDefault(),this.bold();break;case"formaItalic":t.preventDefault(),this.italic();break;case"formatUnderline":t.preventDefault(),this.underline();break;case"formatStrikeThrough":t.preventDefault(),this.strikethrough();break;case"formatSuperscript":t.preventDefault(),this.superscript();break;case"formatSubscript":t.preventDefault(),this.subscript();break;case"formatJustifyFull":case"formatJustifyCenter":case"formatJustifyRight":case"formatJustifyLeft":{t.preventDefault();let e=t.inputType.slice(13).toLowerCase();e==="full"&&(e="justify"),this.setTextAlignment(e);break}case"formatRemove":t.preventDefault(),this.removeAllFormatting();break;case"formatSetBlockTextDirection":{t.preventDefault();let e=t.data;e==="null"&&(e=null),this.setTextDirection(e);break}case"formatBackColor":t.preventDefault(),this.setHighlightColor(t.data);break;case"formatFontColor":t.preventDefault(),this.setTextColor(t.data);break;case"formatFontName":t.preventDefault(),this.setFontFace(t.data);break}}handleEvent(t){this.fireEvent(t.type,t)}fireEvent(t,e){let n=this._events.get(t);if(/^(?:focus|blur)/.test(t)){let i=this._root===document.activeElement;if(t==="focus"){if(!i||this._isFocused)return this;this._isFocused=!0}else{if(i||!this._isFocused)return this;this._isFocused=!1}}if(n){let i=e instanceof Event?e:new CustomEvent(t,{detail:e});n=n.slice();for(let s of n)try{"handleEvent"in s?s.handleEvent(i):s.call(this,i)}catch(r){this._config.didError(r)}}return this}addEventListener(t,e){let n=this._events.get(t),i=this._root;return n||(n=[],this._events.set(t,n),this.customEvents.has(t)||(t==="selectionchange"&&(i=document),i.addEventListener(t,this,!0))),n.push(e),this}removeEventListener(t,e){let n=this._events.get(t),i=this._root;if(n){if(e){let s=n.length;for(;s--;)n[s]===e&&n.splice(s,1)}else n.length=0;n.length||(this._events.delete(t),this.customEvents.has(t)||(t==="selectionchange"&&(i=document),i.removeEventListener(t,this,!0)))}return this}focus(){return this._root.focus({preventScroll:!0}),this}blur(){return this._root.blur(),this}_enableRestoreSelection(){this._willRestoreSelection=!0}_disableRestoreSelection(){this._willRestoreSelection=!1}_restoreSelection(){this._willRestoreSelection&&this.setSelection(this._lastSelection)}_removeZWS(){this._mayHaveZWS&&(re(this._root),this._mayHaveZWS=!1)}_saveRangeToBookmark(t){let e=p("INPUT",{id:this.startSelectionId,type:"hidden"}),n=p("INPUT",{id:this.endSelectionId,type:"hidden"}),i;j(t,e),t.collapse(!1),j(t,n),e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING&&(e.id=this.endSelectionId,n.id=this.startSelectionId,i=e,e=n,n=i),t.setStartAfter(e),t.setEndBefore(n)}_getRangeAndRemoveBookmark(t){let e=this._root,n=e.querySelector("#"+this.startSelectionId),i=e.querySelector("#"+this.endSelectionId);if(n&&i){let s=n.parentNode,r=i.parentNode,l=Array.from(s.childNodes).indexOf(n),a=Array.from(r.childNodes).indexOf(i);s===r&&(a-=1),n.remove(),i.remove(),t||(t=document.createRange()),t.setStart(s,l),t.setEnd(r,a),te(s,t),s!==r&&te(r,t),t.collapsed&&(s=t.startContainer,s instanceof Text&&(r=s.childNodes[t.startOffset],(!r||!(r instanceof Text))&&(r=s.childNodes[t.startOffset-1]),r&&r instanceof Text&&(t.setStart(r,0),t.collapse(!0))))}return t||null}getSelection(){let t=window.getSelection(),e=this._root,n=null;if(this._isFocused&&t&&t.rangeCount){n=t.getRangeAt(0).cloneRange();let i=n.startContainer,s=n.endContainer;i&&M(i)&&n.setStartBefore(i),s&&M(s)&&n.setEndBefore(s)}return n&&e.contains(n.commonAncestorContainer)?this._lastSelection=n:(n=this._lastSelection,document.contains(n.commonAncestorContainer)||(n=null)),n||(n=$(e.firstElementChild||e,0)),n}setSelection(t){if(this._lastSelection=t,!this._isFocused)this._enableRestoreSelection();else{let e=window.getSelection();e&&("setBaseAndExtent"in Selection.prototype?e.setBaseAndExtent(t.startContainer,t.startOffset,t.endContainer,t.endOffset):(e.removeAllRanges(),e.addRange(t)))}return this}_moveCursorTo(t){let e=this._root,n=$(e,t?0:e.childNodes.length);return T(n),this.setSelection(n),this}moveCursorToStart(){return this._moveCursorTo(!0)}moveCursorToEnd(){return this._moveCursorTo(!1)}getCursorPosition(){let t=this.getSelection(),e=t.getBoundingClientRect();if(e&&!e.top){this._ignoreChange=!0;let n=p("SPAN");n.textContent=B,j(t,n),e=n.getBoundingClientRect();let i=n.parentNode;i.removeChild(n),te(i,t)}return e}getPath(){return this._path}_updatePathOnEvent(){this._isFocused&&this._updatePath(this.getSelection())}_updatePath(t,e){let n=t.startContainer,i=t.endContainer,s;(e||n!==this._lastAnchorNode||i!==this._lastFocusNode)&&(this._lastAnchorNode=n,this._lastFocusNode=i,s=n&&i?n===i?this._getPath(i):"(selection)":"",this._path!==s&&(this._path=s,this.fireEvent("pathChange",{path:s}))),this.fireEvent(t.collapsed?"cursor":"select",{range:t})}_getPath(t){let e=this._root,n=this._config,i="";if(t&&t!==e){let s=t.parentNode;if(i=s?this._getPath(s):"",t instanceof HTMLElement){let r=t.id,l=t.classList,a=Array.from(l).sort(),d=t.dir,c=n.classNames;i+=(i?">":"")+t.nodeName,r&&(i+="#"+r),a.length&&(i+=".",i+=a.join(".")),d&&(i+="[dir="+d+"]"),l.contains(c.highlight)&&(i+="[backgroundColor="+t.style.backgroundColor.replace(/ /g,"")+"]"),l.contains(c.color)&&(i+="[color="+t.style.color.replace(/ /g,"")+"]"),l.contains(c.fontFamily)&&(i+="[fontFamily="+t.style.fontFamily.replace(/ /g,"")+"]"),l.contains(c.fontSize)&&(i+="[fontSize="+t.style.fontSize+"]")}}return i}modifyDocument(t){let e=this._mutation;return e&&(e.takeRecords().length&&this._docWasChanged(),e.disconnect()),this._ignoreAllChanges=!0,t(),this._ignoreAllChanges=!1,e&&(e.observe(this._root,{childList:!0,attributes:!0,characterData:!0,subtree:!0}),this._ignoreChange=!1),this}_docWasChanged(){if(qe(),this._mayHaveZWS=!0,!this._ignoreAllChanges){if(this._ignoreChange){this._ignoreChange=!1;return}this._isInUndoState&&(this._isInUndoState=!1,this.fireEvent("undoStateChange",{canUndo:!0,canRedo:!1})),this.fireEvent("input")}}_recordUndoState(t,e){let n=this._isInUndoState;if(!n||e){let i=this._undoIndex+1,s=this._undoStack,r=this._config.undo,l=r.documentSizeThreshold,a=r.undoLimit;if(i<this._undoStackLength&&(s.length=this._undoStackLength=i),t&&this._saveRangeToBookmark(t),n)return this;let d=this._getRawHTML();e&&(i-=1),l>-1&&d.length*2>l&&a>-1&&i>a&&(s.splice(0,i-a),i=a,this._undoStackLength=a),s[i]=d,this._undoIndex=i,this._undoStackLength+=1,this._isInUndoState=!0}return this}saveUndoState(t){return t||(t=this.getSelection()),this._recordUndoState(t,this._isInUndoState),this._getRangeAndRemoveBookmark(t),this}undo(){if(this._undoIndex!==0||!this._isInUndoState){this._recordUndoState(this.getSelection(),!1),this._undoIndex-=1,this._setRawHTML(this._undoStack[this._undoIndex]);let t=this._getRangeAndRemoveBookmark();t&&this.setSelection(t),this._isInUndoState=!0,this.fireEvent("undoStateChange",{canUndo:this._undoIndex!==0,canRedo:!0}),this.fireEvent("input")}return this.focus()}redo(){let t=this._undoIndex,e=this._undoStackLength;if(t+1<e&&this._isInUndoState){this._undoIndex+=1,this._setRawHTML(this._undoStack[this._undoIndex]);let n=this._getRangeAndRemoveBookmark();n&&this.setSelection(n),this.fireEvent("undoStateChange",{canUndo:!0,canRedo:t+2<e}),this.fireEvent("input")}return this.focus()}getRoot(){return this._root}_getRawHTML(){return this._root.innerHTML}_setRawHTML(t){let e=this._root;e.innerHTML=t;let n=e,i=n.firstChild;if(!i||i.nodeName==="BR"){let s=this.createDefaultBlock();i?n.replaceChild(s,i):n.appendChild(s)}else for(;n=W(n,e);)x(n);return this._ignoreChange=!0,this}getHTML(t){let e;t&&(e=this.getSelection(),this._saveRangeToBookmark(e));let n=this._getRawHTML().replace(/\u200B/g,"");return t&&this._getRangeAndRemoveBookmark(e),n}setHTML(t){let e=this._config.sanitizeToDOMFragment(t,this),n=this._root;Ne(e,this._config),le(e,n,!1),D(e,n);let i=e,s=i.firstChild;if(!s||s.nodeName==="BR"){let l=this.createDefaultBlock();s?i.replaceChild(l,s):i.appendChild(l)}else for(;i=W(i,n);)x(i);for(this._ignoreChange=!0;s=n.lastChild;)n.removeChild(s);n.appendChild(e),this._undoIndex=-1,this._undoStack.length=0,this._undoStackLength=0,this._isInUndoState=!1;let r=this._getRangeAndRemoveBookmark()||$(n.firstElementChild||n,0);return this.saveUndoState(r),this.setSelection(r),this._updatePath(r,!0),this}insertHTML(t,e){let n=this._config,i=n.sanitizeToDOMFragment(t,this),s=this.getSelection();this.saveUndoState(s);try{let r=this._root;n.addLinks&&this.addDetectedLinks(i,i),Ne(i,this._config),le(i,r,!1),Se(i),i.normalize();let l=i;for(;l=W(l,i);)x(l);let a=!0;if(e){let d=new CustomEvent("willPaste",{cancelable:!0,detail:{fragment:i}});this.fireEvent("willPaste",d),i=d.detail.fragment,a=!d.defaultPrevented}a&&(Ze(s,i,r),s.collapse(!1),Oe(s,"A",r),this._ensureBottomLine()),this.setSelection(s),this._updatePath(s,!0),e&&this.focus()}catch(r){this._config.didError(r)}return this}insertElement(t,e){if(e||(e=this.getSelection()),e.collapse(!0),N(t))j(e,t),e.setStartAfter(t);else{let n=this._root,i=L(e,n),s=i||n,r=null;for(;s!==n&&!s.nextSibling;)s=s.parentNode;if(s!==n){let a=s.parentNode;r=w(a,s.nextSibling,n,n)}i&&ce(i)&&E(i),n.insertBefore(t,r);let l=this.createDefaultBlock();n.insertBefore(l,r),e.setStart(l,0),e.setEnd(l,0),T(e)}return this.focus(),this.setSelection(e),this._updatePath(e),this}insertImage(t,e){let n=p("IMG",Object.assign({src:t},e));return this.insertElement(n),n}insertPlainText(t,e){let n=this.getSelection();if(n.collapsed&&S(n.startContainer,this._root,"PRE")){let c=n.startContainer,f=n.startOffset,u;if(!c||!(c instanceof Text)){let h=document.createTextNode("");c.insertBefore(h,c.childNodes[f]),u=h,f=0}else u=c;let m=!0;if(e){let h=new CustomEvent("willPaste",{cancelable:!0,detail:{text:t}});this.fireEvent("willPaste",h),t=h.detail.text,m=!h.defaultPrevented}return m&&(u.insertData(f,t),n.setStart(u,f+t.length),n.collapse(!0)),this.setSelection(n),this}let i=t.split(`
+`),s=this._config,r=s.blockTag,l=s.blockAttributes,a="</"+r+">",d="<"+r;for(let c in l)d+=" "+c+'="'+ke(l[c])+'"';d+=">";for(let c=0,f=i.length;c<f;c+=1){let u=i[c];u=ke(u).replace(/ (?=(?: |$))/g,"&nbsp;"),c&&(u=d+(u||"<BR>")+a),i[c]=u}return this.insertHTML(i.join(""),e)}getSelectedText(t){return ge(t||this.getSelection())}getFontInfo(t){let e={color:void 0,backgroundColor:void 0,fontFamily:void 0,fontSize:void 0};t||(t=this.getSelection()),T(t);let n=0,i=t.commonAncestorContainer;if(t.collapsed||i instanceof Text)for(i instanceof Text&&(i=i.parentNode);n<4&&i;){let s=i.style;if(s){let r=s.color;!e.color&&r&&(e.color=r,n+=1);let l=s.backgroundColor;!e.backgroundColor&&l&&(e.backgroundColor=l,n+=1);let a=s.fontFamily;!e.fontFamily&&a&&(e.fontFamily=a,n+=1);let d=s.fontSize;!e.fontSize&&d&&(e.fontSize=d,n+=1)}i=i.parentNode}return e}hasFormat(t,e,n){t=t.toUpperCase(),e||(e={}),n||(n=this.getSelection()),!n.collapsed&&n.startContainer instanceof Text&&n.startOffset===n.startContainer.length&&n.startContainer.nextSibling&&n.setStartBefore(n.startContainer.nextSibling),!n.collapsed&&n.endContainer instanceof Text&&n.endOffset===0&&n.endContainer.previousSibling&&n.setEndAfter(n.endContainer.previousSibling);let i=this._root,s=n.commonAncestorContainer;if(S(s,i,t,e))return!0;if(s instanceof Text)return!1;let r=new C(s,4,d=>K(n,d,!0)),l=!1,a;for(;a=r.nextNode();){if(!S(a,i,t,e))return!1;l=!0}return l}changeFormat(t,e,n,i){return n||(n=this.getSelection()),this.saveUndoState(n),e&&(n=this._removeFormat(e.tag.toUpperCase(),e.attributes||{},n,i)),t&&(n=this._addFormat(t.tag.toUpperCase(),t.attributes||{},n)),this.setSelection(n),this._updatePath(n,!0),this.focus()}_addFormat(t,e,n){let i=this._root;if(n.collapsed){let s=x(p(t,e));j(n,s);let r=s.firstChild||s,l=r instanceof Text?r.length:0;n.setStart(r,l),n.collapse(!0);let a=s;for(;N(a);)a=a.parentNode;re(a,s)}else{let s=new C(n.commonAncestorContainer,5,c=>(c instanceof Text||c.nodeName==="BR"||c.nodeName==="IMG")&&K(n,c,!0)),{startContainer:r,startOffset:l,endContainer:a,endOffset:d}=n;if(s.currentNode=r,!(r instanceof Element)&&!(r instanceof Text)||!s.filter(r)){let c=s.nextNode();if(!c)return n;r=c,l=0}do{let c=s.currentNode;if(!S(c,i,t,e)){c===a&&c.length>d&&c.splitText(d),c===r&&l&&(c=c.splitText(l),a===r?(a=c,d-=l):a===r.parentNode&&(d+=1),r=c,l=0);let u=p(t,e);R(c,u),u.appendChild(c)}}while(s.nextNode());n=$(r,l,a,d)}return n}_removeFormat(t,e,n,i){this._saveRangeToBookmark(n);let s;n.collapsed&&(oe?s=document.createTextNode(B):s=document.createTextNode(""),j(n,s));let r=n.commonAncestorContainer;for(;N(r);)r=r.parentNode;let l=n.startContainer,a=n.startOffset,d=n.endContainer,c=n.endOffset,f=[],u=(h,g)=>{if(K(n,h,!1))return;let b,O;if(!K(n,h,!0)){!(h instanceof HTMLInputElement)&&(!(h instanceof Text)||h.data)&&f.push([g,h]);return}if(h instanceof Text)h===d&&c!==h.length&&f.push([g,h.splitText(c)]),h===l&&a&&(h.splitText(a),f.push([g,h]));else for(b=h.firstChild;b;b=O)O=b.nextSibling,u(b,g)},m=Array.from(r.getElementsByTagName(t)).filter(h=>K(n,h,!0)&&he(h,t,e));if(i||m.forEach(h=>{u(h,h)}),f.forEach(([h,g])=>{h=h.cloneNode(!1),R(g,h),h.appendChild(g)}),m.forEach(h=>{R(h,_(h))}),oe&&s){s=s.parentNode;let h=s;for(;h&&N(h);)h=h.parentNode;h&&re(h,s)}return this._getRangeAndRemoveBookmark(n),s&&n.collapse(!1),te(r,n),n}bold(){return this.changeFormat({tag:"B"})}removeBold(){return this.changeFormat(null,{tag:"B"})}italic(){return this.changeFormat({tag:"I"})}removeItalic(){return this.changeFormat(null,{tag:"I"})}underline(){return this.changeFormat({tag:"U"})}removeUnderline(){return this.changeFormat(null,{tag:"U"})}strikethrough(){return this.changeFormat({tag:"S"})}removeStrikethrough(){return this.changeFormat(null,{tag:"S"})}subscript(){return this.changeFormat({tag:"SUB"},{tag:"SUP"})}removeSubscript(){return this.changeFormat(null,{tag:"SUB"})}superscript(){return this.changeFormat({tag:"SUP"},{tag:"SUB"})}removeSuperscript(){return this.changeFormat(null,{tag:"SUP"})}makeLink(t,e){let n=this.getSelection();if(n.collapsed){let i=t.indexOf(":")+1;if(i)for(;t[i]==="/";)i+=1;j(n,document.createTextNode(t.slice(i)))}return e=Object.assign({href:t},this._config.tagAttributes.a,e),this.changeFormat({tag:"A",attributes:e},{tag:"A"},n)}removeLink(){return this.changeFormat(null,{tag:"A"},this.getSelection(),!0)}addDetectedLinks(t,e){let n=new C(t,4,l=>!S(l,e||this._root,"A")),i=this.linkRegExp,s=this._config.tagAttributes.a,r;for(;r=n.nextNode();){let l=r.parentNode,a=r.data,d;for(;d=i.exec(a);){let c=d.index,f=c+d[0].length;c&&l.insertBefore(document.createTextNode(a.slice(0,c)),r);let u=p("A",Object.assign({href:d[1]?/^(?:ht|f)tps?:/i.test(d[1])?d[1]:"http://"+d[1]:"mailto:"+d[0]},s));u.textContent=a.slice(c,f),l.insertBefore(u,r),r.data=a=a.slice(f)}}return this}setFontFace(t){let e=this._config.classNames.fontFamily;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"font-family: "+t+", sans-serif;"}}:null,{tag:"SPAN",attributes:{class:e}})}setFontSize(t){let e=this._config.classNames.fontSize;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"font-size: "+(typeof t=="number"?t+"px":t)}}:null,{tag:"SPAN",attributes:{class:e}})}setTextColor(t){let e=this._config.classNames.color;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"color:"+t}}:null,{tag:"SPAN",attributes:{class:e}})}setHighlightColor(t){let e=this._config.classNames.highlight;return this.changeFormat(t?{tag:"SPAN",attributes:{class:e,style:"background-color:"+t}}:null,{tag:"SPAN",attributes:{class:e}})}_ensureBottomLine(){let t=this._root,e=t.lastElementChild;(!e||e.nodeName!==this._config.blockTag||!P(e))&&t.appendChild(this.createDefaultBlock())}createDefaultBlock(t){let e=this._config;return x(p(e.blockTag,e.blockAttributes,t))}splitBlock(t,e){e||(e=this.getSelection());let n=this._root,i,s,r,l;if(this._recordUndoState(e),this._removeZWS(),this._getRangeAndRemoveBookmark(e),e.collapsed||H(e,n),this._config.addLinks){T(e);let u=e.startContainer,m=e.startOffset;setTimeout(()=>{Te(this,u,m)},0)}if(i=L(e,n),i&&(s=S(i,n,"PRE"))){T(e),r=e.startContainer;let u=e.startOffset;return r instanceof Text||(r=document.createTextNode(""),s.insertBefore(r,s.firstChild)),!t&&r instanceof Text&&(r.data.charAt(u-1)===`
 `||X(e,n))&&(r.data.charAt(u)===`
 `||Z(e,n))?(r.deleteData(u&&u-1,u?2:1),l=w(r,u&&u-1,n,n),r=l.previousSibling,r.textContent||E(r),r=this.createDefaultBlock(),l.parentNode.insertBefore(r,l),l.textContent||E(l),e.setStart(r,0)):(r.insertData(u,`
-`),x(s),r.length===u+1?e.setStartAfter(r):e.setStart(r,u+1)),e.collapse(!0),this.setSelection(e),this._updatePath(e,!0),this._docWasChanged(),this}if(!i||t||/^T[HD]$/.test(i.nodeName))return Oe(e,"A",n),j(e,p("BR")),e.collapse(!1),this.setSelection(e),this._updatePath(e,!0),this;if((s=S(i,n,"LI"))&&(i=s),ce(i)){if(S(i,n,"UL")||S(i,n,"OL"))return this.decreaseListLevel(e),this;if(S(i,n,"BLOCKQUOTE"))return this.removeQuote(e),this}r=e.startContainer;let a=e.startOffset,d=this.tagAfterSplit[i.nodeName];l=w(r,a,i.parentNode,this._root);let c=this._config,f=null;for(d||(d=c.blockTag,f=c.blockAttributes),he(l,d,f)||(i=p(d,f),l.dir&&(i.dir=l.dir),R(l,i),i.appendChild(C(l)),l=i),re(i),Se(i),x(i);l instanceof Element;){let u=l.firstChild,m;if(l.nodeName==="A"&&(!l.textContent||l.textContent===B)){u=document.createTextNode(""),R(l,u),l=u;break}for(;u&&u instanceof Text&&!u.data&&(m=u.nextSibling,!(!m||m.nodeName==="BR"));)E(u),u=m;if(!u||u.nodeName==="BR"||u instanceof Text)break;l=u}return e=$(l,0),this.setSelection(e),this._updatePath(e,!0),this}forEachBlock(t,e,n){n||(n=this.getSelection()),e&&this.saveUndoState(n);let i=this._root,s=L(n,i),r=G(n,i);if(s&&r)do if(t(s)||s===r)break;while(s=W(s,i));return e&&(this.setSelection(n),this._updatePath(n,!0)),this}modifyBlocks(t,e){e||(e=this.getSelection()),this._recordUndoState(e,this._isInUndoState);let n=this._root;ye(e,n),q(e,n,n,n);let i=Be(e,n,n);if(!e.collapsed){let s=e.endContainer;if(s===n)e.collapse(!1);else{for(;s.parentNode!==n;)s=s.parentNode;e.setStartBefore(s),e.collapse(!0)}}return j(e,t.call(this,i)),e.endOffset<e.endContainer.childNodes.length&&F(e.endContainer.childNodes[e.endOffset],n),F(e.startContainer.childNodes[e.startOffset],n),this._getRangeAndRemoveBookmark(e),this.setSelection(e),this._updatePath(e,!0),this}setTextAlignment(t){return this.forEachBlock(e=>{let n=e.className.split(/\s+/).filter(i=>!!i&&!/^align/.test(i)).join(" ");t?(e.className=n+" align-"+t,e.style.textAlign=t):(e.className=n,e.style.textAlign="")},!0),this.focus()}setTextDirection(t){return this.forEachBlock(e=>{t?e.dir=t:e.removeAttribute("dir")},!0),this.focus()}_getListSelection(t,e){let n=t.commonAncestorContainer,i=t.startContainer,s=t.endContainer;for(;n&&n!==e&&!/^[OU]L$/.test(n.nodeName);)n=n.parentNode;if(!n||n===e)return null;for(i===n&&(i=i.childNodes[t.startOffset]),s===n&&(s=s.childNodes[t.endOffset]);i&&i.parentNode!==n;)i=i.parentNode;for(;s&&s.parentNode!==n;)s=s.parentNode;return[n,i,s]}increaseListLevel(t){t||(t=this.getSelection());let e=this._root,n=this._getListSelection(t,e);if(!n)return this.focus();let[i,s,r]=n;if(!s||s===i.firstChild)return this.focus();this._recordUndoState(t,this._isInUndoState);let l=i.nodeName,a=s.previousSibling,d,c;a.nodeName!==l&&(d=this._config.tagAttributes[l.toLowerCase()],a=p(l,d),i.insertBefore(a,s));do c=s===r?null:s.nextSibling,a.appendChild(s);while(s=c);return c=a.nextSibling,c&&F(c,e),this._getRangeAndRemoveBookmark(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}decreaseListLevel(t){t||(t=this.getSelection());let e=this._root,n=this._getListSelection(t,e);if(!n)return this.focus();let[i,s,r]=n;s||(s=i.firstChild),r||(r=i.lastChild),this._recordUndoState(t,this._isInUndoState);let l,a=null;if(s){let d=i.parentNode;if(a=r.nextSibling?w(i,r.nextSibling,d,e):i.nextSibling,d!==e&&d.nodeName==="LI"){for(d=d.parentNode;a;)l=a.nextSibling,r.appendChild(a),a=l;a=i.parentNode.nextSibling}let c=!/^[OU]L$/.test(d.nodeName);do l=s===r?null:s.nextSibling,i.removeChild(s),c&&s.nodeName==="LI"&&(s=this.createDefaultBlock([C(s)])),d.insertBefore(s,a);while(s=l)}return i.firstChild||E(i),a&&F(a,e),this._getRangeAndRemoveBookmark(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}_makeList(t,e){let n=ae(t,this._root),i=this._config.tagAttributes,s=i[e.toLowerCase()],r=i.li,l;for(;l=n.nextNode();)if(l.parentNode instanceof HTMLLIElement&&(l=l.parentNode,n.currentNode=l.lastChild),l instanceof HTMLLIElement){l=l.parentNode;let a=l.nodeName;a!==e&&/^[OU]L$/.test(a)&&R(l,p(e,s,[C(l)]))}else{let a=p("LI",r);l.dir&&(a.dir=l.dir);let d=l.previousSibling;d&&d.nodeName===e?(d.appendChild(a),E(l)):R(l,p(e,s,[a])),a.appendChild(C(l)),n.currentNode=a}return t}makeUnorderedList(){return this.modifyBlocks(t=>this._makeList(t,"UL")),this.focus()}makeOrderedList(){return this.modifyBlocks(t=>this._makeList(t,"OL")),this.focus()}removeList(){return this.modifyBlocks(t=>{let e=t.querySelectorAll("UL, OL"),n=t.querySelectorAll("LI"),i=this._root;for(let s=0,r=e.length;s<r;s+=1){let l=e[s],a=C(l);D(a,i),R(l,a)}for(let s=0,r=n.length;s<r;s+=1){let l=n[s];P(l)?R(l,this.createDefaultBlock([C(l)])):(D(l,i),R(l,C(l)))}return t}),this.focus()}increaseQuoteLevel(t){return this.modifyBlocks(e=>p("BLOCKQUOTE",this._config.tagAttributes.blockquote,[e]),t),this.focus()}decreaseQuoteLevel(t){return this.modifyBlocks(e=>(Array.from(e.querySelectorAll("blockquote")).filter(n=>!S(n.parentNode,e,"BLOCKQUOTE")).forEach(n=>{R(n,C(n))}),e),t),this.focus()}removeQuote(t){return this.modifyBlocks(()=>this.createDefaultBlock([p("INPUT",{id:this.startSelectionId,type:"hidden"}),p("INPUT",{id:this.endSelectionId,type:"hidden"})]),t),this.focus()}code(){let t=this.getSelection();return t.collapsed||Q(t.commonAncestorContainer)?(this.modifyBlocks(e=>{let n=this._root,i=document.createDocumentFragment(),s=ae(e,n),r;for(;r=s.nextNode();){let a=r.querySelectorAll("BR"),d=[],c=a.length;for(let f=0;f<c;f+=1)d[f]=ee(a[f],!1);for(;c--;){let f=a[c];d[c]?R(f,document.createTextNode(`
-`)):E(f)}for(a=r.querySelectorAll("CODE"),c=a.length;c--;)R(a[c],C(a[c]));i.childNodes.length&&i.appendChild(document.createTextNode(`
-`)),i.appendChild(C(r))}let l=new T(i,4);for(;r=l.nextNode();)r.data=r.data.replace(/ /g," ");return i.normalize(),x(p("PRE",this._config.tagAttributes.pre,[i]))},t),this.focus()):this.changeFormat({tag:"CODE",attributes:this._config.tagAttributes.code},null,t),this}removeCode(){let t=this.getSelection(),e=t.commonAncestorContainer;return S(e,this._root,"PRE")?(this.modifyBlocks(i=>{let s=this._root,r=i.querySelectorAll("PRE"),l=r.length;for(;l--;){let a=r[l],d=new T(a,4),c;for(;c=d.nextNode();){let f=c.data;f=f.replace(/ (?= )/g,"\xA0");let u=document.createDocumentFragment(),m;for(;(m=f.indexOf(`
-`))>-1;)u.appendChild(document.createTextNode(f.slice(0,m))),u.appendChild(p("BR")),f=f.slice(m+1);c.parentNode.insertBefore(u,c),c.data=f}D(a,s),R(a,C(a))}return i},t),this.focus()):this.changeFormat(null,{tag:"CODE"},t),this}toggleCode(){return this.hasFormat("PRE")||this.hasFormat("CODE")?this.removeCode():this.code(),this}_removeFormatting(t,e){for(let n=t.firstChild,i;n;n=i){if(i=n.nextSibling,N(n)){if(n instanceof Text||n.nodeName==="BR"||n.nodeName==="IMG"){e.appendChild(n);continue}}else if(P(n)){e.appendChild(this.createDefaultBlock([this._removeFormatting(n,document.createDocumentFragment())]));continue}this._removeFormatting(n,e)}return e}removeAllFormatting(t){if(t||(t=this.getSelection()),t.collapsed)return this.focus();let e=this._root,n=t.commonAncestorContainer;for(;n&&!P(n);)n=n.parentNode;if(n||(ye(t,e),n=e),n instanceof Text)return this.focus();this.saveUndoState(t),q(t,n,n,e);let i=t.startContainer,s=t.startOffset,r=t.endContainer,l=t.endOffset,a=document.createDocumentFragment(),d=document.createDocumentFragment(),c=w(r,l,n,e),f=w(i,s,n,e),u;for(;f!==c;)u=f.nextSibling,a.appendChild(f),f=u;if(this._removeFormatting(a,d),d.normalize(),f=d.firstChild,u=d.lastChild,f){n.insertBefore(d,c);let m=Array.from(n.childNodes);s=m.indexOf(f),l=u?m.indexOf(u)+1:0}else c&&(s=Array.from(n.childNodes).indexOf(c),l=s);return t.setStart(n,s),t.setEnd(n,l),te(n,t),_(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}};var Mo=Ce;export{Mo as default};
+`),x(s),r.length===u+1?e.setStartAfter(r):e.setStart(r,u+1)),e.collapse(!0),this.setSelection(e),this._updatePath(e,!0),this._docWasChanged(),this}if(!i||t||/^T[HD]$/.test(i.nodeName))return Oe(e,"A",n),j(e,p("BR")),e.collapse(!1),this.setSelection(e),this._updatePath(e,!0),this;if((s=S(i,n,"LI"))&&(i=s),ce(i)){if(S(i,n,"UL")||S(i,n,"OL"))return this.decreaseListLevel(e),this;if(S(i,n,"BLOCKQUOTE"))return this.removeQuote(e),this}r=e.startContainer;let a=e.startOffset,d=this.tagAfterSplit[i.nodeName];l=w(r,a,i.parentNode,this._root);let c=this._config,f=null;for(d||(d=c.blockTag,f=c.blockAttributes),he(l,d,f)||(i=p(d,f),l.dir&&(i.dir=l.dir),R(l,i),i.appendChild(_(l)),l=i),re(i),Se(i),x(i);l instanceof Element;){let u=l.firstChild,m;if(l.nodeName==="A"&&(!l.textContent||l.textContent===B)){u=document.createTextNode(""),R(l,u),l=u;break}for(;u&&u instanceof Text&&!u.data&&(m=u.nextSibling,!(!m||m.nodeName==="BR"));)E(u),u=m;if(!u||u.nodeName==="BR"||u instanceof Text)break;l=u}return e=$(l,0),this.setSelection(e),this._updatePath(e,!0),this}forEachBlock(t,e,n){n||(n=this.getSelection()),e&&this.saveUndoState(n);let i=this._root,s=L(n,i),r=G(n,i);if(s&&r)do if(t(s)||s===r)break;while(s=W(s,i));return e&&(this.setSelection(n),this._updatePath(n,!0)),this}modifyBlocks(t,e){e||(e=this.getSelection()),this._recordUndoState(e,this._isInUndoState);let n=this._root;ye(e,n),q(e,n,n,n);let i=Be(e,n,n);if(!e.collapsed){let s=e.endContainer;if(s===n)e.collapse(!1);else{for(;s.parentNode!==n;)s=s.parentNode;e.setStartBefore(s),e.collapse(!0)}}return j(e,t.call(this,i)),e.endOffset<e.endContainer.childNodes.length&&F(e.endContainer.childNodes[e.endOffset],n),F(e.startContainer.childNodes[e.startOffset],n),this._getRangeAndRemoveBookmark(e),this.setSelection(e),this._updatePath(e,!0),this}setTextAlignment(t){return this.forEachBlock(e=>{let n=e.className.split(/\s+/).filter(i=>!!i&&!/^align/.test(i)).join(" ");t?(e.className=n+" align-"+t,e.style.textAlign=t):(e.className=n,e.style.textAlign="")},!0),this.focus()}setTextDirection(t){return this.forEachBlock(e=>{t?e.dir=t:e.removeAttribute("dir")},!0),this.focus()}_getListSelection(t,e){let n=t.commonAncestorContainer,i=t.startContainer,s=t.endContainer;for(;n&&n!==e&&!/^[OU]L$/.test(n.nodeName);)n=n.parentNode;if(!n||n===e)return null;for(i===n&&(i=i.childNodes[t.startOffset]),s===n&&(s=s.childNodes[t.endOffset]);i&&i.parentNode!==n;)i=i.parentNode;for(;s&&s.parentNode!==n;)s=s.parentNode;return[n,i,s]}increaseListLevel(t){t||(t=this.getSelection());let e=this._root,n=this._getListSelection(t,e);if(!n)return this.focus();let[i,s,r]=n;if(!s||s===i.firstChild)return this.focus();this._recordUndoState(t,this._isInUndoState);let l=i.nodeName,a=s.previousSibling,d,c;a.nodeName!==l&&(d=this._config.tagAttributes[l.toLowerCase()],a=p(l,d),i.insertBefore(a,s));do c=s===r?null:s.nextSibling,a.appendChild(s);while(s=c);return c=a.nextSibling,c&&F(c,e),this._getRangeAndRemoveBookmark(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}decreaseListLevel(t){t||(t=this.getSelection());let e=this._root,n=this._getListSelection(t,e);if(!n)return this.focus();let[i,s,r]=n;s||(s=i.firstChild),r||(r=i.lastChild),this._recordUndoState(t,this._isInUndoState);let l,a=null;if(s){let d=i.parentNode;if(a=r.nextSibling?w(i,r.nextSibling,d,e):i.nextSibling,d!==e&&d.nodeName==="LI"){for(d=d.parentNode;a;)l=a.nextSibling,r.appendChild(a),a=l;a=i.parentNode.nextSibling}let c=!/^[OU]L$/.test(d.nodeName);do l=s===r?null:s.nextSibling,i.removeChild(s),c&&s.nodeName==="LI"&&(s=this.createDefaultBlock([_(s)])),d.insertBefore(s,a);while(s=l)}return i.firstChild||E(i),a&&F(a,e),this._getRangeAndRemoveBookmark(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}_makeList(t,e){let n=ae(t,this._root),i=this._config.tagAttributes,s=i[e.toLowerCase()],r=i.li,l;for(;l=n.nextNode();)if(l.parentNode instanceof HTMLLIElement&&(l=l.parentNode,n.currentNode=l.lastChild),l instanceof HTMLLIElement){l=l.parentNode;let a=l.nodeName;a!==e&&/^[OU]L$/.test(a)&&R(l,p(e,s,[_(l)]))}else{let a=p("LI",r);l.dir&&(a.dir=l.dir);let d=l.previousSibling;d&&d.nodeName===e?(d.appendChild(a),E(l)):R(l,p(e,s,[a])),a.appendChild(_(l)),n.currentNode=a}return t}makeUnorderedList(){return this.modifyBlocks(t=>this._makeList(t,"UL")),this.focus()}makeOrderedList(){return this.modifyBlocks(t=>this._makeList(t,"OL")),this.focus()}removeList(){return this.modifyBlocks(t=>{let e=t.querySelectorAll("UL, OL"),n=t.querySelectorAll("LI"),i=this._root;for(let s=0,r=e.length;s<r;s+=1){let l=e[s],a=_(l);D(a,i),R(l,a)}for(let s=0,r=n.length;s<r;s+=1){let l=n[s];P(l)?R(l,this.createDefaultBlock([_(l)])):(D(l,i),R(l,_(l)))}return t}),this.focus()}increaseQuoteLevel(t){return this.modifyBlocks(e=>p("BLOCKQUOTE",this._config.tagAttributes.blockquote,[e]),t),this.focus()}decreaseQuoteLevel(t){return this.modifyBlocks(e=>(Array.from(e.querySelectorAll("blockquote")).filter(n=>!S(n.parentNode,e,"BLOCKQUOTE")).forEach(n=>{R(n,_(n))}),e),t),this.focus()}removeQuote(t){return this.modifyBlocks(()=>this.createDefaultBlock([p("INPUT",{id:this.startSelectionId,type:"hidden"}),p("INPUT",{id:this.endSelectionId,type:"hidden"})]),t),this.focus()}code(){let t=this.getSelection();return t.collapsed||Q(t.commonAncestorContainer)?(this.modifyBlocks(e=>{let n=this._root,i=document.createDocumentFragment(),s=ae(e,n),r;for(;r=s.nextNode();){let a=r.querySelectorAll("BR"),d=[],c=a.length;for(let f=0;f<c;f+=1)d[f]=ee(a[f],!1);for(;c--;){let f=a[c];d[c]?R(f,document.createTextNode(`
+`)):E(f)}for(a=r.querySelectorAll("CODE"),c=a.length;c--;)R(a[c],_(a[c]));i.childNodes.length&&i.appendChild(document.createTextNode(`
+`)),i.appendChild(_(r))}let l=new C(i,4);for(;r=l.nextNode();)r.data=r.data.replace(/ /g," ");return i.normalize(),x(p("PRE",this._config.tagAttributes.pre,[i]))},t),this.focus()):this.changeFormat({tag:"CODE",attributes:this._config.tagAttributes.code},null,t),this}removeCode(){let t=this.getSelection(),e=t.commonAncestorContainer;return S(e,this._root,"PRE")?(this.modifyBlocks(i=>{let s=this._root,r=i.querySelectorAll("PRE"),l=r.length;for(;l--;){let a=r[l],d=new C(a,4),c;for(;c=d.nextNode();){let f=c.data;f=f.replace(/ (?= )/g,"\xA0");let u=document.createDocumentFragment(),m;for(;(m=f.indexOf(`
+`))>-1;)u.appendChild(document.createTextNode(f.slice(0,m))),u.appendChild(p("BR")),f=f.slice(m+1);c.parentNode.insertBefore(u,c),c.data=f}D(a,s),R(a,_(a))}return i},t),this.focus()):this.changeFormat(null,{tag:"CODE"},t),this}toggleCode(){return this.hasFormat("PRE")||this.hasFormat("CODE")?this.removeCode():this.code(),this}_removeFormatting(t,e){for(let n=t.firstChild,i;n;n=i){if(i=n.nextSibling,N(n)){if(n instanceof Text||n.nodeName==="BR"||n.nodeName==="IMG"){e.appendChild(n);continue}}else if(P(n)){e.appendChild(this.createDefaultBlock([this._removeFormatting(n,document.createDocumentFragment())]));continue}this._removeFormatting(n,e)}return e}removeAllFormatting(t){if(t||(t=this.getSelection()),t.collapsed)return this.focus();let e=this._root,n=t.commonAncestorContainer;for(;n&&!P(n);)n=n.parentNode;if(n||(ye(t,e),n=e),n instanceof Text)return this.focus();this.saveUndoState(t),q(t,n,n,e);let i=t.startContainer,s=t.startOffset,r=t.endContainer,l=t.endOffset,a=document.createDocumentFragment(),d=document.createDocumentFragment(),c=w(r,l,n,e),f=w(i,s,n,e),u;for(;f!==c;)u=f.nextSibling,a.appendChild(f),f=u;if(this._removeFormatting(a,d),d.normalize(),f=d.firstChild,u=d.lastChild,f){n.insertBefore(d,c);let m=Array.from(n.childNodes);s=m.indexOf(f),l=u?m.indexOf(u)+1:0}else c&&(s=Array.from(n.childNodes).indexOf(c),l=s);return t.setStart(n,s),t.setEnd(n,l),te(n,t),T(t),this.setSelection(t),this._updatePath(t,!0),this.focus()}};var Mo=Ce;export{Mo as default};
 //# sourceMappingURL=squire.mjs.map
diff --git a/dist/squire.mjs.map b/dist/squire.mjs.map
index be45103..9c087c2 100644
--- a/dist/squire.mjs.map
+++ b/dist/squire.mjs.map
@@ -1,7 +1,7 @@
 {
   "version": 3,
   "sources": ["../source/node/TreeIterator.ts", "../source/Constants.ts", "../source/node/Category.ts", "../source/node/Node.ts", "../source/node/Whitespace.ts", "../source/range/Boundaries.ts", "../source/node/MergeSplit.ts", "../source/Clean.ts", "../source/node/Block.ts", "../source/range/Block.ts", "../source/range/InsertDelete.ts", "../source/range/Contents.ts", "../source/Clipboard.ts", "../source/keyboard/Enter.ts", "../source/keyboard/KeyHelpers.ts", "../source/keyboard/Backspace.ts", "../source/keyboard/Delete.ts", "../source/keyboard/Tab.ts", "../source/keyboard/Space.ts", "../source/keyboard/KeyHandlers.ts", "../source/Editor.ts", "../source/Squire.ts"],
-  "sourcesContent": ["type NODE_TYPE = 1 | 4 | 5;\nconst SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;\nconst SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;\nconst SHOW_ELEMENT_OR_TEXT = 5; // SHOW_ELEMENT|SHOW_TEXT;\n\nconst always = (): true => true;\n\nclass TreeIterator<T extends Node> {\n    root: Node;\n    currentNode: Node;\n    nodeType: NODE_TYPE;\n    filter: (n: T) => boolean;\n\n    constructor(root: Node, nodeType: NODE_TYPE, filter?: (n: T) => boolean) {\n        this.root = root;\n        this.currentNode = root;\n        this.nodeType = nodeType;\n        this.filter = filter || always;\n    }\n\n    isAcceptableNode(node: Node): boolean {\n        const nodeType = node.nodeType;\n        const nodeFilterType =\n            nodeType === Node.ELEMENT_NODE\n                ? SHOW_ELEMENT\n                : nodeType === Node.TEXT_NODE\n                ? SHOW_TEXT\n                : 0;\n        return !!(nodeFilterType & this.nodeType) && this.filter(node as T);\n    }\n\n    nextNode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            node = current.firstChild;\n            while (!node && current) {\n                if (current === root) {\n                    break;\n                }\n                node = current.nextSibling;\n                if (!node) {\n                    current = current.parentNode;\n                }\n            }\n            if (!node) {\n                return null;\n            }\n\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n\n    previousNode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            if (current === root) {\n                return null;\n            }\n            node = current.previousSibling;\n            if (node) {\n                while ((current = node.lastChild)) {\n                    node = current;\n                }\n            } else {\n                node = current.parentNode;\n            }\n            if (!node) {\n                return null;\n            }\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n\n    // Previous node in post-order.\n    previousPONode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            node = current.lastChild;\n            while (!node && current) {\n                if (current === root) {\n                    break;\n                }\n                node = current.previousSibling;\n                if (!node) {\n                    current = current.parentNode;\n                }\n            }\n            if (!node) {\n                return null;\n            }\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n}\n\n// ---\n\nexport { TreeIterator, SHOW_ELEMENT, SHOW_TEXT, SHOW_ELEMENT_OR_TEXT };\n", "const DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING\nconst ELEMENT_NODE = 1; // Node.ELEMENT_NODE;\nconst TEXT_NODE = 3; // Node.TEXT_NODE;\nconst DOCUMENT_NODE = 9; // Node.DOCUMENT_NODE;\nconst DOCUMENT_FRAGMENT_NODE = 11; // Node.DOCUMENT_FRAGMENT_NODE;\n\nconst ZWS = '\\u200B';\n\nconst ua = navigator.userAgent;\n\nconst isMac = /Mac OS X/.test(ua);\nconst isWin = /Windows NT/.test(ua);\nconst isIOS =\n    /iP(?:ad|hone|od)/.test(ua) || (isMac && !!navigator.maxTouchPoints);\nconst isAndroid = /Android/.test(ua);\n\nconst isGecko = /Gecko\\//.test(ua);\nconst isLegacyEdge = /Edge\\//.test(ua);\nconst isWebKit = !isLegacyEdge && /WebKit\\//.test(ua);\n\nconst ctrlKey = isMac || isIOS ? 'Meta-' : 'Ctrl-';\n\nconst cantFocusEmptyTextNodes = isWebKit;\n\nconst supportsInputEvents =\n    'onbeforeinput' in document && 'inputType' in new InputEvent('input');\n\n// Use [^ \\t\\r\\n] instead of \\S so that nbsp does not count as white-space\nconst notWS = /[^ \\t\\r\\n]/;\n\n// ---\n\nexport {\n    DOCUMENT_POSITION_PRECEDING,\n    ELEMENT_NODE,\n    TEXT_NODE,\n    DOCUMENT_NODE,\n    DOCUMENT_FRAGMENT_NODE,\n    notWS,\n    ZWS,\n    ua,\n    isMac,\n    isWin,\n    isIOS,\n    isAndroid,\n    isGecko,\n    isLegacyEdge,\n    isWebKit,\n    ctrlKey,\n    cantFocusEmptyTextNodes,\n    supportsInputEvents,\n};\n", "import { ELEMENT_NODE, TEXT_NODE, DOCUMENT_FRAGMENT_NODE } from '../Constants';\n\n// ---\n\nconst inlineNodeNames =\n    /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/;\n\nconst leafNodeNames = new Set(['BR', 'HR', 'IFRAME', 'IMG', 'INPUT']);\n\nconst UNKNOWN = 0;\nconst INLINE = 1;\nconst BLOCK = 2;\nconst CONTAINER = 3;\n\n// ---\n\nlet cache: WeakMap<Node, number> = new WeakMap();\n\nconst resetNodeCategoryCache = (): void => {\n    cache = new WeakMap();\n};\n\n// ---\n\nconst isLeaf = (node: Node): boolean => {\n    return leafNodeNames.has(node.nodeName);\n};\n\nconst getNodeCategory = (node: Node): number => {\n    switch (node.nodeType) {\n        case TEXT_NODE:\n            return INLINE;\n        case ELEMENT_NODE:\n        case DOCUMENT_FRAGMENT_NODE:\n            if (cache.has(node)) {\n                return cache.get(node) as number;\n            }\n            break;\n        default:\n            return UNKNOWN;\n    }\n\n    let nodeCategory: number;\n    if (!Array.from(node.childNodes).every(isInline)) {\n        // Malformed HTML can have block tags inside inline tags. Need to treat\n        // these as containers rather than inline. See #239.\n        nodeCategory = CONTAINER;\n    } else if (inlineNodeNames.test(node.nodeName)) {\n        nodeCategory = INLINE;\n    } else {\n        nodeCategory = BLOCK;\n    }\n    cache.set(node, nodeCategory);\n    return nodeCategory;\n};\n\nconst isInline = (node: Node): boolean => {\n    return getNodeCategory(node) === INLINE;\n};\n\nconst isBlock = (node: Node): boolean => {\n    return getNodeCategory(node) === BLOCK;\n};\n\nconst isContainer = (node: Node): boolean => {\n    return getNodeCategory(node) === CONTAINER;\n};\n\n// ---\n\nexport {\n    getNodeCategory,\n    isBlock,\n    isContainer,\n    isInline,\n    isLeaf,\n    leafNodeNames,\n    resetNodeCategoryCache,\n};\n", "import { isLeaf } from './Category';\n\n// ---\n\nconst createElement = (\n    tag: string,\n    props?: Record<string, string> | null,\n    children?: Node[],\n): HTMLElement => {\n    const el = document.createElement(tag);\n    if (props instanceof Array) {\n        children = props;\n        props = null;\n    }\n    if (props) {\n        for (const attr in props) {\n            const value = props[attr];\n            if (value !== undefined) {\n                el.setAttribute(attr, value);\n            }\n        }\n    }\n    if (children) {\n        children.forEach((node) => el.appendChild(node));\n    }\n    return el;\n};\n\n// --- Tests\n\nconst areAlike = (\n    node: HTMLElement | Node,\n    node2: HTMLElement | Node,\n): boolean => {\n    if (isLeaf(node)) {\n        return false;\n    }\n    if (node.nodeType !== node2.nodeType || node.nodeName !== node2.nodeName) {\n        return false;\n    }\n    if (node instanceof HTMLElement && node2 instanceof HTMLElement) {\n        return (\n            node.nodeName !== 'A' &&\n            node.className === node2.className &&\n            node.style.cssText === node2.style.cssText\n        );\n    }\n    return true;\n};\n\nconst hasTagAttributes = (\n    node: Node | Element,\n    tag: string,\n    attributes?: Record<string, string> | null,\n): boolean => {\n    if (node.nodeName !== tag) {\n        return false;\n    }\n    for (const attr in attributes) {\n        if (\n            !('getAttribute' in node) ||\n            node.getAttribute(attr) !== attributes[attr]\n        ) {\n            return false;\n        }\n    }\n    return true;\n};\n\n// --- Traversal\n\nconst getNearest = (\n    node: Node | null,\n    root: Element | DocumentFragment,\n    tag: string,\n    attributes?: Record<string, string> | null,\n): Node | null => {\n    while (node && node !== root) {\n        if (hasTagAttributes(node, tag, attributes)) {\n            return node;\n        }\n        node = node.parentNode;\n    }\n    return null;\n};\n\nconst getNodeBeforeOffset = (node: Node, offset: number): Node => {\n    let children = node.childNodes;\n    while (offset && node instanceof Element) {\n        node = children[offset - 1];\n        children = node.childNodes;\n        offset = children.length;\n    }\n    return node;\n};\n\nconst getNodeAfterOffset = (node: Node, offset: number): Node | null => {\n    let returnNode: Node | null = node;\n    if (returnNode instanceof Element) {\n        const children = returnNode.childNodes;\n        if (offset < children.length) {\n            returnNode = children[offset];\n        } else {\n            while (returnNode && !returnNode.nextSibling) {\n                returnNode = returnNode.parentNode;\n            }\n            if (returnNode) {\n                returnNode = returnNode.nextSibling;\n            }\n        }\n    }\n    return returnNode;\n};\n\nconst getLength = (node: Node): number => {\n    return node instanceof Element || node instanceof DocumentFragment\n        ? node.childNodes.length\n        : node instanceof CharacterData\n        ? node.length\n        : 0;\n};\n\n// --- Manipulation\n\nconst empty = (node: Node): DocumentFragment => {\n    const frag = document.createDocumentFragment();\n    let child = node.firstChild;\n    while (child) {\n        frag.appendChild(child);\n        child = node.firstChild;\n    }\n    return frag;\n};\n\nconst detach = (node: Node): Node => {\n    const parent = node.parentNode;\n    if (parent) {\n        parent.removeChild(node);\n    }\n    return node;\n};\n\nconst replaceWith = (node: Node, node2: Node): void => {\n    const parent = node.parentNode;\n    if (parent) {\n        parent.replaceChild(node2, node);\n    }\n};\n\n// --- Export\n\nexport {\n    areAlike,\n    createElement,\n    detach,\n    empty,\n    getLength,\n    getNearest,\n    getNodeAfterOffset,\n    getNodeBeforeOffset,\n    hasTagAttributes,\n    replaceWith,\n};\n", "import { ZWS, notWS } from '../Constants';\nimport { isInline } from './Category';\nimport { getLength } from './Node';\nimport { SHOW_ELEMENT_OR_TEXT, SHOW_TEXT, TreeIterator } from './TreeIterator';\n\n// ---\n\nconst notWSTextNode = (node: Node): boolean => {\n    return node instanceof Element\n        ? node.nodeName === 'BR'\n        : // okay if data is 'undefined' here.\n          notWS.test((node as CharacterData).data);\n};\n\nconst isLineBreak = (br: Element, isLBIfEmptyBlock: boolean): boolean => {\n    let block = br.parentNode!;\n    while (isInline(block)) {\n        block = block.parentNode!;\n    }\n    const walker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        notWSTextNode,\n    );\n    walker.currentNode = br;\n    return !!walker.nextNode() || (isLBIfEmptyBlock && !walker.previousNode());\n};\n\n// --- Workaround for browsers that can't focus empty text nodes\n\n// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256\n\n// Walk down the tree starting at the root and remove any ZWS. If the node only\n// contained ZWS space then remove it too. We may want to keep one ZWS node at\n// the bottom of the tree so the block can be selected. Define that node as the\n// keepNode.\nconst removeZWS = (root: Node, keepNode?: Node | null): void => {\n    const walker = new TreeIterator<Text>(root, SHOW_TEXT);\n    let textNode: Text | null;\n    let index: number;\n    while ((textNode = walker.nextNode())) {\n        while (\n            (index = textNode.data.indexOf(ZWS)) > -1 &&\n            // eslint-disable-next-line no-unmodified-loop-condition\n            (!keepNode || textNode.parentNode !== keepNode)\n        ) {\n            if (textNode.length === 1) {\n                let node: Node = textNode;\n                let parent = node.parentNode;\n                while (parent) {\n                    parent.removeChild(node);\n                    walker.currentNode = parent;\n                    if (!isInline(parent) || getLength(parent)) {\n                        break;\n                    }\n                    node = parent;\n                    parent = node.parentNode;\n                }\n                break;\n            } else {\n                textNode.deleteData(index, 1);\n            }\n        }\n    }\n};\n\n// ---\n\nexport { isLineBreak, removeZWS };\n", "import { isLeaf } from '../node/Category';\nimport { getLength, getNearest } from '../node/Node';\nimport { isLineBreak } from '../node/Whitespace';\nimport { TEXT_NODE } from '../Constants';\n\n// ---\n\nconst START_TO_START = 0; // Range.START_TO_START\nconst START_TO_END = 1; // Range.START_TO_END\nconst END_TO_END = 2; // Range.END_TO_END\nconst END_TO_START = 3; // Range.END_TO_START\n\nconst isNodeContainedInRange = (\n    range: Range,\n    node: Node,\n    partial: boolean,\n): boolean => {\n    const nodeRange = document.createRange();\n    nodeRange.selectNode(node);\n    if (partial) {\n        // Node must not finish before range starts or start after range\n        // finishes.\n        const nodeEndBeforeStart =\n            range.compareBoundaryPoints(END_TO_START, nodeRange) > -1;\n        const nodeStartAfterEnd =\n            range.compareBoundaryPoints(START_TO_END, nodeRange) < 1;\n        return !nodeEndBeforeStart && !nodeStartAfterEnd;\n    } else {\n        // Node must start after range starts and finish before range\n        // finishes\n        const nodeStartAfterStart =\n            range.compareBoundaryPoints(START_TO_START, nodeRange) < 1;\n        const nodeEndBeforeEnd =\n            range.compareBoundaryPoints(END_TO_END, nodeRange) > -1;\n        return nodeStartAfterStart && nodeEndBeforeEnd;\n    }\n};\n\n/**\n * Moves the range to an equivalent position with the start/end as deep in\n * the tree as possible.\n */\nconst moveRangeBoundariesDownTree = (range: Range): void => {\n    let { startContainer, startOffset, endContainer, endOffset } = range;\n\n    while (!(startContainer instanceof Text)) {\n        let child: ChildNode | null = startContainer.childNodes[startOffset];\n        if (!child || isLeaf(child)) {\n            if (startOffset) {\n                child = startContainer.childNodes[startOffset - 1];\n                if (child instanceof Text) {\n                    // Need a new variable to satisfy TypeScript's type checker\n                    // for some reason.\n                    let textChild: Text = child;\n                    // If we have an empty text node next to another text node,\n                    // just skip and remove it.\n                    let prev: ChildNode | null;\n                    while (\n                        !textChild.length &&\n                        (prev = textChild.previousSibling) &&\n                        prev instanceof Text\n                    ) {\n                        textChild.remove();\n                        textChild = prev;\n                    }\n                    startContainer = textChild;\n                    startOffset = textChild.data.length;\n                }\n            }\n            break;\n        }\n        startContainer = child;\n        startOffset = 0;\n    }\n    if (endOffset) {\n        while (!(endContainer instanceof Text)) {\n            const child = endContainer.childNodes[endOffset - 1];\n            if (!child || isLeaf(child)) {\n                if (\n                    child &&\n                    child.nodeName === 'BR' &&\n                    !isLineBreak(child as Element, false)\n                ) {\n                    endOffset -= 1;\n                    continue;\n                }\n                break;\n            }\n            endContainer = child;\n            endOffset = getLength(endContainer);\n        }\n    } else {\n        while (!(endContainer instanceof Text)) {\n            const child = endContainer.firstChild!;\n            if (!child || isLeaf(child)) {\n                break;\n            }\n            endContainer = child;\n        }\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\nconst moveRangeBoundariesUpTree = (\n    range: Range,\n    startMax: Node,\n    endMax: Node,\n    root: Node,\n): void => {\n    let startContainer = range.startContainer;\n    let startOffset = range.startOffset;\n    let endContainer = range.endContainer;\n    let endOffset = range.endOffset;\n    let parent: Node;\n\n    if (!startMax) {\n        startMax = range.commonAncestorContainer;\n    }\n    if (!endMax) {\n        endMax = startMax;\n    }\n\n    while (\n        !startOffset &&\n        startContainer !== startMax &&\n        startContainer !== root\n    ) {\n        parent = startContainer.parentNode!;\n        startOffset = Array.from(parent.childNodes).indexOf(\n            startContainer as ChildNode,\n        );\n        startContainer = parent;\n    }\n\n    while (true) {\n        if (endContainer === endMax || endContainer === root) {\n            break;\n        }\n        if (\n            endContainer.nodeType !== TEXT_NODE &&\n            endContainer.childNodes[endOffset] &&\n            endContainer.childNodes[endOffset].nodeName === 'BR' &&\n            !isLineBreak(endContainer.childNodes[endOffset] as Element, false)\n        ) {\n            endOffset += 1;\n        }\n        if (endOffset !== getLength(endContainer)) {\n            break;\n        }\n        parent = endContainer.parentNode!;\n        endOffset =\n            Array.from(parent.childNodes).indexOf(endContainer as ChildNode) +\n            1;\n        endContainer = parent;\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\nconst moveRangeBoundaryOutOf = (\n    range: Range,\n    tag: string,\n    root: Element,\n): Range => {\n    let parent = getNearest(range.endContainer, root, tag);\n    if (parent && (parent = parent.parentNode)) {\n        const clone = range.cloneRange();\n        moveRangeBoundariesUpTree(clone, parent, parent, root);\n        if (clone.endContainer === parent) {\n            range.setStart(clone.endContainer, clone.endOffset);\n            range.setEnd(clone.endContainer, clone.endOffset);\n        }\n    }\n    return range;\n};\n\n// ---\n\nexport {\n    isNodeContainedInRange,\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n    moveRangeBoundaryOutOf,\n};\n", "import { ZWS, cantFocusEmptyTextNodes } from '../Constants';\nimport {\n    createElement,\n    getNearest,\n    areAlike,\n    getLength,\n    detach,\n    empty,\n} from './Node';\nimport { isInline, isContainer } from './Category';\n\n// ---\n\nconst fixCursor = (node: Node): Node => {\n    // In Webkit and Gecko, block level elements are collapsed and\n    // unfocusable if they have no content. To remedy this, a <BR> must be\n    // inserted. In Opera and IE, we just need a textnode in order for the\n    // cursor to appear.\n    let fixer: Element | Text | null = null;\n\n    if (node instanceof Text) {\n        return node;\n    }\n\n    if (isInline(node)) {\n        let child = node.firstChild;\n        if (cantFocusEmptyTextNodes) {\n            while (child && child instanceof Text && !child.data) {\n                node.removeChild(child);\n                child = node.firstChild;\n            }\n        }\n        if (!child) {\n            if (cantFocusEmptyTextNodes) {\n                fixer = document.createTextNode(ZWS);\n            } else {\n                fixer = document.createTextNode('');\n            }\n        }\n    } else if (\n        (node instanceof Element || node instanceof DocumentFragment) &&\n        !node.querySelector('BR')\n    ) {\n        fixer = createElement('BR');\n        let parent: Element | DocumentFragment = node;\n        let child: Element | null;\n        while ((child = parent.lastElementChild) && !isInline(child)) {\n            parent = child;\n        }\n        node = parent;\n    }\n    if (fixer) {\n        try {\n            node.appendChild(fixer);\n        } catch (error) {}\n    }\n\n    return node;\n};\n\n// Recursively examine container nodes and wrap any inline children.\nconst fixContainer = (\n    container: Node,\n    root: Element | DocumentFragment,\n): Node => {\n    let wrapper: HTMLElement | null = null;\n    Array.from(container.childNodes).forEach((child) => {\n        const isBR = child.nodeName === 'BR';\n        if (!isBR && isInline(child)) {\n            if (!wrapper) {\n                wrapper = createElement('DIV');\n            }\n            wrapper.appendChild(child);\n        } else if (isBR || wrapper) {\n            if (!wrapper) {\n                wrapper = createElement('DIV');\n            }\n            fixCursor(wrapper);\n            if (isBR) {\n                container.replaceChild(wrapper, child);\n            } else {\n                container.insertBefore(wrapper, child);\n            }\n            wrapper = null;\n        }\n        if (isContainer(child)) {\n            fixContainer(child, root);\n        }\n    });\n    if (wrapper) {\n        container.appendChild(fixCursor(wrapper));\n    }\n    return container;\n};\n\nconst split = (\n    node: Node,\n    offset: number | Node | null,\n    stopNode: Node,\n    root: Element | DocumentFragment,\n): Node | null => {\n    if (node instanceof Text && node !== stopNode) {\n        if (typeof offset !== 'number') {\n            throw new Error('Offset must be a number to split text node!');\n        }\n        if (!node.parentNode) {\n            throw new Error('Cannot split text node with no parent!');\n        }\n        return split(node.parentNode, node.splitText(offset), stopNode, root);\n    }\n\n    let nodeAfterSplit: Node | null =\n        typeof offset === 'number'\n            ? offset < node.childNodes.length\n                ? node.childNodes[offset]\n                : null\n            : offset;\n    const parent = node.parentNode;\n    if (!parent || node === stopNode || !(node instanceof Element)) {\n        return nodeAfterSplit;\n    }\n\n    // Clone node without children\n    const clone = node.cloneNode(false) as Element;\n\n    // Add right-hand siblings to the clone\n    while (nodeAfterSplit) {\n        const next = nodeAfterSplit.nextSibling;\n        clone.appendChild(nodeAfterSplit);\n        nodeAfterSplit = next;\n    }\n\n    // Maintain li numbering if inside a quote.\n    if (\n        node instanceof HTMLOListElement &&\n        getNearest(node, root, 'BLOCKQUOTE')\n    ) {\n        (clone as HTMLOListElement).start =\n            (+node.start || 1) + node.childNodes.length - 1;\n    }\n\n    // DO NOT NORMALISE. This may undo the fixCursor() call\n    // of a node lower down the tree!\n    // We need something in the element in order for the cursor to appear.\n    fixCursor(node);\n    fixCursor(clone);\n\n    // Inject clone after original node\n    parent.insertBefore(clone, node.nextSibling);\n\n    // Keep on splitting up the tree\n    return split(parent, clone, stopNode, root);\n};\n\nconst _mergeInlines = (\n    node: Node,\n    fakeRange: {\n        startContainer: Node;\n        startOffset: number;\n        endContainer: Node;\n        endOffset: number;\n    },\n): void => {\n    const children = node.childNodes;\n    let l = children.length;\n    const frags: DocumentFragment[] = [];\n    while (l--) {\n        const child = children[l];\n        const prev = l ? children[l - 1] : null;\n        if (prev && isInline(child) && areAlike(child, prev)) {\n            if (fakeRange.startContainer === child) {\n                fakeRange.startContainer = prev;\n                fakeRange.startOffset += getLength(prev);\n            }\n            if (fakeRange.endContainer === child) {\n                fakeRange.endContainer = prev;\n                fakeRange.endOffset += getLength(prev);\n            }\n            if (fakeRange.startContainer === node) {\n                if (fakeRange.startOffset > l) {\n                    fakeRange.startOffset -= 1;\n                } else if (fakeRange.startOffset === l) {\n                    fakeRange.startContainer = prev;\n                    fakeRange.startOffset = getLength(prev);\n                }\n            }\n            if (fakeRange.endContainer === node) {\n                if (fakeRange.endOffset > l) {\n                    fakeRange.endOffset -= 1;\n                } else if (fakeRange.endOffset === l) {\n                    fakeRange.endContainer = prev;\n                    fakeRange.endOffset = getLength(prev);\n                }\n            }\n            detach(child);\n            if (child instanceof Text) {\n                (prev as Text).appendData(child.data);\n            } else {\n                frags.push(empty(child));\n            }\n        } else if (child instanceof Element) {\n            let frag: DocumentFragment | undefined;\n            while ((frag = frags.pop())) {\n                child.appendChild(frag);\n            }\n            _mergeInlines(child, fakeRange);\n        }\n    }\n};\n\nconst mergeInlines = (node: Node, range: Range): void => {\n    const element = node instanceof Text ? node.parentNode : node;\n    if (element instanceof Element) {\n        const fakeRange = {\n            startContainer: range.startContainer,\n            startOffset: range.startOffset,\n            endContainer: range.endContainer,\n            endOffset: range.endOffset,\n        };\n        _mergeInlines(element, fakeRange);\n        range.setStart(fakeRange.startContainer, fakeRange.startOffset);\n        range.setEnd(fakeRange.endContainer, fakeRange.endOffset);\n    }\n};\n\nconst mergeWithBlock = (\n    block: Node,\n    next: Node,\n    range: Range,\n    root: Element,\n): void => {\n    let container = next;\n    let parent: Node | null;\n    let offset: number;\n    while (\n        (parent = container.parentNode) &&\n        parent !== root &&\n        parent instanceof Element &&\n        parent.childNodes.length === 1\n    ) {\n        container = parent;\n    }\n    detach(container);\n\n    offset = block.childNodes.length;\n\n    // Remove extra <BR> fixer if present.\n    const last = block.lastChild;\n    if (last && last.nodeName === 'BR') {\n        block.removeChild(last);\n        offset -= 1;\n    }\n\n    block.appendChild(empty(next));\n\n    range.setStart(block, offset);\n    range.collapse(true);\n    mergeInlines(block, range);\n};\n\nconst mergeContainers = (node: Node, root: Element): void => {\n    const prev = node.previousSibling;\n    const first = node.firstChild;\n    const isListItem = node.nodeName === 'LI';\n\n    // Do not merge LIs, unless it only contains a UL\n    if (isListItem && (!first || !/^[OU]L$/.test(first.nodeName))) {\n        return;\n    }\n\n    if (prev && areAlike(prev, node)) {\n        if (!isContainer(prev)) {\n            if (isListItem) {\n                const block = createElement('DIV');\n                block.appendChild(empty(prev));\n                prev.appendChild(block);\n            } else {\n                return;\n            }\n        }\n        detach(node);\n        const needsFix = !isContainer(node);\n        prev.appendChild(empty(node));\n        if (needsFix) {\n            fixContainer(prev, root);\n        }\n        if (first) {\n            mergeContainers(first, root);\n        }\n    } else if (isListItem) {\n        const block = createElement('DIV');\n        node.insertBefore(block, first);\n        fixCursor(block);\n    }\n};\n\n// ---\n\nexport {\n    fixContainer,\n    fixCursor,\n    mergeContainers,\n    mergeInlines,\n    mergeWithBlock,\n    split,\n};\n", "import { notWS } from './Constants';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from './node/TreeIterator';\nimport { createElement, empty, detach, replaceWith } from './node/Node';\nimport { isInline, isLeaf } from './node/Category';\nimport { fixContainer } from './node/MergeSplit';\nimport { isLineBreak } from './node/Whitespace';\n\nimport type { SquireConfig } from './Editor';\n\n// ---\n\ntype StyleRewriter = (\n    node: HTMLElement,\n    parent: Node,\n    config: SquireConfig,\n) => HTMLElement;\n\n// ---\n\nconst styleToSemantic: Record<\n    string,\n    { regexp: RegExp; replace: (x: any, y: string) => HTMLElement }\n> = {\n    'font-weight': {\n        regexp: /^bold|^700/i,\n        replace(): HTMLElement {\n            return createElement('B');\n        },\n    },\n    'font-style': {\n        regexp: /^italic/i,\n        replace(): HTMLElement {\n            return createElement('I');\n        },\n    },\n    'font-family': {\n        regexp: notWS,\n        replace(\n            classNames: { fontFamily: string },\n            family: string,\n        ): HTMLElement {\n            return createElement('SPAN', {\n                class: classNames.fontFamily,\n                style: 'font-family:' + family,\n            });\n        },\n    },\n    'font-size': {\n        regexp: notWS,\n        replace(classNames: { fontSize: string }, size: string): HTMLElement {\n            return createElement('SPAN', {\n                class: classNames.fontSize,\n                style: 'font-size:' + size,\n            });\n        },\n    },\n    'text-decoration': {\n        regexp: /^underline/i,\n        replace(): HTMLElement {\n            return createElement('U');\n        },\n    },\n};\n\nconst replaceStyles = (\n    node: HTMLElement,\n    _: Node,\n    config: SquireConfig,\n): HTMLElement => {\n    const style = node.style;\n    let newTreeBottom: HTMLElement | undefined;\n    let newTreeTop: HTMLElement | undefined;\n\n    for (const attr in styleToSemantic) {\n        const converter = styleToSemantic[attr];\n        const css = style.getPropertyValue(attr);\n        if (css && converter.regexp.test(css)) {\n            const el = converter.replace(config.classNames, css);\n            if (\n                el.nodeName === node.nodeName &&\n                el.className === node.className\n            ) {\n                continue;\n            }\n            if (!newTreeTop) {\n                newTreeTop = el;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(el);\n            }\n            newTreeBottom = el;\n            node.style.removeProperty(attr);\n        }\n    }\n\n    if (newTreeTop && newTreeBottom) {\n        newTreeBottom.appendChild(empty(node));\n        if (node.style.cssText) {\n            node.appendChild(newTreeTop);\n        } else {\n            replaceWith(node, newTreeTop);\n        }\n    }\n\n    return newTreeBottom || node;\n};\n\nconst replaceWithTag = (tag: string) => {\n    return (node: HTMLElement, parent: Node) => {\n        const el = createElement(tag);\n        const attributes = node.attributes;\n        for (let i = 0, l = attributes.length; i < l; i += 1) {\n            const attribute = attributes[i];\n            el.setAttribute(attribute.name, attribute.value);\n        }\n        parent.replaceChild(el, node);\n        el.appendChild(empty(node));\n        return el;\n    };\n};\n\nconst fontSizes: Record<string, string> = {\n    '1': '10',\n    '2': '13',\n    '3': '16',\n    '4': '18',\n    '5': '24',\n    '6': '32',\n    '7': '48',\n};\n\nconst stylesRewriters: Record<string, StyleRewriter> = {\n    STRONG: replaceWithTag('B'),\n    EM: replaceWithTag('I'),\n    INS: replaceWithTag('U'),\n    STRIKE: replaceWithTag('S'),\n    SPAN: replaceStyles,\n    FONT: (\n        node: HTMLElement,\n        parent: Node,\n        config: SquireConfig,\n    ): HTMLElement => {\n        const font = node as HTMLFontElement;\n        const face = font.face;\n        const size = font.size;\n        let color = font.color;\n        const classNames = config.classNames;\n        let fontSpan: HTMLElement;\n        let sizeSpan: HTMLElement;\n        let colorSpan: HTMLElement;\n        let newTreeBottom: HTMLElement | undefined;\n        let newTreeTop: HTMLElement | undefined;\n        if (face) {\n            fontSpan = createElement('SPAN', {\n                class: classNames.fontFamily,\n                style: 'font-family:' + face,\n            });\n            newTreeTop = fontSpan;\n            newTreeBottom = fontSpan;\n        }\n        if (size) {\n            sizeSpan = createElement('SPAN', {\n                class: classNames.fontSize,\n                style: 'font-size:' + fontSizes[size] + 'px',\n            });\n            if (!newTreeTop) {\n                newTreeTop = sizeSpan;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(sizeSpan);\n            }\n            newTreeBottom = sizeSpan;\n        }\n        if (color && /^#?([\\dA-F]{3}){1,2}$/i.test(color)) {\n            if (color.charAt(0) !== '#') {\n                color = '#' + color;\n            }\n            colorSpan = createElement('SPAN', {\n                class: classNames.color,\n                style: 'color:' + color,\n            });\n            if (!newTreeTop) {\n                newTreeTop = colorSpan;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(colorSpan);\n            }\n            newTreeBottom = colorSpan;\n        }\n        if (!newTreeTop || !newTreeBottom) {\n            newTreeTop = newTreeBottom = createElement('SPAN');\n        }\n        parent.replaceChild(newTreeTop, font);\n        newTreeBottom.appendChild(empty(font));\n        return newTreeBottom;\n    },\n    TT: (node: Node, parent: Node, config: SquireConfig): HTMLElement => {\n        const el = createElement('SPAN', {\n            class: config.classNames.fontFamily,\n            style: 'font-family:menlo,consolas,\"courier new\",monospace',\n        });\n        parent.replaceChild(el, node);\n        el.appendChild(empty(node));\n        return el;\n    },\n};\n\nconst allowedBlock =\n    /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/;\n\nconst blacklist = /^(?:HEAD|META|STYLE)/;\n\n/*\n    Two purposes:\n\n    1. Remove nodes we don't want, such as weird <o:p> tags, comment nodes\n       and whitespace nodes.\n    2. Convert inline tags into our preferred format.\n*/\nconst cleanTree = (\n    node: Node,\n    config: SquireConfig,\n    preserveWS?: boolean,\n): Node => {\n    const children = node.childNodes;\n\n    let nonInlineParent = node;\n    while (isInline(nonInlineParent)) {\n        nonInlineParent = nonInlineParent.parentNode!;\n    }\n    const walker = new TreeIterator<Element | Text>(\n        nonInlineParent,\n        SHOW_ELEMENT_OR_TEXT,\n    );\n\n    for (let i = 0, l = children.length; i < l; i += 1) {\n        let child = children[i];\n        const nodeName = child.nodeName;\n        const rewriter = stylesRewriters[nodeName];\n        if (child instanceof HTMLElement) {\n            const childLength = child.childNodes.length;\n            if (rewriter) {\n                child = rewriter(child, node, config);\n            } else if (blacklist.test(nodeName)) {\n                node.removeChild(child);\n                i -= 1;\n                l -= 1;\n                continue;\n            } else if (!allowedBlock.test(nodeName) && !isInline(child)) {\n                i -= 1;\n                l += childLength - 1;\n                node.replaceChild(empty(child), child);\n                continue;\n            }\n            if (childLength) {\n                cleanTree(child, config, preserveWS || nodeName === 'PRE');\n            }\n        } else {\n            if (child instanceof Text) {\n                let data = child.data;\n                const startsWithWS = !notWS.test(data.charAt(0));\n                const endsWithWS = !notWS.test(data.charAt(data.length - 1));\n                if (preserveWS || (!startsWithWS && !endsWithWS)) {\n                    continue;\n                }\n                // Iterate through the nodes; if we hit some other content\n                // before the start of a new block we don't trim\n                if (startsWithWS) {\n                    walker.currentNode = child;\n                    let sibling;\n                    while ((sibling = walker.previousPONode())) {\n                        if (\n                            sibling.nodeName === 'IMG' ||\n                            (sibling instanceof Text &&\n                                notWS.test(sibling.data))\n                        ) {\n                            break;\n                        }\n                        if (!isInline(sibling)) {\n                            sibling = null;\n                            break;\n                        }\n                    }\n                    data = data.replace(/^[ \\t\\r\\n]+/g, sibling ? ' ' : '');\n                }\n                if (endsWithWS) {\n                    walker.currentNode = child;\n                    let sibling;\n                    while ((sibling = walker.nextNode())) {\n                        if (\n                            sibling.nodeName === 'IMG' ||\n                            (sibling instanceof Text &&\n                                notWS.test(sibling.data))\n                        ) {\n                            break;\n                        }\n                        if (!isInline(sibling)) {\n                            sibling = null;\n                            break;\n                        }\n                    }\n                    data = data.replace(/[ \\t\\r\\n]+$/g, sibling ? ' ' : '');\n                }\n                if (data) {\n                    child.data = data;\n                    continue;\n                }\n            }\n            node.removeChild(child);\n            i -= 1;\n            l -= 1;\n        }\n    }\n    return node;\n};\n\n// ---\n\nconst removeEmptyInlines = (node: Node): void => {\n    const children = node.childNodes;\n    let l = children.length;\n    while (l--) {\n        const child = children[l];\n        if (child instanceof Element && !isLeaf(child)) {\n            removeEmptyInlines(child);\n            if (isInline(child) && !child.firstChild) {\n                node.removeChild(child);\n            }\n        } else if (child instanceof Text && !child.data) {\n            node.removeChild(child);\n        }\n    }\n};\n\n// ---\n\n// <br> elements are treated specially, and differently depending on the\n// browser, when in rich text editor mode. When adding HTML from external\n// sources, we must remove them, replacing the ones that actually affect\n// line breaks by wrapping the inline text in a <div>. Browsers that want <br>\n// elements at the end of each block will then have them added back in a later\n// fixCursor method call.\nconst cleanupBRs = (\n    node: Element | DocumentFragment,\n    root: Element,\n    keepForBlankLine: boolean,\n): void => {\n    const brs: NodeListOf<HTMLBRElement> = node.querySelectorAll('BR');\n    const brBreaksLine: boolean[] = [];\n    let l = brs.length;\n\n    // Must calculate whether the <br> breaks a line first, because if we\n    // have two <br>s next to each other, after the first one is converted\n    // to a block split, the second will be at the end of a block and\n    // therefore seem to not be a line break. But in its original context it\n    // was, so we should also convert it to a block split.\n    for (let i = 0; i < l; i += 1) {\n        brBreaksLine[i] = isLineBreak(brs[i], keepForBlankLine);\n    }\n    while (l--) {\n        const br = brs[l];\n        // Cleanup may have removed it\n        const parent = br.parentNode;\n        if (!parent) {\n            continue;\n        }\n        // If it doesn't break a line, just remove it; it's not doing\n        // anything useful. We'll add it back later if required by the\n        // browser. If it breaks a line, wrap the content in div tags\n        // and replace the brs.\n        if (!brBreaksLine[l]) {\n            detach(br);\n        } else if (!isInline(parent)) {\n            fixContainer(parent, root);\n        }\n    }\n};\n\n// ---\n\nconst escapeHTML = (text: string): string => {\n    return text\n        .split('&')\n        .join('&amp;')\n        .split('<')\n        .join('&lt;')\n        .split('>')\n        .join('&gt;')\n        .split('\"')\n        .join('&quot;');\n};\n\n// ---\n\nexport { cleanTree, cleanupBRs, isLineBreak, removeEmptyInlines, escapeHTML };\n", "import { TreeIterator, SHOW_ELEMENT } from './TreeIterator';\nimport { isBlock } from './Category';\n\n// ---\n\nconst getBlockWalker = (\n    node: Node,\n    root: Element | DocumentFragment,\n): TreeIterator<HTMLElement> => {\n    const walker = new TreeIterator<HTMLElement>(root, SHOW_ELEMENT, isBlock);\n    walker.currentNode = node;\n    return walker;\n};\n\nconst getPreviousBlock = (\n    node: Node,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const block = getBlockWalker(node, root).previousNode();\n    return block !== root ? block : null;\n};\n\nconst getNextBlock = (\n    node: Node,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const block = getBlockWalker(node, root).nextNode();\n    return block !== root ? block : null;\n};\n\nconst isEmptyBlock = (block: Element): boolean => {\n    return !block.textContent && !block.querySelector('IMG');\n};\n\n// ---\n\nexport { getBlockWalker, getPreviousBlock, getNextBlock, isEmptyBlock };\n", "import { isInline, isBlock } from '../node/Category';\nimport { getPreviousBlock, getNextBlock } from '../node/Block';\nimport { getNodeBeforeOffset, getNodeAfterOffset } from '../node/Node';\nimport { ZWS, notWS } from '../Constants';\nimport { isNodeContainedInRange } from './Boundaries';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from '../node/TreeIterator';\n\n// ---\n\n// Returns the first block at least partially contained by the range,\n// or null if no block is contained by the range.\nconst getStartBlockOfRange = (\n    range: Range,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const container = range.startContainer;\n    let block: HTMLElement | null;\n\n    // If inline, get the containing block.\n    if (isInline(container)) {\n        block = getPreviousBlock(container, root);\n    } else if (\n        container !== root &&\n        container instanceof HTMLElement &&\n        isBlock(container)\n    ) {\n        block = container;\n    } else {\n        const node = getNodeBeforeOffset(container, range.startOffset);\n        block = getNextBlock(node, root);\n    }\n    // Check the block actually intersects the range\n    return block && isNodeContainedInRange(range, block, true) ? block : null;\n};\n\n// Returns the last block at least partially contained by the range,\n// or null if no block is contained by the range.\nconst getEndBlockOfRange = (\n    range: Range,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const container = range.endContainer;\n    let block: HTMLElement | null;\n\n    // If inline, get the containing block.\n    if (isInline(container)) {\n        block = getPreviousBlock(container, root);\n    } else if (\n        container !== root &&\n        container instanceof HTMLElement &&\n        isBlock(container)\n    ) {\n        block = container;\n    } else {\n        let node = getNodeAfterOffset(container, range.endOffset);\n        if (!node || !root.contains(node)) {\n            node = root;\n            let child: Node | null;\n            while ((child = node.lastChild)) {\n                node = child;\n            }\n        }\n        block = getPreviousBlock(node, root);\n    }\n    // Check the block actually intersects the range\n    return block && isNodeContainedInRange(range, block, true) ? block : null;\n};\n\nconst isContent = (node: Element | Text): boolean => {\n    return node instanceof Text\n        ? notWS.test(node.data)\n        : node.nodeName === 'IMG';\n};\n\nconst rangeDoesStartAtBlockBoundary = (\n    range: Range,\n    root: Element,\n): boolean => {\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    let nodeAfterCursor: Node | null;\n\n    // If in the middle or end of a text node, we're not at the boundary.\n    if (startContainer instanceof Text) {\n        const text = startContainer.data;\n        for (let i = startOffset; i > 0; i -= 1) {\n            if (text.charAt(i - 1) !== ZWS) {\n                return false;\n            }\n        }\n        nodeAfterCursor = startContainer;\n    } else {\n        nodeAfterCursor = getNodeAfterOffset(startContainer, startOffset);\n        if (nodeAfterCursor && !root.contains(nodeAfterCursor)) {\n            nodeAfterCursor = null;\n        }\n        // The cursor was right at the end of the document\n        if (!nodeAfterCursor) {\n            nodeAfterCursor = getNodeBeforeOffset(startContainer, startOffset);\n            if (nodeAfterCursor instanceof Text && nodeAfterCursor.length) {\n                return false;\n            }\n        }\n    }\n\n    // Otherwise, look for any previous content in the same block.\n    const block = getStartBlockOfRange(range, root);\n    if (!block) {\n        return false;\n    }\n    const contentWalker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        isContent,\n    );\n    contentWalker.currentNode = nodeAfterCursor;\n\n    return !contentWalker.previousNode();\n};\n\nconst rangeDoesEndAtBlockBoundary = (range: Range, root: Element): boolean => {\n    const endContainer = range.endContainer;\n    const endOffset = range.endOffset;\n    let currentNode: Node;\n\n    // If in a text node with content, and not at the end, we're not\n    // at the boundary. Ignore ZWS.\n    if (endContainer instanceof Text) {\n        const text = endContainer.data;\n        const length = text.length;\n        for (let i = endOffset; i < length; i += 1) {\n            if (text.charAt(i) !== ZWS) {\n                return false;\n            }\n        }\n        currentNode = endContainer;\n    } else {\n        currentNode = getNodeBeforeOffset(endContainer, endOffset);\n    }\n\n    // Otherwise, look for any further content in the same block.\n    const block = getEndBlockOfRange(range, root);\n    if (!block) {\n        return false;\n    }\n    const contentWalker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        isContent,\n    );\n    contentWalker.currentNode = currentNode;\n    return !contentWalker.nextNode();\n};\n\nconst expandRangeToBlockBoundaries = (range: Range, root: Element): void => {\n    const start = getStartBlockOfRange(range, root);\n    const end = getEndBlockOfRange(range, root);\n    let parent: Node;\n\n    if (start && end) {\n        parent = start.parentNode!;\n        range.setStart(parent, Array.from(parent.childNodes).indexOf(start));\n        parent = end.parentNode!;\n        range.setEnd(parent, Array.from(parent.childNodes).indexOf(end) + 1);\n    }\n};\n\n// ---\n\nexport {\n    getStartBlockOfRange,\n    getEndBlockOfRange,\n    rangeDoesStartAtBlockBoundary,\n    rangeDoesEndAtBlockBoundary,\n    expandRangeToBlockBoundaries,\n};\n", "import { cleanupBRs } from '../Clean';\nimport {\n    split,\n    fixCursor,\n    mergeWithBlock,\n    fixContainer,\n    mergeContainers,\n} from '../node/MergeSplit';\nimport { detach, getNearest, getLength } from '../node/Node';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from '../node/TreeIterator';\nimport { isInline, isContainer, isLeaf } from '../node/Category';\nimport { getNextBlock, isEmptyBlock, getPreviousBlock } from '../node/Block';\nimport {\n    getStartBlockOfRange,\n    getEndBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n    rangeDoesStartAtBlockBoundary,\n} from './Block';\nimport {\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n} from './Boundaries';\n\n// ---\n\nfunction createRange(startContainer: Node, startOffset: number): Range;\nfunction createRange(\n    startContainer: Node,\n    startOffset: number,\n    endContainer: Node,\n    endOffset: number,\n): Range;\nfunction createRange(\n    startContainer: Node,\n    startOffset: number,\n    endContainer?: Node,\n    endOffset?: number,\n): Range {\n    const range = document.createRange();\n    range.setStart(startContainer, startOffset);\n    if (endContainer && typeof endOffset === 'number') {\n        range.setEnd(endContainer, endOffset);\n    } else {\n        range.setEnd(startContainer, startOffset);\n    }\n    return range;\n}\n\nconst insertNodeInRange = (range: Range, node: Node): void => {\n    // Insert at start.\n    let { startContainer, startOffset, endContainer, endOffset } = range;\n    let children: NodeListOf<ChildNode>;\n\n    // If part way through a text node, split it.\n    if (startContainer instanceof Text) {\n        const parent = startContainer.parentNode!;\n        children = parent.childNodes;\n        if (startOffset === startContainer.length) {\n            startOffset = Array.from(children).indexOf(startContainer) + 1;\n            if (range.collapsed) {\n                endContainer = parent;\n                endOffset = startOffset;\n            }\n        } else {\n            if (startOffset) {\n                const afterSplit = startContainer.splitText(startOffset);\n                if (endContainer === startContainer) {\n                    endOffset -= startOffset;\n                    endContainer = afterSplit;\n                } else if (endContainer === parent) {\n                    endOffset += 1;\n                }\n                startContainer = afterSplit;\n            }\n            startOffset = Array.from(children).indexOf(\n                startContainer as ChildNode,\n            );\n        }\n        startContainer = parent;\n    } else {\n        children = startContainer.childNodes;\n    }\n\n    const childCount = children.length;\n\n    if (startOffset === childCount) {\n        startContainer.appendChild(node);\n    } else {\n        startContainer.insertBefore(node, children[startOffset]);\n    }\n\n    if (startContainer === endContainer) {\n        endOffset += children.length - childCount;\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\n/**\n * Removes the contents of the range and returns it as a DocumentFragment.\n * The range at the end will be at the same position, with the edges just\n * before/after the split. If the start/end have the same parents, it will\n * be collapsed.\n */\nconst extractContentsOfRange = (\n    range: Range,\n    common: Node | null,\n    root: Element,\n): DocumentFragment => {\n    const frag = document.createDocumentFragment();\n    if (range.collapsed) {\n        return frag;\n    }\n\n    if (!common) {\n        common = range.commonAncestorContainer;\n    }\n    if (common instanceof Text) {\n        common = common.parentNode!;\n    }\n\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n\n    let endContainer = split(range.endContainer, range.endOffset, common, root);\n    let endOffset = 0;\n\n    let node = split(startContainer, startOffset, common, root);\n    while (node && node !== endContainer) {\n        const next = node.nextSibling;\n        frag.appendChild(node);\n        node = next;\n    }\n\n    // Merge text nodes if adjacent\n    if (startContainer instanceof Text && endContainer instanceof Text) {\n        startContainer.appendData(endContainer.data);\n        detach(endContainer);\n        endContainer = startContainer;\n        endOffset = startOffset;\n    }\n\n    range.setStart(startContainer, startOffset);\n    if (endContainer) {\n        range.setEnd(endContainer, endOffset);\n    } else {\n        // endContainer will be null if at end of parent's child nodes list.\n        range.setEnd(common, common.childNodes.length);\n    }\n\n    fixCursor(common);\n\n    return frag;\n};\n\n/**\n * Returns the next/prev node that's part of the same inline content.\n */\nconst getAdjacentInlineNode = (\n    iterator: TreeIterator<Node>,\n    method: 'nextNode' | 'previousPONode',\n    node: Node,\n): Node | null => {\n    iterator.currentNode = node;\n    let nextNode: Node | null;\n    while ((nextNode = iterator[method]())) {\n        if (nextNode instanceof Text || isLeaf(nextNode)) {\n            return nextNode;\n        }\n        if (!isInline(nextNode)) {\n            return null;\n        }\n    }\n    return null;\n};\n\nconst deleteContentsOfRange = (\n    range: Range,\n    root: Element,\n): DocumentFragment => {\n    const startBlock = getStartBlockOfRange(range, root);\n    let endBlock = getEndBlockOfRange(range, root);\n    const needsMerge = startBlock !== endBlock;\n\n    // Move boundaries up as much as possible without exiting block,\n    // to reduce need to split.\n    if (startBlock && endBlock) {\n        moveRangeBoundariesDownTree(range);\n        moveRangeBoundariesUpTree(range, startBlock, endBlock, root);\n    }\n\n    // Remove selected range\n    const frag = extractContentsOfRange(range, null, root);\n\n    // Move boundaries back down tree as far as possible.\n    moveRangeBoundariesDownTree(range);\n\n    // If we split into two different blocks, merge the blocks.\n    if (needsMerge) {\n        // endBlock will have been split, so need to refetch\n        endBlock = getEndBlockOfRange(range, root);\n        if (startBlock && endBlock && startBlock !== endBlock) {\n            mergeWithBlock(startBlock, endBlock, range, root);\n        }\n    }\n\n    // Ensure block has necessary children\n    if (startBlock) {\n        fixCursor(startBlock);\n    }\n\n    // Ensure root has a block-level element in it.\n    const child = root.firstChild;\n    if (!child || child.nodeName === 'BR') {\n        fixCursor(root);\n        if (root.firstChild) {\n            range.selectNodeContents(root.firstChild);\n        }\n    }\n\n    range.collapse(true);\n\n    // Now we may need to swap a space for a nbsp if the browser is going\n    // to swallow it due to HTML whitespace rules:\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    const iterator = new TreeIterator(root, SHOW_ELEMENT_OR_TEXT);\n\n    // Find the character after cursor point\n    let afterNode: Node | null = startContainer;\n    let afterOffset = startOffset;\n    if (!(afterNode instanceof Text) || afterOffset === afterNode.data.length) {\n        afterNode = getAdjacentInlineNode(iterator, 'nextNode', afterNode);\n        afterOffset = 0;\n    }\n\n    // Find the character before cursor point\n    let beforeNode: Node | null = startContainer;\n    let beforeOffset = startOffset - 1;\n    if (!(beforeNode instanceof Text) || beforeOffset === -1) {\n        beforeNode = getAdjacentInlineNode(\n            iterator,\n            'previousPONode',\n            afterNode ||\n                (startContainer instanceof Text\n                    ? startContainer\n                    : startContainer.childNodes[startOffset] || startContainer),\n        );\n        if (beforeNode instanceof Text) {\n            beforeOffset = beforeNode.data.length;\n        }\n    }\n\n    // If range starts at block boundary and character after cursor point\n    // is a space, replace with nbsp\n    let node = null;\n    let offset = 0;\n    if (\n        afterNode instanceof Text &&\n        afterNode.data.charAt(afterOffset) === ' ' &&\n        rangeDoesStartAtBlockBoundary(range, root)\n    ) {\n        node = afterNode;\n        offset = afterOffset;\n    } else if (\n        beforeNode instanceof Text &&\n        beforeNode.data.charAt(beforeOffset) === ' '\n    ) {\n        // If character before cursor point is a space, replace with nbsp\n        // if either:\n        // a) There is a space after it; or\n        // b) The point after is the end of the block\n        if (\n            (afterNode instanceof Text &&\n                afterNode.data.charAt(afterOffset) === ' ') ||\n            rangeDoesEndAtBlockBoundary(range, root)\n        ) {\n            node = beforeNode;\n            offset = beforeOffset;\n        }\n    }\n    if (node) {\n        node.replaceData(offset, 1, '\u00A0'); // nbsp\n    }\n    // Range needs to be put back in place\n    range.setStart(startContainer, startOffset);\n    range.collapse(true);\n\n    return frag;\n};\n\n// Contents of range will be deleted.\n// After method, range will be around inserted content\nconst insertTreeFragmentIntoRange = (\n    range: Range,\n    frag: DocumentFragment,\n    root: Element,\n): void => {\n    const firstInFragIsInline = frag.firstChild && isInline(frag.firstChild);\n    let node: Node | null;\n\n    // Fixup content: ensure no top-level inline, and add cursor fix elements.\n    fixContainer(frag, root);\n    node = frag;\n    while ((node = getNextBlock(node, root))) {\n        fixCursor(node);\n    }\n\n    // Delete any selected content.\n    if (!range.collapsed) {\n        deleteContentsOfRange(range, root);\n    }\n\n    // Move range down into text nodes.\n    moveRangeBoundariesDownTree(range);\n    range.collapse(false); // collapse to end\n\n    // Where will we split up to? First blockquote parent, otherwise root.\n    const stopPoint =\n        getNearest(range.endContainer, root, 'BLOCKQUOTE') || root;\n\n    // Merge the contents of the first block in the frag with the focused block.\n    // If there are contents in the block after the focus point, collect this\n    // up to insert in the last block later. This preserves the style that was\n    // present in this bit of the page.\n    //\n    // If the block being inserted into is empty though, replace it instead of\n    // merging if the fragment had block contents.\n    // e.g. <blockquote><p>Foo</p></blockquote>\n    // This seems a reasonable approximation of user intent.\n    let block = getStartBlockOfRange(range, root);\n    let blockContentsAfterSplit: DocumentFragment | null = null;\n    const firstBlockInFrag = getNextBlock(frag, frag);\n    const replaceBlock = !firstInFragIsInline && !!block && isEmptyBlock(block);\n    if (\n        block &&\n        firstBlockInFrag &&\n        !replaceBlock &&\n        // Don't merge table cells or PRE elements into block\n        !getNearest(firstBlockInFrag, frag, 'PRE') &&\n        !getNearest(firstBlockInFrag, frag, 'TABLE')\n    ) {\n        moveRangeBoundariesUpTree(range, block, block, root);\n        range.collapse(true); // collapse to start\n        let container = range.endContainer;\n        let offset = range.endOffset;\n        // Remove trailing <br> \u2013 we don't want this considered content to be\n        // inserted again later\n        cleanupBRs(block as HTMLElement, root, false);\n        if (isInline(container)) {\n            // Split up to block parent.\n            const nodeAfterSplit = split(\n                container,\n                offset,\n                getPreviousBlock(container, root) || root,\n                root,\n            ) as Node;\n            container = nodeAfterSplit.parentNode!;\n            offset = Array.from(container.childNodes).indexOf(\n                nodeAfterSplit as ChildNode,\n            );\n        }\n        if (/*isBlock( container ) && */ offset !== getLength(container)) {\n            // Collect any inline contents of the block after the range point\n            blockContentsAfterSplit = document.createDocumentFragment();\n            while ((node = container.childNodes[offset])) {\n                blockContentsAfterSplit.appendChild(node);\n            }\n        }\n        // And merge the first block in.\n        mergeWithBlock(container, firstBlockInFrag, range, root);\n\n        // And where we will insert\n        offset =\n            Array.from(container.parentNode!.childNodes).indexOf(\n                container as ChildNode,\n            ) + 1;\n        container = container.parentNode!;\n        range.setEnd(container, offset);\n    }\n\n    // Is there still any content in the fragment?\n    if (getLength(frag)) {\n        if (replaceBlock && block) {\n            range.setEndBefore(block);\n            range.collapse(false);\n            detach(block);\n        }\n        moveRangeBoundariesUpTree(range, stopPoint, stopPoint, root);\n        // Now split after block up to blockquote (if a parent) or root\n        let nodeAfterSplit = split(\n            range.endContainer,\n            range.endOffset,\n            stopPoint,\n            root,\n        ) as Node | null;\n        const nodeBeforeSplit = nodeAfterSplit\n            ? nodeAfterSplit.previousSibling\n            : stopPoint.lastChild;\n        stopPoint.insertBefore(frag, nodeAfterSplit);\n        if (nodeAfterSplit) {\n            range.setEndBefore(nodeAfterSplit);\n        } else {\n            range.setEnd(stopPoint, getLength(stopPoint));\n        }\n        block = getEndBlockOfRange(range, root);\n\n        // Get a reference that won't be invalidated if we merge containers.\n        moveRangeBoundariesDownTree(range);\n        const container = range.endContainer;\n        const offset = range.endOffset;\n\n        // Merge inserted containers with edges of split\n        if (nodeAfterSplit && isContainer(nodeAfterSplit)) {\n            mergeContainers(nodeAfterSplit, root);\n        }\n        nodeAfterSplit = nodeBeforeSplit && nodeBeforeSplit.nextSibling;\n        if (nodeAfterSplit && isContainer(nodeAfterSplit)) {\n            mergeContainers(nodeAfterSplit, root);\n        }\n        range.setEnd(container, offset);\n    }\n\n    // Insert inline content saved from before.\n    if (blockContentsAfterSplit && block) {\n        const tempRange = range.cloneRange();\n        fixCursor(blockContentsAfterSplit);\n        mergeWithBlock(block, blockContentsAfterSplit, tempRange, root);\n        range.setEnd(tempRange.endContainer, tempRange.endOffset);\n    }\n    moveRangeBoundariesDownTree(range);\n};\n\n// ---\n\nexport {\n    createRange,\n    deleteContentsOfRange,\n    extractContentsOfRange,\n    insertNodeInRange,\n    insertTreeFragmentIntoRange,\n};\n", "import { SHOW_ELEMENT_OR_TEXT, TreeIterator } from '../node/TreeIterator';\nimport { isNodeContainedInRange } from './Boundaries';\nimport { isInline } from '../node/Category';\n\n// ---\n\nconst getTextContentsOfRange = (range: Range) => {\n    if (range.collapsed) {\n        return '';\n    }\n    const startContainer = range.startContainer;\n    const endContainer = range.endContainer;\n    const walker = new TreeIterator<Element | Text>(\n        range.commonAncestorContainer,\n        SHOW_ELEMENT_OR_TEXT,\n        (node) => {\n            return isNodeContainedInRange(range, node, true);\n        },\n    );\n    walker.currentNode = startContainer;\n\n    let node: Node | null = startContainer;\n    let textContent = '';\n    let addedTextInBlock = false;\n    let value: string;\n\n    if (\n        (!(node instanceof Element) && !(node instanceof Text)) ||\n        !walker.filter(node)\n    ) {\n        node = walker.nextNode();\n    }\n\n    while (node) {\n        if (node instanceof Text) {\n            value = node.data;\n            if (value && /\\S/.test(value)) {\n                if (node === endContainer) {\n                    value = value.slice(0, range.endOffset);\n                }\n                if (node === startContainer) {\n                    value = value.slice(range.startOffset);\n                }\n                textContent += value;\n                addedTextInBlock = true;\n            }\n        } else if (\n            node.nodeName === 'BR' ||\n            (addedTextInBlock && !isInline(node))\n        ) {\n            textContent += '\\n';\n            addedTextInBlock = false;\n        }\n        node = walker.nextNode();\n    }\n    // Replace nbsp with regular space;\n    // eslint-disable-next-line no-irregular-whitespace\n    textContent = textContent.replace(/\u00A0/g, ' ');\n\n    return textContent;\n};\n\n// ---\n\nexport { getTextContentsOfRange };\n", "import { isWin, isGecko, isLegacyEdge, notWS } from './Constants';\nimport { createElement, detach } from './node/Node';\nimport { getStartBlockOfRange, getEndBlockOfRange } from './range/Block';\nimport { createRange, deleteContentsOfRange } from './range/InsertDelete';\nimport {\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n} from './range/Boundaries';\n\nimport type { Squire } from './Editor';\nimport { getTextContentsOfRange } from './range/Contents';\n\n// ---\n\nconst indexOf = Array.prototype.indexOf;\n\nconst extractRangeToClipboard = (\n    event: ClipboardEvent,\n    range: Range,\n    root: HTMLElement,\n    removeRangeFromDocument: boolean,\n    toCleanHTML: null | ((html: string) => string),\n    toPlainText: null | ((html: string) => string),\n    plainTextOnly: boolean,\n): boolean => {\n    // Edge only seems to support setting plain text as of 2016-03-11.\n    const clipboardData = event.clipboardData;\n    if (isLegacyEdge || !clipboardData) {\n        return false;\n    }\n    // First get the plain text version from the range (unless we have a custom\n    // HTML -> Text conversion fn)\n    let text = toPlainText ? '' : getTextContentsOfRange(range);\n\n    // Clipboard content should include all parents within block, or all\n    // parents up to root if selection across blocks\n    const startBlock = getStartBlockOfRange(range, root);\n    const endBlock = getEndBlockOfRange(range, root);\n    let copyRoot = root;\n\n    // If the content is not in well-formed blocks, the start and end block\n    // may be the same, but actually the range goes outside it. Must check!\n    if (\n        startBlock === endBlock &&\n        startBlock?.contains(range.commonAncestorContainer)\n    ) {\n        copyRoot = startBlock;\n    }\n\n    // Extract the contents\n    let contents: Node;\n    if (removeRangeFromDocument) {\n        contents = deleteContentsOfRange(range, root);\n    } else {\n        // Clone range to mutate, then move up as high as possible without\n        // passing the copy root node.\n        range = range.cloneRange();\n        moveRangeBoundariesDownTree(range);\n        moveRangeBoundariesUpTree(range, copyRoot, copyRoot, root);\n        contents = range.cloneContents();\n    }\n\n    // Add any other parents not in extracted content, up to copy root\n    let parent = range.commonAncestorContainer;\n    if (parent instanceof Text) {\n        parent = parent.parentNode!;\n    }\n    while (parent && parent !== copyRoot) {\n        const newContents = parent.cloneNode(false);\n        newContents.appendChild(contents);\n        contents = newContents;\n        parent = parent.parentNode!;\n    }\n\n    // Get HTML version of data\n    let html: string | undefined;\n    if (\n        contents.childNodes.length === 1 &&\n        contents.childNodes[0] instanceof Text\n    ) {\n        // Replace nbsp with regular space;\n        // eslint-disable-next-line no-irregular-whitespace\n        text = contents.childNodes[0].data.replace(/\u00A0/g, ' ');\n        plainTextOnly = true;\n    } else {\n        const node = createElement('DIV') as HTMLDivElement;\n        node.appendChild(contents);\n        html = node.innerHTML;\n        if (toCleanHTML) {\n            html = toCleanHTML(html);\n        }\n    }\n\n    // Get Text version of data if converting from HTML\n    if (toPlainText && html !== undefined) {\n        text = toPlainText(html);\n    }\n\n    // Firefox (and others?) returns unix line endings (\\n) even on Windows.\n    // If on Windows, normalise to \\r\\n, since Notepad and some other crappy\n    // apps do not understand just \\n.\n    if (isWin) {\n        text = text.replace(/\\r?\\n/g, '\\r\\n');\n    }\n\n    // Set clipboard data\n    if (!plainTextOnly && html && text !== html) {\n        clipboardData.setData('text/html', html);\n    }\n    clipboardData.setData('text/plain', text);\n    event.preventDefault();\n\n    return true;\n};\n\n// ---\n\nconst _onCut = function (this: Squire, event: ClipboardEvent): void {\n    const range: Range = this.getSelection();\n    const root: HTMLElement = this._root;\n\n    // Nothing to do\n    if (range.collapsed) {\n        event.preventDefault();\n        return;\n    }\n\n    // Save undo checkpoint\n    this.saveUndoState(range);\n\n    const handled = extractRangeToClipboard(\n        event,\n        range,\n        root,\n        true,\n        this._config.willCutCopy,\n        this._config.toPlainText,\n        false,\n    );\n    if (!handled) {\n        setTimeout(() => {\n            try {\n                // If all content removed, ensure div at start of root.\n                this._ensureBottomLine();\n            } catch (error) {\n                this._config.didError(error);\n            }\n        }, 0);\n    }\n\n    this.setSelection(range);\n};\n\nconst _onCopy = function (this: Squire, event: ClipboardEvent): void {\n    extractRangeToClipboard(\n        event,\n        this.getSelection(),\n        this._root,\n        false,\n        this._config.willCutCopy,\n        this._config.toPlainText,\n        false,\n    );\n};\n\n// Need to monitor for shift key like this, as event.shiftKey is not available\n// in paste event.\nconst _monitorShiftKey = function (this: Squire, event: KeyboardEvent): void {\n    this._isShiftDown = event.shiftKey;\n};\n\nconst _onPaste = function (this: Squire, event: ClipboardEvent): void {\n    const clipboardData = event.clipboardData;\n    const items = clipboardData?.items;\n    const choosePlain: boolean | undefined = this._isShiftDown;\n    let hasRTF = false;\n    let hasImage = false;\n    let plainItem: null | DataTransferItem = null;\n    let htmlItem: null | DataTransferItem = null;\n\n    // Current HTML5 Clipboard interface\n    // ---------------------------------\n    // https://html.spec.whatwg.org/multipage/interaction.html\n    if (items) {\n        let l = items.length;\n        while (l--) {\n            const item = items[l];\n            const type = item.type;\n            if (type === 'text/html') {\n                htmlItem = item;\n                // iOS copy URL gives you type text/uri-list which is just a list\n                // of 1 or more URLs separated by new lines. Can just treat as\n                // plain text.\n            } else if (type === 'text/plain' || type === 'text/uri-list') {\n                plainItem = item;\n            } else if (type === 'text/rtf') {\n                hasRTF = true;\n            } else if (/^image\\/.*/.test(type)) {\n                hasImage = true;\n            }\n        }\n\n        // Treat image paste as a drop of an image file. When you copy\n        // an image in Chrome/Firefox (at least), it copies the image data\n        // but also an HTML version (referencing the original URL of the image)\n        // and a plain text version.\n        //\n        // However, when you copy in Excel, you get html, rtf, text, image;\n        // in this instance you want the html version! So let's try using\n        // the presence of text/rtf as an indicator to choose the html version\n        // over the image.\n        if (hasImage && !(hasRTF && htmlItem)) {\n            event.preventDefault();\n            this.fireEvent('pasteImage', {\n                clipboardData,\n            });\n            return;\n        }\n\n        // Edge only provides access to plain text as of 2016-03-11 and gives no\n        // indication there should be an HTML part. However, it does support\n        // access to image data, so we check for that first. Otherwise though,\n        // fall through to fallback clipboard handling methods\n        if (!isLegacyEdge) {\n            event.preventDefault();\n            if (htmlItem && (!choosePlain || !plainItem)) {\n                htmlItem.getAsString((html) => {\n                    this.insertHTML(html, true);\n                });\n            } else if (plainItem) {\n                plainItem.getAsString((text) => {\n                    // If we have a selection and text is solely a URL,\n                    // just make the text a link.\n                    let isLink = false;\n                    const range = this.getSelection();\n                    if (!range.collapsed && notWS.test(range.toString())) {\n                        const match = this.linkRegExp.exec(text);\n                        isLink = !!match && match[0].length === text.length;\n                    }\n                    if (isLink) {\n                        this.makeLink(text);\n                    } else {\n                        this.insertPlainText(text, true);\n                    }\n                });\n            }\n            return;\n        }\n    }\n\n    // Old interface\n    // -------------\n\n    // Safari (and indeed many other OS X apps) copies stuff as text/rtf\n    // rather than text/html; even from a webpage in Safari. The only way\n    // to get an HTML version is to fallback to letting the browser insert\n    // the content. Same for getting image data. *Sigh*.\n    //\n    // Firefox is even worse: it doesn't even let you know that there might be\n    // an RTF version on the clipboard, but it will also convert to HTML if you\n    // let the browser insert the content. I've filed\n    // https://bugzilla.mozilla.org/show_bug.cgi?id=1254028\n    const types = clipboardData?.types;\n    if (\n        !isLegacyEdge &&\n        types &&\n        (indexOf.call(types, 'text/html') > -1 ||\n            (!isGecko &&\n                indexOf.call(types, 'text/plain') > -1 &&\n                indexOf.call(types, 'text/rtf') < 0))\n    ) {\n        event.preventDefault();\n        // Abiword on Linux copies a plain text and html version, but the HTML\n        // version is the empty string! So always try to get HTML, but if none,\n        // insert plain text instead. On iOS, Facebook (and possibly other\n        // apps?) copy links as type text/uri-list, but also insert a **blank**\n        // text/plain item onto the clipboard. Why? Who knows.\n        let data;\n        if (!choosePlain && (data = clipboardData.getData('text/html'))) {\n            this.insertHTML(data, true);\n        } else if (\n            (data = clipboardData.getData('text/plain')) ||\n            (data = clipboardData.getData('text/uri-list'))\n        ) {\n            this.insertPlainText(data, true);\n        }\n        return;\n    }\n\n    // No interface. Includes all versions of IE :(\n    // --------------------------------------------\n\n    const body = document.body;\n    const range = this.getSelection();\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    const endContainer = range.endContainer;\n    const endOffset = range.endOffset;\n\n    // We need to position the pasteArea in the visible portion of the screen\n    // to stop the browser auto-scrolling.\n    let pasteArea: Element = createElement('DIV', {\n        contenteditable: 'true',\n        style: 'position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;',\n    });\n    body.appendChild(pasteArea);\n    range.selectNodeContents(pasteArea);\n    this.setSelection(range);\n\n    // A setTimeout of 0 means this is added to the back of the\n    // single javascript thread, so it will be executed after the\n    // paste event.\n    setTimeout(() => {\n        try {\n            // Get the pasted content and clean\n            let html = '';\n            let next: Element = pasteArea;\n            let first: Node | null;\n\n            // #88: Chrome can apparently split the paste area if certain\n            // content is inserted; gather them all up.\n            while ((pasteArea = next)) {\n                next = pasteArea.nextSibling as Element;\n                detach(pasteArea);\n                // Safari and IE like putting extra divs around things.\n                first = pasteArea.firstChild;\n                if (\n                    first &&\n                    first === pasteArea.lastChild &&\n                    first instanceof HTMLDivElement\n                ) {\n                    pasteArea = first;\n                }\n                html += pasteArea.innerHTML;\n            }\n\n            this.setSelection(\n                createRange(\n                    startContainer,\n                    startOffset,\n                    endContainer,\n                    endOffset,\n                ),\n            );\n\n            if (html) {\n                this.insertHTML(html, true);\n            }\n        } catch (error) {\n            this._config.didError(error);\n        }\n    }, 0);\n};\n\n// On Windows you can drag an drop text. We can't handle this ourselves, because\n// as far as I can see, there's no way to get the drop insertion point. So just\n// save an undo state and hope for the best.\nconst _onDrop = function (this: Squire, event: DragEvent): void {\n    // it's possible for dataTransfer to be null, let's avoid it.\n    if (!event.dataTransfer) {\n        return;\n    }\n    const types = event.dataTransfer.types;\n    let l = types.length;\n    let hasPlain = false;\n    let hasHTML = false;\n    while (l--) {\n        switch (types[l]) {\n            case 'text/plain':\n                hasPlain = true;\n                break;\n            case 'text/html':\n                hasHTML = true;\n                break;\n            default:\n                return;\n        }\n    }\n    if (hasHTML || (hasPlain && this.saveUndoState)) {\n        this.saveUndoState();\n    }\n};\n\n// ---\n\nexport {\n    extractRangeToClipboard,\n    _onCut,\n    _onCopy,\n    _monitorShiftKey,\n    _onPaste,\n    _onDrop,\n};\n", "import type { Squire } from '../Editor';\n\n// ---\n\nconst Enter = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    event.preventDefault();\n    self.splitBlock(event.shiftKey, range);\n};\n\n// ---\n\nexport { Enter };\n", "import { ZWS } from '../Constants';\nimport { getPreviousBlock } from '../node/Block';\nimport { isInline, isBlock } from '../node/Category';\nimport { fixCursor } from '../node/MergeSplit';\nimport { createElement, detach, getNearest } from '../node/Node';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\n// If you delete the content inside a span with a font styling, Webkit will\n// replace it with a <font> tag (!). If you delete all the text inside a\n// link in Opera, it won't delete the link. Let's make things consistent. If\n// you delete all text inside an inline tag, remove the inline tag.\nconst afterDelete = (self: Squire, range?: Range): void => {\n    try {\n        if (!range) {\n            range = self.getSelection();\n        }\n        let node = range!.startContainer;\n        // Climb the tree from the focus point while we are inside an empty\n        // inline element\n        if (node instanceof Text) {\n            node = node.parentNode!;\n        }\n        let parent = node;\n        while (\n            isInline(parent) &&\n            (!parent.textContent || parent.textContent === ZWS)\n        ) {\n            node = parent;\n            parent = node.parentNode!;\n        }\n        // If focused in empty inline element\n        if (node !== parent) {\n            // Move focus to just before empty inline(s)\n            range!.setStart(\n                parent,\n                Array.from(parent.childNodes as NodeListOf<Node>).indexOf(node),\n            );\n            range!.collapse(true);\n            // Remove empty inline(s)\n            parent.removeChild(node);\n            // Fix cursor in block\n            if (!isBlock(parent)) {\n                parent = getPreviousBlock(parent, self._root) || self._root;\n            }\n            fixCursor(parent);\n            // Move cursor into text node\n            moveRangeBoundariesDownTree(range!);\n        }\n        // If you delete the last character in the sole <div> in Chrome,\n        // it removes the div and replaces it with just a <br> inside the\n        // root. Detach the <br>; the _ensureBottomLine call will insert a new\n        // block.\n        if (\n            node === self._root &&\n            (node = node.firstChild!) &&\n            node.nodeName === 'BR'\n        ) {\n            detach(node);\n        }\n        self._ensureBottomLine();\n        self.setSelection(range);\n        self._updatePath(range, true);\n    } catch (error) {\n        self._config.didError(error);\n    }\n};\n\nconst detachUneditableNode = (node: Node, root: Element): void => {\n    let parent: Node | null;\n    while ((parent = node.parentNode)) {\n        if (parent === root || (parent as HTMLElement).isContentEditable) {\n            break;\n        }\n        node = parent;\n    }\n    detach(node);\n};\n\n// ---\n\nconst linkifyText = (self: Squire, textNode: Text, offset: number): void => {\n    if (getNearest(textNode, self._root, 'A')) {\n        return;\n    }\n    const data = textNode.data || '';\n    const searchFrom =\n        Math.max(\n            data.lastIndexOf(' ', offset - 1),\n            data.lastIndexOf('\u00A0', offset - 1),\n        ) + 1;\n    const searchText = data.slice(searchFrom, offset);\n    const match = self.linkRegExp.exec(searchText);\n    if (match) {\n        // Record an undo point\n        const selection = self.getSelection();\n        self._docWasChanged();\n        self._recordUndoState(selection);\n        self._getRangeAndRemoveBookmark(selection);\n\n        const index = searchFrom + match.index;\n        const endIndex = index + match[0].length;\n        const needsSelectionUpdate = selection.startContainer === textNode;\n        const newSelectionOffset = selection.startOffset - endIndex;\n        if (index) {\n            textNode = textNode.splitText(index);\n        }\n\n        const defaultAttributes = self._config.tagAttributes.a;\n        const link = createElement(\n            'A',\n            Object.assign(\n                {\n                    href: match[1]\n                        ? /^(?:ht|f)tps?:/i.test(match[1])\n                            ? match[1]\n                            : 'http://' + match[1]\n                        : 'mailto:' + match[0],\n                },\n                defaultAttributes,\n            ),\n        );\n        link.textContent = data.slice(index, endIndex);\n        textNode.parentNode!.insertBefore(link, textNode);\n        textNode.data = data.slice(endIndex);\n\n        if (needsSelectionUpdate) {\n            selection.setStart(textNode, newSelectionOffset);\n            selection.setEnd(textNode, newSelectionOffset);\n        }\n        self.setSelection(selection);\n    }\n};\n\n// ---\n\nexport { afterDelete, detachUneditableNode, linkifyText };\n", "import type { Squire } from '../Editor';\nimport { getPreviousBlock } from '../node/Block';\nimport {\n    fixContainer,\n    mergeContainers,\n    mergeWithBlock,\n} from '../node/MergeSplit';\nimport { getNearest } from '../node/Node';\nimport {\n    getStartBlockOfRange,\n    rangeDoesStartAtBlockBoundary,\n} from '../range/Block';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport { afterDelete, detachUneditableNode } from './KeyHelpers';\n\n// ---\n\nconst Backspace = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root: Element = self._root;\n    self._removeZWS();\n    // Record undo checkpoint.\n    self.saveUndoState(range);\n    if (!range.collapsed) {\n        // If not collapsed, delete contents\n        event.preventDefault();\n        deleteContentsOfRange(range, root);\n        afterDelete(self, range);\n    } else if (rangeDoesStartAtBlockBoundary(range, root)) {\n        // If at beginning of block, merge with previous\n        event.preventDefault();\n        const startBlock = getStartBlockOfRange(range, root);\n        if (!startBlock) {\n            return;\n        }\n        let current = startBlock;\n        // In case inline data has somehow got between blocks.\n        fixContainer(current.parentNode!, root);\n        // Now get previous block\n        const previous = getPreviousBlock(current, root);\n        // Must not be at the very beginning of the text area.\n        if (previous) {\n            // If not editable, just delete whole block.\n            if (!(previous as HTMLElement).isContentEditable) {\n                detachUneditableNode(previous, root);\n                return;\n            }\n            // Otherwise merge.\n            mergeWithBlock(previous, current, range, root);\n            // If deleted line between containers, merge newly adjacent\n            // containers.\n            current = previous.parentNode as HTMLElement;\n            while (current !== root && !current.nextSibling) {\n                current = current.parentNode as HTMLElement;\n            }\n            if (\n                current !== root &&\n                (current = current.nextSibling as HTMLElement)\n            ) {\n                mergeContainers(current, root);\n            }\n            self.setSelection(range);\n            // If at very beginning of text area, allow backspace\n            // to break lists/blockquote.\n        } else if (current) {\n            if (\n                getNearest(current, root, 'UL') ||\n                getNearest(current, root, 'OL')\n            ) {\n                // Break list\n                self.decreaseListLevel(range);\n                return;\n            } else if (getNearest(current, root, 'BLOCKQUOTE')) {\n                // Break blockquote\n                self.removeQuote(range);\n                return;\n            }\n            self.setSelection(range);\n            self._updatePath(range, true);\n        }\n    } else {\n        // If deleting text inside a link that looks like a URL, delink.\n        // This is to allow you to easily correct auto-linked text.\n        moveRangeBoundariesDownTree(range);\n        const text = range.startContainer;\n        const offset = range.startOffset;\n        const a = text.parentNode;\n        if (\n            text instanceof Text &&\n            a instanceof HTMLAnchorElement &&\n            offset &&\n            a.href.includes(text.data)\n        ) {\n            text.deleteData(offset - 1, 1);\n            self.setSelection(range);\n            self.removeLink();\n            event.preventDefault();\n        } else {\n            // Otherwise, leave to browser but check afterwards whether it has\n            // left behind an empty inline tag.\n            self.setSelection(range);\n            setTimeout(() => {\n                afterDelete(self);\n            }, 0);\n        }\n    }\n};\n\n// ---\n\nexport { Backspace };\n", "import { getNextBlock } from '../node/Block';\nimport {\n    fixContainer,\n    mergeWithBlock,\n    mergeContainers,\n} from '../node/MergeSplit';\nimport { detach } from '../node/Node';\nimport {\n    rangeDoesEndAtBlockBoundary,\n    getStartBlockOfRange,\n} from '../range/Block';\nimport {\n    moveRangeBoundariesUpTree,\n    moveRangeBoundariesDownTree,\n} from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport { afterDelete, detachUneditableNode } from './KeyHelpers';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\nconst Delete = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    let current: Node | null;\n    let next: Node | null;\n    let originalRange: Range;\n    let cursorContainer: Node;\n    let cursorOffset: number;\n    let nodeAfterCursor: Node;\n    self._removeZWS();\n    // Record undo checkpoint.\n    self.saveUndoState(range);\n    // If not collapsed, delete contents\n    if (!range.collapsed) {\n        event.preventDefault();\n        deleteContentsOfRange(range, root);\n        afterDelete(self, range);\n        // If at end of block, merge next into this block\n    } else if (rangeDoesEndAtBlockBoundary(range, root)) {\n        event.preventDefault();\n        current = getStartBlockOfRange(range, root);\n        if (!current) {\n            return;\n        }\n        // In case inline data has somehow got between blocks.\n        fixContainer(current.parentNode!, root);\n        // Now get next block\n        next = getNextBlock(current, root);\n        // Must not be at the very end of the text area.\n        if (next) {\n            // If not editable, just delete whole block.\n            if (!(next as HTMLElement).isContentEditable) {\n                detachUneditableNode(next, root);\n                return;\n            }\n            // Otherwise merge.\n            mergeWithBlock(current, next, range, root);\n            // If deleted line between containers, merge newly adjacent\n            // containers.\n            next = current.parentNode!;\n            while (next !== root && !next.nextSibling) {\n                next = next.parentNode!;\n            }\n            if (next !== root && (next = next.nextSibling)) {\n                mergeContainers(next, root);\n            }\n            self.setSelection(range);\n            self._updatePath(range, true);\n        }\n        // Otherwise, leave to browser but check afterwards whether it has\n        // left behind an empty inline tag.\n    } else {\n        // But first check if the cursor is just before an IMG tag. If so,\n        // delete it ourselves, because the browser won't if it is not\n        // inline.\n        originalRange = range.cloneRange();\n        moveRangeBoundariesUpTree(range, root, root, root);\n        cursorContainer = range.endContainer;\n        cursorOffset = range.endOffset;\n        if (cursorContainer instanceof Element) {\n            nodeAfterCursor = cursorContainer.childNodes[cursorOffset];\n            if (nodeAfterCursor && nodeAfterCursor.nodeName === 'IMG') {\n                event.preventDefault();\n                detach(nodeAfterCursor);\n                moveRangeBoundariesDownTree(range);\n                afterDelete(self, range);\n                return;\n            }\n        }\n        self.setSelection(originalRange);\n        setTimeout(() => {\n            afterDelete(self);\n        }, 0);\n    }\n};\n\n// ---\n\nexport { Delete };\n", "import {\n    rangeDoesStartAtBlockBoundary,\n    getStartBlockOfRange,\n} from '../range/Block';\nimport { getNearest } from '../node/Node';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\nconst Tab = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    self._removeZWS();\n    // If no selection and at start of block\n    if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) {\n        let node: Node = getStartBlockOfRange(range, root)!;\n        // Iterate through the block's parents\n        let parent: Node | null;\n        while ((parent = node.parentNode)) {\n            // If we find a UL or OL (so are in a list, node must be an LI)\n            if (parent.nodeName === 'UL' || parent.nodeName === 'OL') {\n                // Then increase the list level\n                event.preventDefault();\n                self.increaseListLevel(range);\n                break;\n            }\n            node = parent;\n        }\n    }\n};\n\nconst ShiftTab = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    self._removeZWS();\n    // If no selection and at start of block\n    if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) {\n        // Break list\n        const node = range.startContainer;\n        if (getNearest(node, root, 'UL') || getNearest(node, root, 'OL')) {\n            event.preventDefault();\n            self.decreaseListLevel(range);\n        }\n    }\n};\n\n// ---\n\nexport { Tab, ShiftTab };\n", "import { detach, getLength } from '../node/Node';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\n\nimport type { Squire } from '../Editor';\nimport { linkifyText } from './KeyHelpers';\nimport {\n    getStartBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n} from '../range/Block';\nimport { SHOW_TEXT, TreeIterator } from '../node/TreeIterator';\nimport { ZWS } from '../Constants';\n\n// ---\n\nconst Space = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    let node: Node | null;\n    const root = self._root;\n    self._recordUndoState(range);\n    self._getRangeAndRemoveBookmark(range);\n\n    // Delete the selection if not collapsed\n    if (!range.collapsed) {\n        deleteContentsOfRange(range, root);\n        self._ensureBottomLine();\n        self.setSelection(range);\n        self._updatePath(range, true);\n    } else if (rangeDoesEndAtBlockBoundary(range, root)) {\n        const block = getStartBlockOfRange(range, root);\n        if (block && block.nodeName !== 'PRE') {\n            const text = block.textContent?.trimEnd().replace(ZWS, '');\n            if (text === '*' || text === '1.') {\n                event.preventDefault();\n                self.insertPlainText(' ', false);\n                self._docWasChanged();\n                self.saveUndoState(range);\n                const walker = new TreeIterator<Text>(block, SHOW_TEXT);\n                let textNode: Text | null;\n                while ((textNode = walker.nextNode())) {\n                    detach(textNode);\n                }\n                if (text === '*') {\n                    self.makeUnorderedList();\n                } else {\n                    self.makeOrderedList();\n                }\n                return;\n            }\n        }\n    }\n\n    // If the cursor is at the end of a link (<a>foo|</a>) then move it\n    // outside of the link (<a>foo</a>|) so that the space is not part of\n    // the link text.\n    node = range.endContainer;\n    if (range.endOffset === getLength(node)) {\n        do {\n            if (node.nodeName === 'A') {\n                range.setStartAfter(node);\n                break;\n            }\n        } while (\n            !node.nextSibling &&\n            (node = node.parentNode) &&\n            node !== root\n        );\n    }\n\n    // Linkify text\n    if (self._config.addLinks) {\n        const linkRange = range.cloneRange();\n        moveRangeBoundariesDownTree(linkRange);\n        const textNode = linkRange.startContainer as Text;\n        const offset = linkRange.startOffset;\n        setTimeout(() => {\n            linkifyText(self, textNode, offset);\n        }, 0);\n    }\n\n    self.setSelection(range);\n};\n\n// ---\n\nexport { Space };\n", "import {\n    isMac,\n    isWin,\n    isIOS,\n    ctrlKey,\n    supportsInputEvents,\n} from '../Constants';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport type { Squire } from '../Editor';\nimport { Enter } from './Enter';\nimport { Backspace } from './Backspace';\nimport { Delete } from './Delete';\nimport { ShiftTab, Tab } from './Tab';\nimport { Space } from './Space';\nimport { rangeDoesEndAtBlockBoundary } from '../range/Block';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\n\n// ---\n\nconst _onKey = function (this: Squire, event: KeyboardEvent): void {\n    // Ignore key events where event.isComposing, to stop us from blatting\n    // Kana-Kanji conversion\n    if (event.defaultPrevented || event.isComposing) {\n        return;\n    }\n\n    // We need to apply the Backspace/delete handlers regardless of\n    // control key modifiers.\n    let key = event.key;\n    let modifiers = '';\n    const code = event.code;\n    // If pressing a number key + Shift, make sure we handle it as the number\n    // key and not whatever different character the shift might turn it into.\n    if (/^Digit\\d$/.test(code)) {\n        key = code.slice(-1);\n    }\n    if (key !== 'Backspace' && key !== 'Delete') {\n        if (event.altKey) {\n            modifiers += 'Alt-';\n        }\n        if (event.ctrlKey) {\n            modifiers += 'Ctrl-';\n        }\n        if (event.metaKey) {\n            modifiers += 'Meta-';\n        }\n        if (event.shiftKey) {\n            modifiers += 'Shift-';\n        }\n    }\n    // However, on Windows, Shift-Delete is apparently \"cut\" (WTF right?), so\n    // we want to let the browser handle Shift-Delete in this situation.\n    if (isWin && event.shiftKey && key === 'Delete') {\n        modifiers += 'Shift-';\n    }\n    key = modifiers + key;\n\n    const range: Range = this.getSelection();\n    if (this._keyHandlers[key]) {\n        this._keyHandlers[key](this, event, range);\n    } else if (\n        !range.collapsed &&\n        !event.ctrlKey &&\n        !event.metaKey &&\n        key.length === 1\n    ) {\n        // Record undo checkpoint.\n        this.saveUndoState(range);\n        // Delete the selection\n        deleteContentsOfRange(range, this._root);\n        this._ensureBottomLine();\n        this.setSelection(range);\n        this._updatePath(range, true);\n    }\n};\n\n// ---\n\ntype KeyHandler = (self: Squire, event: KeyboardEvent, range: Range) => void;\n\nconst keyHandlers: Record<string, KeyHandler> = {\n    'Backspace': Backspace,\n    'Delete': Delete,\n    'Tab': Tab,\n    'Shift-Tab': ShiftTab,\n    ' ': Space,\n    'ArrowLeft'(self: Squire): void {\n        self._removeZWS();\n    },\n    'ArrowRight'(self: Squire, event: KeyboardEvent, range: Range): void {\n        self._removeZWS();\n        // Allow right arrow to always break out of <code> block.\n        const root = self.getRoot();\n        if (rangeDoesEndAtBlockBoundary(range, root)) {\n            moveRangeBoundariesDownTree(range);\n            let node: Node | null = range.endContainer;\n            do {\n                if (node.nodeName === 'CODE') {\n                    let next = node.nextSibling;\n                    if (!(next instanceof Text)) {\n                        const textNode = document.createTextNode('\u00A0'); // nbsp\n                        node.parentNode!.insertBefore(textNode, next);\n                        next = textNode;\n                    }\n                    range.setStart(next, 1);\n                    self.setSelection(range);\n                    event.preventDefault();\n                    break;\n                }\n            } while (\n                !node.nextSibling &&\n                (node = node.parentNode) &&\n                node !== root\n            );\n        }\n    },\n};\n\nif (!supportsInputEvents) {\n    keyHandlers.Enter = Enter;\n    keyHandlers['Shift-Enter'] = Enter;\n}\n\n// System standard for page up/down on Mac/iOS is to just scroll, not move the\n// cursor. On Linux/Windows, it should move the cursor, but some browsers don't\n// implement this natively. Override to support it.\nif (!isMac && !isIOS) {\n    keyHandlers.PageUp = (self: Squire) => {\n        self.moveCursorToStart();\n    };\n    keyHandlers.PageDown = (self: Squire) => {\n        self.moveCursorToEnd();\n    };\n}\n\n// ---\n\nconst mapKeyToFormat = (\n    tag: string,\n    remove?: { tag: string } | null,\n): KeyHandler => {\n    remove = remove || null;\n    return (self: Squire, event: Event) => {\n        event.preventDefault();\n        const range = self.getSelection();\n        if (self.hasFormat(tag, null, range)) {\n            self.changeFormat(null, { tag }, range);\n        } else {\n            self.changeFormat({ tag }, remove, range);\n        }\n    };\n};\n\nkeyHandlers[ctrlKey + 'b'] = mapKeyToFormat('B');\nkeyHandlers[ctrlKey + 'i'] = mapKeyToFormat('I');\nkeyHandlers[ctrlKey + 'u'] = mapKeyToFormat('U');\nkeyHandlers[ctrlKey + 'Shift-7'] = mapKeyToFormat('S');\nkeyHandlers[ctrlKey + 'Shift-5'] = mapKeyToFormat('SUB', { tag: 'SUP' });\nkeyHandlers[ctrlKey + 'Shift-6'] = mapKeyToFormat('SUP', { tag: 'SUB' });\n\nkeyHandlers[ctrlKey + 'Shift-8'] = (\n    self: Squire,\n    event: KeyboardEvent,\n): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (!/(?:^|>)UL/.test(path)) {\n        self.makeUnorderedList();\n    } else {\n        self.removeList();\n    }\n};\nkeyHandlers[ctrlKey + 'Shift-9'] = (\n    self: Squire,\n    event: KeyboardEvent,\n): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (!/(?:^|>)OL/.test(path)) {\n        self.makeOrderedList();\n    } else {\n        self.removeList();\n    }\n};\n\nkeyHandlers[ctrlKey + '['] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) {\n        self.decreaseQuoteLevel();\n    } else {\n        self.decreaseListLevel();\n    }\n};\nkeyHandlers[ctrlKey + ']'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) {\n        self.increaseQuoteLevel();\n    } else {\n        self.increaseListLevel();\n    }\n};\n\nkeyHandlers[ctrlKey + 'd'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    self.toggleCode();\n};\n\nkeyHandlers[ctrlKey + 'z'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    self.undo();\n};\nkeyHandlers[ctrlKey + 'y'] =\n    // Depending on platform, the Shift may cause the key to come through as\n    // upper case, but sometimes not. Just add both as shortcuts \u2014 the browser\n    // will only ever fire one or the other.\n    keyHandlers[ctrlKey + 'Shift-z'] =\n    keyHandlers[ctrlKey + 'Shift-Z'] =\n        (self: Squire, event: KeyboardEvent): void => {\n            event.preventDefault();\n            self.redo();\n        };\n\nexport { _onKey, keyHandlers };\n", "import {\n    TreeIterator,\n    SHOW_TEXT,\n    SHOW_ELEMENT_OR_TEXT,\n} from './node/TreeIterator';\nimport {\n    createElement,\n    detach,\n    empty,\n    getNearest,\n    hasTagAttributes,\n    replaceWith,\n} from './node/Node';\nimport {\n    isLeaf,\n    isInline,\n    resetNodeCategoryCache,\n    isContainer,\n    isBlock,\n} from './node/Category';\nimport { isLineBreak, removeZWS } from './node/Whitespace';\nimport {\n    moveRangeBoundariesDownTree,\n    isNodeContainedInRange,\n    moveRangeBoundaryOutOf,\n    moveRangeBoundariesUpTree,\n} from './range/Boundaries';\nimport {\n    createRange,\n    deleteContentsOfRange,\n    extractContentsOfRange,\n    insertNodeInRange,\n    insertTreeFragmentIntoRange,\n} from './range/InsertDelete';\nimport {\n    fixContainer,\n    fixCursor,\n    mergeContainers,\n    mergeInlines,\n    split,\n} from './node/MergeSplit';\nimport { getBlockWalker, getNextBlock, isEmptyBlock } from './node/Block';\nimport { cleanTree, cleanupBRs, escapeHTML, removeEmptyInlines } from './Clean';\nimport { cantFocusEmptyTextNodes, ZWS } from './Constants';\nimport {\n    expandRangeToBlockBoundaries,\n    getEndBlockOfRange,\n    getStartBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n    rangeDoesStartAtBlockBoundary,\n} from './range/Block';\nimport {\n    _monitorShiftKey,\n    _onCopy,\n    _onCut,\n    _onDrop,\n    _onPaste,\n} from './Clipboard';\nimport { keyHandlers, _onKey } from './keyboard/KeyHandlers';\nimport { linkifyText } from './keyboard/KeyHelpers';\nimport { getTextContentsOfRange } from './range/Contents';\n\ndeclare const DOMPurify: any;\n\n// ---\n\ntype EventHandler = { handleEvent: (e: Event) => void } | ((e: Event) => void);\n\ntype KeyHandlerFunction = (x: Squire, y: KeyboardEvent, z: Range) => void;\n\ntype TagAttributes = {\n    [key: string]: { [key: string]: string };\n};\n\ninterface SquireConfig {\n    blockTag: string;\n    blockAttributes: null | Record<string, string>;\n    tagAttributes: TagAttributes;\n    classNames: {\n        color: string;\n        fontFamily: string;\n        fontSize: string;\n        highlight: string;\n    };\n    undo: {\n        documentSizeThreshold: number;\n        undoLimit: number;\n    };\n    addLinks: boolean;\n    willCutCopy: null | ((html: string) => string);\n    toPlainText: null | ((html: string) => string);\n    sanitizeToDOMFragment: (html: string, editor: Squire) => DocumentFragment;\n    didError: (x: any) => void;\n}\n\n// ---\n\nclass Squire {\n    _root: HTMLElement;\n    _config: SquireConfig;\n\n    _isFocused: boolean;\n    _lastSelection: Range;\n    _willRestoreSelection: boolean;\n    _mayHaveZWS: boolean;\n\n    _lastAnchorNode: Node | null;\n    _lastFocusNode: Node | null;\n    _path: string;\n\n    _events: Map<string, Array<EventHandler>>;\n\n    _undoIndex: number;\n    _undoStack: Array<string>;\n    _undoStackLength: number;\n    _isInUndoState: boolean;\n    _ignoreChange: boolean;\n    _ignoreAllChanges: boolean;\n\n    _isShiftDown: boolean;\n    _keyHandlers: Record<string, KeyHandlerFunction>;\n\n    _mutation: MutationObserver;\n\n    constructor(root: HTMLElement, config?: Partial<SquireConfig>) {\n        this._root = root;\n\n        this._config = this._makeConfig(config);\n\n        this._isFocused = false;\n        this._lastSelection = createRange(root, 0);\n        this._willRestoreSelection = false;\n        this._mayHaveZWS = false;\n\n        this._lastAnchorNode = null;\n        this._lastFocusNode = null;\n        this._path = '';\n\n        this._events = new Map();\n\n        this._undoIndex = -1;\n        this._undoStack = [];\n        this._undoStackLength = 0;\n        this._isInUndoState = false;\n        this._ignoreChange = false;\n        this._ignoreAllChanges = false;\n\n        // Add event listeners\n        this.addEventListener('selectionchange', this._updatePathOnEvent);\n\n        // On blur, restore focus except if the user taps or clicks to focus a\n        // specific point. Can't actually use click event because focus happens\n        // before click, so use mousedown/touchstart\n        this.addEventListener('blur', this._enableRestoreSelection);\n        this.addEventListener('mousedown', this._disableRestoreSelection);\n        this.addEventListener('touchstart', this._disableRestoreSelection);\n        this.addEventListener('focus', this._restoreSelection);\n\n        // Clipboard support\n        this._isShiftDown = false;\n        this.addEventListener('cut', _onCut as (e: Event) => void);\n        this.addEventListener('copy', _onCopy as (e: Event) => void);\n        this.addEventListener('paste', _onPaste as (e: Event) => void);\n        this.addEventListener('drop', _onDrop as (e: Event) => void);\n        this.addEventListener(\n            'keydown',\n            _monitorShiftKey as (e: Event) => void,\n        );\n        this.addEventListener('keyup', _monitorShiftKey as (e: Event) => void);\n\n        // Keyboard support\n        this.addEventListener('keydown', _onKey as (e: Event) => void);\n        this._keyHandlers = Object.create(keyHandlers);\n\n        const mutation = new MutationObserver(() => this._docWasChanged());\n        mutation.observe(root, {\n            childList: true,\n            attributes: true,\n            characterData: true,\n            subtree: true,\n        });\n        this._mutation = mutation;\n\n        // Make it editable\n        root.setAttribute('contenteditable', 'true');\n\n        // Modern browsers let you override their default content editable\n        // handling!\n        this.addEventListener(\n            'beforeinput',\n            this._beforeInput as (e: Event) => void,\n        );\n\n        this.setHTML('');\n    }\n\n    destroy(): void {\n        this._events.forEach((_, type) => {\n            this.removeEventListener(type);\n        });\n\n        this._mutation.disconnect();\n\n        this._undoIndex = -1;\n        this._undoStack = [];\n        this._undoStackLength = 0;\n    }\n\n    _makeConfig(userConfig?: object): SquireConfig {\n        const config = {\n            blockTag: 'DIV',\n            blockAttributes: null,\n            tagAttributes: {},\n            classNames: {\n                color: 'color',\n                fontFamily: 'font',\n                fontSize: 'size',\n                highlight: 'highlight',\n            },\n            undo: {\n                documentSizeThreshold: -1, // -1 means no threshold\n                undoLimit: -1, // -1 means no limit\n            },\n            addLinks: true,\n            willCutCopy: null,\n            toPlainText: null,\n            sanitizeToDOMFragment: (\n                html: string,\n                /* editor: Squire, */\n            ): DocumentFragment => {\n                const frag = DOMPurify.sanitize(html, {\n                    ALLOW_UNKNOWN_PROTOCOLS: true,\n                    WHOLE_DOCUMENT: false,\n                    RETURN_DOM: true,\n                    RETURN_DOM_FRAGMENT: true,\n                    FORCE_BODY: false,\n                });\n                return frag\n                    ? document.importNode(frag, true)\n                    : document.createDocumentFragment();\n            },\n            didError: (error: any): void => console.log(error),\n        };\n        if (userConfig) {\n            Object.assign(config, userConfig);\n            config.blockTag = config.blockTag.toUpperCase();\n        }\n\n        return config;\n    }\n\n    setKeyHandler(key: string, fn: KeyHandlerFunction) {\n        this._keyHandlers[key] = fn;\n        return this;\n    }\n\n    _beforeInput(event: InputEvent): void {\n        switch (event.inputType) {\n            case 'insertLineBreak':\n                event.preventDefault();\n                this.splitBlock(true);\n                break;\n            case 'insertParagraph':\n                event.preventDefault();\n                this.splitBlock(false);\n                break;\n            case 'insertOrderedList':\n                event.preventDefault();\n                this.makeOrderedList();\n                break;\n            case 'insertUnoderedList':\n                event.preventDefault();\n                this.makeUnorderedList();\n                break;\n            case 'historyUndo':\n                event.preventDefault();\n                this.undo();\n                break;\n            case 'historyRedo':\n                event.preventDefault();\n                this.redo();\n                break;\n            case 'formatBold':\n                event.preventDefault();\n                this.bold();\n                break;\n            case 'formaItalic':\n                event.preventDefault();\n                this.italic();\n                break;\n            case 'formatUnderline':\n                event.preventDefault();\n                this.underline();\n                break;\n            case 'formatStrikeThrough':\n                event.preventDefault();\n                this.strikethrough();\n                break;\n            case 'formatSuperscript':\n                event.preventDefault();\n                this.superscript();\n                break;\n            case 'formatSubscript':\n                event.preventDefault();\n                this.subscript();\n                break;\n            case 'formatJustifyFull':\n            case 'formatJustifyCenter':\n            case 'formatJustifyRight':\n            case 'formatJustifyLeft': {\n                event.preventDefault();\n                let alignment = event.inputType.slice(13).toLowerCase();\n                if (alignment === 'full') {\n                    alignment = 'justify';\n                }\n                this.setTextAlignment(alignment);\n                break;\n            }\n            case 'formatRemove':\n                event.preventDefault();\n                this.removeAllFormatting();\n                break;\n            case 'formatSetBlockTextDirection': {\n                event.preventDefault();\n                let dir = event.data;\n                if (dir === 'null') {\n                    dir = null;\n                }\n                this.setTextDirection(dir);\n                break;\n            }\n            case 'formatBackColor':\n                event.preventDefault();\n                this.setHighlightColor(event.data);\n                break;\n            case 'formatFontColor':\n                event.preventDefault();\n                this.setTextColor(event.data);\n                break;\n            case 'formatFontName':\n                event.preventDefault();\n                this.setFontFace(event.data);\n                break;\n        }\n    }\n\n    // --- Events\n\n    handleEvent(event: Event): void {\n        this.fireEvent(event.type, event);\n    }\n\n    fireEvent(type: string, detail?: Event | object): Squire {\n        let handlers = this._events.get(type);\n        // UI code, especially modal views, may be monitoring for focus events\n        // and immediately removing focus. In certain conditions, this can\n        // cause the focus event to fire after the blur event, which can cause\n        // an infinite loop. So we detect whether we're actually\n        // focused/blurred before firing.\n        if (/^(?:focus|blur)/.test(type)) {\n            const isFocused = this._root === document.activeElement;\n            if (type === 'focus') {\n                if (!isFocused || this._isFocused) {\n                    return this;\n                }\n                this._isFocused = true;\n            } else {\n                if (isFocused || !this._isFocused) {\n                    return this;\n                }\n                this._isFocused = false;\n            }\n        }\n        if (handlers) {\n            const event: Event =\n                detail instanceof Event\n                    ? detail\n                    : new CustomEvent(type, {\n                          detail,\n                      });\n            // Clone handlers array, so any handlers added/removed do not\n            // affect it.\n            handlers = handlers.slice();\n            for (const handler of handlers) {\n                try {\n                    if ('handleEvent' in handler) {\n                        handler.handleEvent(event);\n                    } else {\n                        handler.call(this, event);\n                    }\n                } catch (error) {\n                    this._config.didError(error);\n                }\n            }\n        }\n        return this;\n    }\n\n    /**\n     * Subscribing to these events won't automatically add a listener to the\n     * document node, since these events are fired in a custom manner by the\n     * editor code.\n     */\n    customEvents = new Set([\n        'pathChange',\n        'select',\n        'input',\n        'pasteImage',\n        'undoStateChange',\n    ]);\n\n    addEventListener(type: string, fn: EventHandler): Squire {\n        let handlers = this._events.get(type);\n        let target: Document | HTMLElement = this._root;\n        if (!handlers) {\n            handlers = [];\n            this._events.set(type, handlers);\n            if (!this.customEvents.has(type)) {\n                if (type === 'selectionchange') {\n                    target = document;\n                }\n                target.addEventListener(type, this, true);\n            }\n        }\n        handlers.push(fn);\n        return this;\n    }\n\n    removeEventListener(type: string, fn?: EventHandler): Squire {\n        const handlers = this._events.get(type);\n        let target: Document | HTMLElement = this._root;\n        if (handlers) {\n            if (fn) {\n                let l = handlers.length;\n                while (l--) {\n                    if (handlers[l] === fn) {\n                        handlers.splice(l, 1);\n                    }\n                }\n            } else {\n                handlers.length = 0;\n            }\n            if (!handlers.length) {\n                this._events.delete(type);\n                if (!this.customEvents.has(type)) {\n                    if (type === 'selectionchange') {\n                        target = document;\n                    }\n                    target.removeEventListener(type, this, true);\n                }\n            }\n        }\n        return this;\n    }\n\n    // --- Focus\n\n    focus(): Squire {\n        this._root.focus({ preventScroll: true });\n        return this;\n    }\n\n    blur(): Squire {\n        this._root.blur();\n        return this;\n    }\n\n    // --- Selection and bookmarking\n\n    _enableRestoreSelection(): void {\n        this._willRestoreSelection = true;\n    }\n\n    _disableRestoreSelection(): void {\n        this._willRestoreSelection = false;\n    }\n\n    _restoreSelection() {\n        if (this._willRestoreSelection) {\n            this.setSelection(this._lastSelection);\n        }\n    }\n\n    // ---\n\n    _removeZWS(): void {\n        if (!this._mayHaveZWS) {\n            return;\n        }\n        removeZWS(this._root);\n        this._mayHaveZWS = false;\n    }\n\n    // ---\n\n    startSelectionId = 'squire-selection-start';\n    endSelectionId = 'squire-selection-end';\n\n    _saveRangeToBookmark(range: Range): void {\n        let startNode = createElement('INPUT', {\n            id: this.startSelectionId,\n            type: 'hidden',\n        });\n        let endNode = createElement('INPUT', {\n            id: this.endSelectionId,\n            type: 'hidden',\n        });\n        let temp: HTMLElement;\n\n        insertNodeInRange(range, startNode);\n        range.collapse(false);\n        insertNodeInRange(range, endNode);\n\n        // In a collapsed range, the start is sometimes inserted after the end!\n        if (\n            startNode.compareDocumentPosition(endNode) &\n            Node.DOCUMENT_POSITION_PRECEDING\n        ) {\n            startNode.id = this.endSelectionId;\n            endNode.id = this.startSelectionId;\n            temp = startNode;\n            startNode = endNode;\n            endNode = temp;\n        }\n\n        range.setStartAfter(startNode);\n        range.setEndBefore(endNode);\n    }\n\n    _getRangeAndRemoveBookmark(range?: Range): Range | null {\n        const root = this._root;\n        const start = root.querySelector('#' + this.startSelectionId);\n        const end = root.querySelector('#' + this.endSelectionId);\n\n        if (start && end) {\n            let startContainer: Node = start.parentNode!;\n            let endContainer: Node = end.parentNode!;\n            const startOffset = Array.from(startContainer.childNodes).indexOf(\n                start,\n            );\n            let endOffset = Array.from(endContainer.childNodes).indexOf(end);\n\n            if (startContainer === endContainer) {\n                endOffset -= 1;\n            }\n\n            start.remove();\n            end.remove();\n\n            if (!range) {\n                range = document.createRange();\n            }\n            range.setStart(startContainer, startOffset);\n            range.setEnd(endContainer, endOffset);\n\n            // Merge any text nodes we split\n            mergeInlines(startContainer, range);\n            if (startContainer !== endContainer) {\n                mergeInlines(endContainer, range);\n            }\n\n            // If we didn't split a text node, we should move into any adjacent\n            // text node to current selection point\n            if (range.collapsed) {\n                startContainer = range.startContainer;\n                if (startContainer instanceof Text) {\n                    endContainer = startContainer.childNodes[range.startOffset];\n                    if (!endContainer || !(endContainer instanceof Text)) {\n                        endContainer =\n                            startContainer.childNodes[range.startOffset - 1];\n                    }\n                    if (endContainer && endContainer instanceof Text) {\n                        range.setStart(endContainer, 0);\n                        range.collapse(true);\n                    }\n                }\n            }\n        }\n        return range || null;\n    }\n\n    getSelection(): Range {\n        const selection = window.getSelection();\n        const root = this._root;\n        let range: Range | null = null;\n        // If not focused, always rely on cached selection; another function may\n        // have set it but the DOM is not modified until focus again\n        if (this._isFocused && selection && selection.rangeCount) {\n            range = selection.getRangeAt(0).cloneRange();\n            const startContainer = range.startContainer;\n            const endContainer = range.endContainer;\n            // FF can return the selection as being inside an <img>. WTF?\n            if (startContainer && isLeaf(startContainer)) {\n                range.setStartBefore(startContainer);\n            }\n            if (endContainer && isLeaf(endContainer)) {\n                range.setEndBefore(endContainer);\n            }\n        }\n        if (range && root.contains(range.commonAncestorContainer)) {\n            this._lastSelection = range;\n        } else {\n            range = this._lastSelection;\n            // Check the editor is in the live document; if not, the range has\n            // probably been rewritten by the browser and is bogus\n            if (!document.contains(range.commonAncestorContainer)) {\n                range = null;\n            }\n        }\n        if (!range) {\n            range = createRange(root.firstElementChild || root, 0);\n        }\n        return range;\n    }\n\n    setSelection(range: Range): Squire {\n        this._lastSelection = range;\n        // If we're setting selection, that automatically, and synchronously,\n        // triggers a focus event. So just store the selection and mark it as\n        // needing restore on focus.\n        if (!this._isFocused) {\n            this._enableRestoreSelection();\n        } else {\n            const selection = window.getSelection();\n            if (selection) {\n                if ('setBaseAndExtent' in Selection.prototype) {\n                    selection.setBaseAndExtent(\n                        range.startContainer,\n                        range.startOffset,\n                        range.endContainer,\n                        range.endOffset,\n                    );\n                } else {\n                    selection.removeAllRanges();\n                    selection.addRange(range);\n                }\n            }\n        }\n        return this;\n    }\n\n    // ---\n\n    _moveCursorTo(toStart: boolean): Squire {\n        const root = this._root;\n        const range = createRange(root, toStart ? 0 : root.childNodes.length);\n        moveRangeBoundariesDownTree(range);\n        this.setSelection(range);\n        return this;\n    }\n\n    moveCursorToStart(): Squire {\n        return this._moveCursorTo(true);\n    }\n\n    moveCursorToEnd(): Squire {\n        return this._moveCursorTo(false);\n    }\n\n    // ---\n\n    getCursorPosition(): DOMRect {\n        const range = this.getSelection();\n        let rect = range.getBoundingClientRect();\n        // If the range is outside of the viewport, some browsers at least\n        // will return 0 for all the values; need to get a DOM node to find\n        // the position instead.\n        if (rect && !rect.top) {\n            this._ignoreChange = true;\n            const node = createElement('SPAN');\n            node.textContent = ZWS;\n            insertNodeInRange(range, node);\n            rect = node.getBoundingClientRect();\n            const parent = node.parentNode!;\n            parent.removeChild(node);\n            mergeInlines(parent, range);\n        }\n        return rect;\n    }\n\n    // --- Path\n\n    getPath(): string {\n        return this._path;\n    }\n\n    _updatePathOnEvent(): void {\n        if (this._isFocused) {\n            this._updatePath(this.getSelection());\n        }\n    }\n\n    _updatePath(range: Range, force?: boolean): void {\n        const anchor = range.startContainer;\n        const focus = range.endContainer;\n        let newPath: string;\n        if (\n            force ||\n            anchor !== this._lastAnchorNode ||\n            focus !== this._lastFocusNode\n        ) {\n            this._lastAnchorNode = anchor;\n            this._lastFocusNode = focus;\n            newPath =\n                anchor && focus\n                    ? anchor === focus\n                        ? this._getPath(focus)\n                        : '(selection)'\n                    : '';\n            if (this._path !== newPath) {\n                this._path = newPath;\n                this.fireEvent('pathChange', {\n                    path: newPath,\n                });\n            }\n        }\n        this.fireEvent(range.collapsed ? 'cursor' : 'select', {\n            range: range,\n        });\n    }\n\n    _getPath(node: Node) {\n        const root = this._root;\n        const config = this._config;\n        let path = '';\n        if (node && node !== root) {\n            const parent = node.parentNode;\n            path = parent ? this._getPath(parent) : '';\n            if (node instanceof HTMLElement) {\n                const id = node.id;\n                const classList = node.classList;\n                const classNames = Array.from(classList).sort();\n                const dir = node.dir;\n                const styleNames = config.classNames;\n                path += (path ? '>' : '') + node.nodeName;\n                if (id) {\n                    path += '#' + id;\n                }\n                if (classNames.length) {\n                    path += '.';\n                    path += classNames.join('.');\n                }\n                if (dir) {\n                    path += '[dir=' + dir + ']';\n                }\n                if (classList.contains(styleNames.highlight)) {\n                    path +=\n                        '[backgroundColor=' +\n                        node.style.backgroundColor.replace(/ /g, '') +\n                        ']';\n                }\n                if (classList.contains(styleNames.color)) {\n                    path +=\n                        '[color=' + node.style.color.replace(/ /g, '') + ']';\n                }\n                if (classList.contains(styleNames.fontFamily)) {\n                    path +=\n                        '[fontFamily=' +\n                        node.style.fontFamily.replace(/ /g, '') +\n                        ']';\n                }\n                if (classList.contains(styleNames.fontSize)) {\n                    path += '[fontSize=' + node.style.fontSize + ']';\n                }\n            }\n        }\n        return path;\n    }\n\n    // --- History\n\n    modifyDocument(modificationFn: () => void): Squire {\n        const mutation = this._mutation;\n        if (mutation) {\n            if (mutation.takeRecords().length) {\n                this._docWasChanged();\n            }\n            mutation.disconnect();\n        }\n\n        this._ignoreAllChanges = true;\n        modificationFn();\n        this._ignoreAllChanges = false;\n\n        if (mutation) {\n            mutation.observe(this._root, {\n                childList: true,\n                attributes: true,\n                characterData: true,\n                subtree: true,\n            });\n            this._ignoreChange = false;\n        }\n\n        return this;\n    }\n\n    _docWasChanged(): void {\n        resetNodeCategoryCache();\n        this._mayHaveZWS = true;\n        if (this._ignoreAllChanges) {\n            return;\n        }\n\n        if (this._ignoreChange) {\n            this._ignoreChange = false;\n            return;\n        }\n        if (this._isInUndoState) {\n            this._isInUndoState = false;\n            this.fireEvent('undoStateChange', {\n                canUndo: true,\n                canRedo: false,\n            });\n        }\n        this.fireEvent('input');\n    }\n\n    /**\n     * Leaves bookmark.\n     */\n    _recordUndoState(range: Range, replace?: boolean): Squire {\n        const isInUndoState = this._isInUndoState;\n        if (!isInUndoState || replace) {\n            // Advance pointer to new position\n            let undoIndex = this._undoIndex + 1;\n            const undoStack = this._undoStack;\n            const undoConfig = this._config.undo;\n            const undoThreshold = undoConfig.documentSizeThreshold;\n            const undoLimit = undoConfig.undoLimit;\n\n            // Truncate stack if longer (i.e. if has been previously undone)\n            if (undoIndex < this._undoStackLength) {\n                undoStack.length = this._undoStackLength = undoIndex;\n            }\n\n            // Add bookmark\n            if (range) {\n                this._saveRangeToBookmark(range);\n            }\n\n            // Don't record if we're already in an undo state\n            if (isInUndoState) {\n                return this;\n            }\n\n            // Get data\n            const html = this._getRawHTML();\n\n            // If this document is above the configured size threshold,\n            // limit the number of saved undo states.\n            // Threshold is in bytes, JS uses 2 bytes per character\n            if (replace) {\n                undoIndex -= 1;\n            }\n            if (undoThreshold > -1 && html.length * 2 > undoThreshold) {\n                if (undoLimit > -1 && undoIndex > undoLimit) {\n                    undoStack.splice(0, undoIndex - undoLimit);\n                    undoIndex = undoLimit;\n                    this._undoStackLength = undoLimit;\n                }\n            }\n\n            // Save data\n            undoStack[undoIndex] = html;\n            this._undoIndex = undoIndex;\n            this._undoStackLength += 1;\n            this._isInUndoState = true;\n        }\n        return this;\n    }\n\n    saveUndoState(range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        this._recordUndoState(range, this._isInUndoState);\n        this._getRangeAndRemoveBookmark(range);\n\n        return this;\n    }\n\n    undo(): Squire {\n        // Sanity check: must not be at beginning of the history stack\n        if (this._undoIndex !== 0 || !this._isInUndoState) {\n            // Make sure any changes since last checkpoint are saved.\n            this._recordUndoState(this.getSelection(), false);\n            this._undoIndex -= 1;\n            this._setRawHTML(this._undoStack[this._undoIndex]);\n            const range = this._getRangeAndRemoveBookmark();\n            if (range) {\n                this.setSelection(range);\n            }\n            this._isInUndoState = true;\n            this.fireEvent('undoStateChange', {\n                canUndo: this._undoIndex !== 0,\n                canRedo: true,\n            });\n            this.fireEvent('input');\n        }\n        return this.focus();\n    }\n\n    redo(): Squire {\n        // Sanity check: must not be at end of stack and must be in an undo\n        // state.\n        const undoIndex = this._undoIndex;\n        const undoStackLength = this._undoStackLength;\n        if (undoIndex + 1 < undoStackLength && this._isInUndoState) {\n            this._undoIndex += 1;\n            this._setRawHTML(this._undoStack[this._undoIndex]);\n            const range = this._getRangeAndRemoveBookmark();\n            if (range) {\n                this.setSelection(range);\n            }\n            this.fireEvent('undoStateChange', {\n                canUndo: true,\n                canRedo: undoIndex + 2 < undoStackLength,\n            });\n            this.fireEvent('input');\n        }\n        return this.focus();\n    }\n\n    // --- Get and set data\n\n    getRoot(): HTMLElement {\n        return this._root;\n    }\n\n    _getRawHTML(): string {\n        return this._root.innerHTML;\n    }\n\n    _setRawHTML(html: string): Squire {\n        const root = this._root;\n        root.innerHTML = html;\n\n        let node: Element | null = root;\n        const child = node.firstChild;\n        if (!child || child.nodeName === 'BR') {\n            const block = this.createDefaultBlock();\n            if (child) {\n                node.replaceChild(block, child);\n            } else {\n                node.appendChild(block);\n            }\n        } else {\n            while ((node = getNextBlock(node, root))) {\n                fixCursor(node);\n            }\n        }\n\n        this._ignoreChange = true;\n\n        return this;\n    }\n\n    getHTML(withBookmark?: boolean): string {\n        let range: Range | undefined;\n        if (withBookmark) {\n            range = this.getSelection();\n            this._saveRangeToBookmark(range);\n        }\n        const html = this._getRawHTML().replace(/\\u200B/g, '');\n        if (withBookmark) {\n            this._getRangeAndRemoveBookmark(range);\n        }\n        return html;\n    }\n\n    setHTML(html: string): Squire {\n        // Parse HTML into DOM tree\n        const frag = this._config.sanitizeToDOMFragment(html, this);\n        const root = this._root;\n\n        // Fixup DOM tree\n        cleanTree(frag, this._config);\n        cleanupBRs(frag, root, false);\n        fixContainer(frag, root);\n\n        // Fix cursor\n        let node: DocumentFragment | HTMLElement | null = frag;\n        let child = node.firstChild;\n        if (!child || child.nodeName === 'BR') {\n            const block = this.createDefaultBlock();\n            if (child) {\n                node.replaceChild(block, child);\n            } else {\n                node.appendChild(block);\n            }\n        } else {\n            while ((node = getNextBlock(node, root))) {\n                fixCursor(node);\n            }\n        }\n\n        // Don't fire an input event\n        this._ignoreChange = true;\n\n        // Remove existing root children and insert new content\n        while ((child = root.lastChild)) {\n            root.removeChild(child);\n        }\n        root.appendChild(frag);\n\n        // Reset the undo stack\n        this._undoIndex = -1;\n        this._undoStack.length = 0;\n        this._undoStackLength = 0;\n        this._isInUndoState = false;\n\n        // Record undo state\n        const range =\n            this._getRangeAndRemoveBookmark() ||\n            createRange(root.firstElementChild || root, 0);\n        this.saveUndoState(range);\n\n        // Set inital selection\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    /**\n     * Insert HTML at the cursor location. If the selection is not collapsed\n     * insertTreeFragmentIntoRange will delete the selection so that it is\n     * replaced by the html being inserted.\n     */\n    insertHTML(html: string, isPaste?: boolean): Squire {\n        // Parse\n        const config = this._config;\n        let frag = config.sanitizeToDOMFragment(html, this);\n\n        // Record undo checkpoint\n        const range = this.getSelection();\n        this.saveUndoState(range);\n\n        try {\n            const root = this._root;\n\n            if (config.addLinks) {\n                this.addDetectedLinks(frag, frag);\n            }\n            cleanTree(frag, this._config);\n            cleanupBRs(frag, root, false);\n            removeEmptyInlines(frag);\n            frag.normalize();\n\n            let node: HTMLElement | DocumentFragment | null = frag;\n            while ((node = getNextBlock(node, frag))) {\n                fixCursor(node);\n            }\n\n            let doInsert = true;\n            if (isPaste) {\n                const event = new CustomEvent('willPaste', {\n                    cancelable: true,\n                    detail: {\n                        fragment: frag,\n                    },\n                });\n                this.fireEvent('willPaste', event);\n                frag = event.detail.fragment;\n                doInsert = !event.defaultPrevented;\n            }\n\n            if (doInsert) {\n                insertTreeFragmentIntoRange(range, frag, root);\n                range.collapse(false);\n\n                // After inserting the fragment, check whether the cursor is\n                // inside an <a> element and if so if there is an equivalent\n                // cursor position after the <a> element. If there is, move it\n                // there.\n                moveRangeBoundaryOutOf(range, 'A', root);\n\n                this._ensureBottomLine();\n            }\n\n            this.setSelection(range);\n            this._updatePath(range, true);\n            // Safari sometimes loses focus after paste. Weird.\n            if (isPaste) {\n                this.focus();\n            }\n        } catch (error) {\n            this._config.didError(error);\n        }\n        return this;\n    }\n\n    insertElement(el: Element, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        range.collapse(true);\n        if (isInline(el)) {\n            insertNodeInRange(range, el);\n            range.setStartAfter(el);\n        } else {\n            // Get containing block node.\n            const root = this._root;\n            const startNode: HTMLElement | null = getStartBlockOfRange(\n                range,\n                root,\n            );\n            let splitNode: Element | Node = startNode || root;\n\n            let nodeAfterSplit: Node | null = null;\n            // While at end of container node, move up DOM tree.\n            while (splitNode !== root && !splitNode.nextSibling) {\n                splitNode = splitNode.parentNode!;\n            }\n            // If in the middle of a container node, split up to root.\n            if (splitNode !== root) {\n                const parent = splitNode.parentNode!;\n                nodeAfterSplit = split(\n                    parent,\n                    splitNode.nextSibling,\n                    root,\n                    root,\n                ) as Node;\n            }\n\n            // If the startNode was empty remove it so that we don't end up\n            // with two blank lines.\n            if (startNode && isEmptyBlock(startNode)) {\n                detach(startNode);\n            }\n\n            // Insert element and blank line.\n            root.insertBefore(el, nodeAfterSplit);\n            const blankLine = this.createDefaultBlock();\n            root.insertBefore(blankLine, nodeAfterSplit);\n\n            // Move cursor to blank line after inserted element.\n            range.setStart(blankLine, 0);\n            range.setEnd(blankLine, 0);\n            moveRangeBoundariesDownTree(range);\n        }\n        this.focus();\n        this.setSelection(range);\n        this._updatePath(range);\n\n        return this;\n    }\n\n    insertImage(\n        src: string,\n        attributes: Record<string, string>,\n    ): HTMLImageElement {\n        const img = createElement(\n            'IMG',\n            Object.assign(\n                {\n                    src: src,\n                },\n                attributes,\n            ),\n        ) as HTMLImageElement;\n        this.insertElement(img);\n        return img;\n    }\n\n    insertPlainText(plainText: string, isPaste: boolean): Squire {\n        const range = this.getSelection();\n        if (\n            range.collapsed &&\n            getNearest(range.startContainer, this._root, 'PRE')\n        ) {\n            const startContainer: Node = range.startContainer;\n            let offset = range.startOffset;\n            let textNode: Text;\n            if (!startContainer || !(startContainer instanceof Text)) {\n                const text = document.createTextNode('');\n                startContainer.insertBefore(\n                    text,\n                    startContainer.childNodes[offset],\n                );\n                textNode = text;\n                offset = 0;\n            } else {\n                textNode = startContainer;\n            }\n            let doInsert = true;\n            if (isPaste) {\n                const event = new CustomEvent('willPaste', {\n                    cancelable: true,\n                    detail: {\n                        text: plainText,\n                    },\n                });\n                this.fireEvent('willPaste', event);\n                plainText = event.detail.text;\n                doInsert = !event.defaultPrevented;\n            }\n\n            if (doInsert) {\n                textNode.insertData(offset, plainText);\n                range.setStart(textNode, offset + plainText.length);\n                range.collapse(true);\n            }\n            this.setSelection(range);\n            return this;\n        }\n        const lines = plainText.split('\\n');\n        const config = this._config;\n        const tag = config.blockTag;\n        const attributes = config.blockAttributes;\n        const closeBlock = '</' + tag + '>';\n        let openBlock = '<' + tag;\n\n        for (const attr in attributes) {\n            openBlock += ' ' + attr + '=\"' + escapeHTML(attributes[attr]) + '\"';\n        }\n        openBlock += '>';\n\n        for (let i = 0, l = lines.length; i < l; i += 1) {\n            let line = lines[i];\n            line = escapeHTML(line).replace(/ (?=(?: |$))/g, '&nbsp;');\n            // We don't wrap the first line in the block, so if it gets inserted\n            // into a blank line it keeps that line's formatting.\n            // Wrap each line in <div></div>\n            if (i) {\n                line = openBlock + (line || '<BR>') + closeBlock;\n            }\n            lines[i] = line;\n        }\n        return this.insertHTML(lines.join(''), isPaste);\n    }\n\n    getSelectedText(range?: Range): string {\n        return getTextContentsOfRange(range || this.getSelection());\n    }\n\n    // --- Inline formatting\n\n    /**\n     * Extracts the font-family and font-size (if any) of the element\n     * holding the cursor. If there's a selection, returns an empty object.\n     */\n    getFontInfo(range?: Range): Record<string, string | undefined> {\n        const fontInfo = {\n            color: undefined,\n            backgroundColor: undefined,\n            fontFamily: undefined,\n            fontSize: undefined,\n        } as Record<string, string | undefined>;\n\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        let seenAttributes = 0;\n        let element: Node | null = range.commonAncestorContainer;\n        if (range.collapsed || element instanceof Text) {\n            if (element instanceof Text) {\n                element = element.parentNode!;\n            }\n            while (seenAttributes < 4 && element) {\n                const style = (element as HTMLElement).style;\n                if (style) {\n                    const color = style.color;\n                    if (!fontInfo.color && color) {\n                        fontInfo.color = color;\n                        seenAttributes += 1;\n                    }\n                    const backgroundColor = style.backgroundColor;\n                    if (!fontInfo.backgroundColor && backgroundColor) {\n                        fontInfo.backgroundColor = backgroundColor;\n                        seenAttributes += 1;\n                    }\n                    const fontFamily = style.fontFamily;\n                    if (!fontInfo.fontFamily && fontFamily) {\n                        fontInfo.fontFamily = fontFamily;\n                        seenAttributes += 1;\n                    }\n                    const fontSize = style.fontSize;\n                    if (!fontInfo.fontSize && fontSize) {\n                        fontInfo.fontSize = fontSize;\n                        seenAttributes += 1;\n                    }\n                }\n                element = element.parentNode;\n            }\n        }\n        return fontInfo;\n    }\n\n    /**\n     * Looks for matching tag and attributes, so won't work if <strong>\n     * instead of <b> etc.\n     */\n    hasFormat(\n        tag: string,\n        attributes?: Record<string, string> | null,\n        range?: Range,\n    ): boolean {\n        // 1. Normalise the arguments and get selection\n        tag = tag.toUpperCase();\n        if (!attributes) {\n            attributes = {};\n        }\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Move range up one level in the DOM tree if at the edge of a text\n        // node, so we don't consider it included when it's not really.\n        if (\n            !range.collapsed &&\n            range.startContainer instanceof Text &&\n            range.startOffset === range.startContainer.length &&\n            range.startContainer.nextSibling\n        ) {\n            range.setStartBefore(range.startContainer.nextSibling);\n        }\n        if (\n            !range.collapsed &&\n            range.endContainer instanceof Text &&\n            range.endOffset === 0 &&\n            range.endContainer.previousSibling\n        ) {\n            range.setEndAfter(range.endContainer.previousSibling);\n        }\n\n        // If the common ancestor is inside the tag we require, we definitely\n        // have the format.\n        const root = this._root;\n        const common = range.commonAncestorContainer;\n        if (getNearest(common, root, tag, attributes)) {\n            return true;\n        }\n\n        // If common ancestor is a text node and doesn't have the format, we\n        // definitely don't have it.\n        if (common instanceof Text) {\n            return false;\n        }\n\n        // Otherwise, check each text node at least partially contained within\n        // the selection and make sure all of them have the format we want.\n        const walker = new TreeIterator<Text>(common, SHOW_TEXT, (node) => {\n            return isNodeContainedInRange(range!, node, true);\n        });\n\n        let seenNode = false;\n        let node: Node | null;\n        while ((node = walker.nextNode())) {\n            if (!getNearest(node, root, tag, attributes)) {\n                return false;\n            }\n            seenNode = true;\n        }\n\n        return seenNode;\n    }\n\n    changeFormat(\n        add: { tag: string; attributes?: Record<string, string> } | null,\n        remove?: { tag: string; attributes?: Record<string, string> } | null,\n        range?: Range,\n        partial?: boolean,\n    ): Squire {\n        // Normalise the arguments and get selection\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Save undo checkpoint\n        this.saveUndoState(range);\n\n        if (remove) {\n            range = this._removeFormat(\n                remove.tag.toUpperCase(),\n                remove.attributes || {},\n                range,\n                partial,\n            );\n        }\n        if (add) {\n            range = this._addFormat(\n                add.tag.toUpperCase(),\n                add.attributes || {},\n                range,\n            );\n        }\n\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    _addFormat(\n        tag: string,\n        attributes: Record<string, string> | null,\n        range: Range,\n    ): Range {\n        // If the range is collapsed we simply insert the node by wrapping\n        // it round the range and focus it.\n        const root = this._root;\n        if (range.collapsed) {\n            const el = fixCursor(createElement(tag, attributes));\n            insertNodeInRange(range, el);\n            const focusNode = el.firstChild || el;\n            // Focus after the ZWS if present\n            const focusOffset =\n                focusNode instanceof Text ? focusNode.length : 0;\n            range.setStart(focusNode, focusOffset);\n            range.collapse(true);\n\n            // Clean up any previous formats that may have been set on this\n            // block that are unused.\n            let block = el;\n            while (isInline(block)) {\n                block = block.parentNode!;\n            }\n            removeZWS(block, el);\n            // Otherwise we find all the textnodes in the range (splitting\n            // partially selected nodes) and if they're not already formatted\n            // correctly we wrap them in the appropriate tag.\n        } else {\n            // Create an iterator to walk over all the text nodes under this\n            // ancestor which are in the range and not already formatted\n            // correctly.\n            //\n            // In Blink/WebKit, empty blocks may have no text nodes, just a\n            // <br>. Therefore we wrap this in the tag as well, as this will\n            // then cause it to apply when the user types something in the\n            // block, which is presumably what was intended.\n            //\n            // IMG tags are included because we may want to create a link around\n            // them, and adding other styles is harmless.\n            const walker = new TreeIterator<Element | Text>(\n                range.commonAncestorContainer,\n                SHOW_ELEMENT_OR_TEXT,\n                (node: Node) => {\n                    return (\n                        (node instanceof Text ||\n                            node.nodeName === 'BR' ||\n                            node.nodeName === 'IMG') &&\n                        isNodeContainedInRange(range, node, true)\n                    );\n                },\n            );\n\n            // Start at the beginning node of the range and iterate through\n            // all the nodes in the range that need formatting.\n            let { startContainer, startOffset, endContainer, endOffset } =\n                range;\n\n            // Make sure we start with a valid node.\n            walker.currentNode = startContainer;\n            if (\n                (!(startContainer instanceof Element) &&\n                    !(startContainer instanceof Text)) ||\n                !walker.filter(startContainer)\n            ) {\n                const next = walker.nextNode();\n                // If there are no interesting nodes in the selection, abort\n                if (!next) {\n                    return range;\n                }\n                startContainer = next;\n                startOffset = 0;\n            }\n\n            do {\n                let node = walker.currentNode;\n                const needsFormat = !getNearest(node, root, tag, attributes);\n                if (needsFormat) {\n                    // <br> can never be a container node, so must have a text\n                    // node if node == (end|start)Container\n                    if (\n                        node === endContainer &&\n                        (node as Text).length > endOffset\n                    ) {\n                        (node as Text).splitText(endOffset);\n                    }\n                    if (node === startContainer && startOffset) {\n                        node = (node as Text).splitText(startOffset);\n                        if (endContainer === startContainer) {\n                            endContainer = node;\n                            endOffset -= startOffset;\n                        } else if (endContainer === startContainer.parentNode) {\n                            endOffset += 1;\n                        }\n                        startContainer = node;\n                        startOffset = 0;\n                    }\n                    const el = createElement(tag, attributes);\n                    replaceWith(node, el);\n                    el.appendChild(node);\n                }\n            } while (walker.nextNode());\n\n            // Now set the selection to as it was before\n            range = createRange(\n                startContainer,\n                startOffset,\n                endContainer,\n                endOffset,\n            );\n        }\n        return range;\n    }\n\n    _removeFormat(\n        tag: string,\n        attributes: Record<string, string>,\n        range: Range,\n        partial?: boolean,\n    ): Range {\n        // Add bookmark\n        this._saveRangeToBookmark(range);\n\n        // We need a node in the selection to break the surrounding\n        // formatted text.\n        let fixer: Node | Text | null | undefined;\n        if (range.collapsed) {\n            if (cantFocusEmptyTextNodes) {\n                fixer = document.createTextNode(ZWS);\n            } else {\n                fixer = document.createTextNode('');\n            }\n            insertNodeInRange(range, fixer!);\n        }\n\n        // Find block-level ancestor of selection\n        let root = range.commonAncestorContainer;\n        while (isInline(root)) {\n            root = root.parentNode!;\n        }\n\n        // Find text nodes inside formatTags that are not in selection and\n        // add an extra tag with the same formatting.\n        const startContainer = range.startContainer;\n        const startOffset = range.startOffset;\n        const endContainer = range.endContainer;\n        const endOffset = range.endOffset;\n        const toWrap: [Node, Node][] = [];\n        const examineNode = (node: Node, exemplar: Node) => {\n            // If the node is completely contained by the range then\n            // we're going to remove all formatting so ignore it.\n            if (isNodeContainedInRange(range, node, false)) {\n                return;\n            }\n\n            let child: Node;\n            let next: Node;\n\n            // If not at least partially contained, wrap entire contents\n            // in a clone of the tag we're removing and we're done.\n            if (!isNodeContainedInRange(range, node, true)) {\n                // Ignore bookmarks and empty text nodes\n                if (\n                    !(node instanceof HTMLInputElement) &&\n                    (!(node instanceof Text) || node.data)\n                ) {\n                    toWrap.push([exemplar, node]);\n                }\n                return;\n            }\n\n            // Split any partially selected text nodes.\n            if (node instanceof Text) {\n                if (node === endContainer && endOffset !== node.length) {\n                    toWrap.push([exemplar, node.splitText(endOffset)]);\n                }\n                if (node === startContainer && startOffset) {\n                    node.splitText(startOffset);\n                    toWrap.push([exemplar, node]);\n                }\n            } else {\n                // If not a text node, recurse onto all children.\n                // Beware, the tree may be rewritten with each call\n                // to examineNode, hence find the next sibling first.\n                for (child = node.firstChild!; child; child = next) {\n                    next = child.nextSibling!;\n                    examineNode(child, exemplar);\n                }\n            }\n        };\n        const formatTags = Array.from(\n            (root as Element).getElementsByTagName(tag),\n        ).filter((el: Node): boolean => {\n            return (\n                isNodeContainedInRange(range, el, true) &&\n                hasTagAttributes(el, tag, attributes)\n            );\n        });\n\n        if (!partial) {\n            formatTags.forEach((node: Node) => {\n                examineNode(node, node);\n            });\n        }\n\n        // Now wrap unselected nodes in the tag\n        toWrap.forEach(([el, node]) => {\n            el = el.cloneNode(false);\n            replaceWith(node, el);\n            el.appendChild(node);\n        });\n        // and remove old formatting tags.\n        formatTags.forEach((el: Element) => {\n            replaceWith(el, empty(el));\n        });\n\n        if (cantFocusEmptyTextNodes && fixer) {\n            // Clean up any previous ZWS in this block. They are not needed,\n            // and this works around a Chrome bug where it doesn't render the\n            // text in some situations with multiple ZWS(!)\n            fixer = fixer.parentNode;\n            let block = fixer;\n            while (block && isInline(block)) {\n                block = block.parentNode;\n            }\n            if (block) {\n                removeZWS(block, fixer);\n            }\n        }\n\n        // Merge adjacent inlines:\n        this._getRangeAndRemoveBookmark(range);\n        if (fixer) {\n            range.collapse(false);\n        }\n        mergeInlines(root, range);\n\n        return range;\n    }\n\n    // ---\n\n    bold(): Squire {\n        return this.changeFormat({ tag: 'B' });\n    }\n\n    removeBold(): Squire {\n        return this.changeFormat(null, { tag: 'B' });\n    }\n\n    italic(): Squire {\n        return this.changeFormat({ tag: 'I' });\n    }\n\n    removeItalic(): Squire {\n        return this.changeFormat(null, { tag: 'I' });\n    }\n\n    underline(): Squire {\n        return this.changeFormat({ tag: 'U' });\n    }\n\n    removeUnderline(): Squire {\n        return this.changeFormat(null, { tag: 'U' });\n    }\n\n    strikethrough(): Squire {\n        return this.changeFormat({ tag: 'S' });\n    }\n\n    removeStrikethrough(): Squire {\n        return this.changeFormat(null, { tag: 'S' });\n    }\n\n    subscript(): Squire {\n        return this.changeFormat({ tag: 'SUB' }, { tag: 'SUP' });\n    }\n\n    removeSubscript(): Squire {\n        return this.changeFormat(null, { tag: 'SUB' });\n    }\n\n    superscript(): Squire {\n        return this.changeFormat({ tag: 'SUP' }, { tag: 'SUB' });\n    }\n\n    removeSuperscript(): Squire {\n        return this.changeFormat(null, { tag: 'SUP' });\n    }\n\n    // ---\n\n    makeLink(url: string, attributes?: Record<string, string>): Squire {\n        const range = this.getSelection();\n        if (range.collapsed) {\n            let protocolEnd = url.indexOf(':') + 1;\n            if (protocolEnd) {\n                while (url[protocolEnd] === '/') {\n                    protocolEnd += 1;\n                }\n            }\n            insertNodeInRange(\n                range,\n                document.createTextNode(url.slice(protocolEnd)),\n            );\n        }\n        attributes = Object.assign(\n            {\n                href: url,\n            },\n            this._config.tagAttributes.a,\n            attributes,\n        );\n\n        return this.changeFormat(\n            {\n                tag: 'A',\n                attributes: attributes as Record<string, string>,\n            },\n            {\n                tag: 'A',\n            },\n            range,\n        );\n    }\n\n    removeLink(): Squire {\n        return this.changeFormat(\n            null,\n            {\n                tag: 'A',\n            },\n            this.getSelection(),\n            true,\n        );\n    }\n\n    /*\n    linkRegExp = new RegExp(\n        // Only look on boundaries\n        '\\\\b(?:' +\n        // Capture group 1: URLs\n        '(' +\n            // Add links to URLS\n            // Starts with:\n            '(?:' +\n                // http(s):// or ftp://\n                '(?:ht|f)tps?:\\\\/\\\\/' +\n                // or\n                '|' +\n                // www.\n                'www\\\\d{0,3}[.]' +\n                // or\n                '|' +\n                // foo90.com/\n                '[a-z0-9][a-z0-9.\\\\-]*[.][a-z]{2,}\\\\/' +\n            ')' +\n            // Then we get one or more:\n            '(?:' +\n                // Run of non-spaces, non ()<>\n                '[^\\\\s()<>]+' +\n                // or\n                '|' +\n                // balanced parentheses (one level deep only)\n                '\\\\([^\\\\s()<>]+\\\\)' +\n            ')+' +\n            // And we finish with\n            '(?:' +\n                // Not a space or punctuation character\n                '[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]' +\n                // or\n                '|' +\n                // Balanced parentheses.\n                '\\\\([^\\\\s()<>]+\\\\)' +\n            ')' +\n        // Capture group 2: Emails\n        ')|(' +\n            // Add links to emails\n            '[\\\\w\\\\-.%+]+@(?:[\\\\w\\\\-]+\\\\.)+[a-z]{2,}\\\\b' +\n            // Allow query parameters in the mailto: style\n            '(?:' +\n                '[?][^&?\\\\s]+=[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+' +\n                '(?:&[^&?\\\\s]+=[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+)*' +\n            ')?' +\n        '))',\n        'i'\n    );\n    */\n    linkRegExp =\n        /\\b(?:((?:(?:ht|f)tps?:\\/\\/|www\\d{0,3}[.]|[a-z0-9][a-z0-9.\\-]*[.][a-z]{2,}\\/)(?:[^\\s()<>]+|\\([^\\s()<>]+\\))+(?:[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]|\\([^\\s()<>]+\\)))|([\\w\\-.%+]+@(?:[\\w\\-]+\\.)+[a-z]{2,}\\b(?:[?][^&?\\s]+=[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+(?:&[^&?\\s]+=[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+)*)?))/i;\n\n    addDetectedLinks(\n        searchInNode: DocumentFragment | Node,\n        root?: DocumentFragment | HTMLElement,\n    ): Squire {\n        const walker = new TreeIterator<Text>(\n            searchInNode,\n            SHOW_TEXT,\n            (node) => !getNearest(node, root || this._root, 'A'),\n        );\n        const linkRegExp = this.linkRegExp;\n        const defaultAttributes = this._config.tagAttributes.a;\n        let node: Text | null;\n        while ((node = walker.nextNode())) {\n            const parent = node.parentNode!;\n            let data = node.data;\n            let match: RegExpExecArray | null;\n            while ((match = linkRegExp.exec(data))) {\n                const index = match.index;\n                const endIndex = index + match[0].length;\n                if (index) {\n                    parent.insertBefore(\n                        document.createTextNode(data.slice(0, index)),\n                        node,\n                    );\n                }\n                const child = createElement(\n                    'A',\n                    Object.assign(\n                        {\n                            href: match[1]\n                                ? /^(?:ht|f)tps?:/i.test(match[1])\n                                    ? match[1]\n                                    : 'http://' + match[1]\n                                : 'mailto:' + match[0],\n                        },\n                        defaultAttributes,\n                    ),\n                );\n                child.textContent = data.slice(index, endIndex);\n                parent.insertBefore(child, node);\n                node.data = data = data.slice(endIndex);\n            }\n        }\n        return this;\n    }\n\n    // ---\n\n    setFontFace(name: string | null): Squire {\n        const className = this._config.classNames.fontFamily;\n        return this.changeFormat(\n            name\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'font-family: ' + name + ', sans-serif;',\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setFontSize(size: string | null): Squire {\n        const className = this._config.classNames.fontSize;\n        return this.changeFormat(\n            size\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style:\n                              'font-size: ' +\n                              (typeof size === 'number' ? size + 'px' : size),\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setTextColor(color: string | null): Squire {\n        const className = this._config.classNames.color;\n        return this.changeFormat(\n            color\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'color:' + color,\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setHighlightColor(color: string | null): Squire {\n        const className = this._config.classNames.highlight;\n        return this.changeFormat(\n            color\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'background-color:' + color,\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    // --- Block formatting\n\n    _ensureBottomLine(): void {\n        const root = this._root;\n        const last = root.lastElementChild;\n        if (\n            !last ||\n            last.nodeName !== this._config.blockTag ||\n            !isBlock(last)\n        ) {\n            root.appendChild(this.createDefaultBlock());\n        }\n    }\n\n    createDefaultBlock(children?: Node[]): HTMLElement {\n        const config = this._config;\n        return fixCursor(\n            createElement(config.blockTag, config.blockAttributes, children),\n        ) as HTMLElement;\n    }\n\n    tagAfterSplit: Record<string, string> = {\n        DT: 'DD',\n        DD: 'DT',\n        LI: 'LI',\n        PRE: 'PRE',\n    };\n\n    splitBlock(lineBreakOnly: boolean, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        const root = this._root;\n        let block: Node | Element | null;\n        let parent: Node | null;\n        let node: Node;\n        let nodeAfterSplit: Node;\n\n        // Save undo checkpoint and remove any zws so we don't think there's\n        // content in an empty block.\n        this._recordUndoState(range);\n        this._removeZWS();\n        this._getRangeAndRemoveBookmark(range);\n\n        // Selected text is overwritten, therefore delete the contents\n        // to collapse selection.\n        if (!range.collapsed) {\n            deleteContentsOfRange(range, root);\n        }\n\n        // Linkify text\n        if (this._config.addLinks) {\n            moveRangeBoundariesDownTree(range);\n            const textNode = range.startContainer as Text;\n            const offset = range.startOffset;\n            setTimeout(() => {\n                linkifyText(this, textNode, offset);\n            }, 0);\n        }\n\n        block = getStartBlockOfRange(range, root);\n\n        // Inside a PRE, insert literal newline, unless on blank line.\n        if (block && (parent = getNearest(block, root, 'PRE'))) {\n            moveRangeBoundariesDownTree(range);\n            node = range.startContainer;\n            const offset = range.startOffset;\n            if (!(node instanceof Text)) {\n                node = document.createTextNode('');\n                parent.insertBefore(node, parent.firstChild);\n            }\n            // If blank line: split and insert default block\n            if (\n                !lineBreakOnly &&\n                node instanceof Text &&\n                (node.data.charAt(offset - 1) === '\\n' ||\n                    rangeDoesStartAtBlockBoundary(range, root)) &&\n                (node.data.charAt(offset) === '\\n' ||\n                    rangeDoesEndAtBlockBoundary(range, root))\n            ) {\n                node.deleteData(offset && offset - 1, offset ? 2 : 1);\n                nodeAfterSplit = split(\n                    node,\n                    offset && offset - 1,\n                    root,\n                    root,\n                ) as Node;\n                node = nodeAfterSplit.previousSibling!;\n                if (!node.textContent) {\n                    detach(node);\n                }\n                node = this.createDefaultBlock();\n                nodeAfterSplit.parentNode!.insertBefore(node, nodeAfterSplit);\n                if (!nodeAfterSplit.textContent) {\n                    detach(nodeAfterSplit);\n                }\n                range.setStart(node, 0);\n            } else {\n                (node as Text).insertData(offset, '\\n');\n                fixCursor(parent);\n                // Firefox bug: if you set the selection in the text node after\n                // the new line, it draws the cursor before the line break still\n                // but if you set the selection to the equivalent position\n                // in the parent, it works.\n                if ((node as Text).length === offset + 1) {\n                    range.setStartAfter(node);\n                } else {\n                    range.setStart(node, offset + 1);\n                }\n            }\n            range.collapse(true);\n            this.setSelection(range);\n            this._updatePath(range, true);\n            this._docWasChanged();\n            return this;\n        }\n\n        // If this is a malformed bit of document or in a table;\n        // just play it safe and insert a <br>.\n        if (!block || lineBreakOnly || /^T[HD]$/.test(block.nodeName)) {\n            // If inside an <a>, move focus out\n            moveRangeBoundaryOutOf(range, 'A', root);\n            insertNodeInRange(range, createElement('BR'));\n            range.collapse(false);\n            this.setSelection(range);\n            this._updatePath(range, true);\n            return this;\n        }\n\n        // If in a list, we'll split the LI instead.\n        if ((parent = getNearest(block, root, 'LI'))) {\n            block = parent;\n        }\n\n        if (isEmptyBlock(block as Element)) {\n            if (\n                getNearest(block, root, 'UL') ||\n                getNearest(block, root, 'OL')\n            ) {\n                // Break list\n                this.decreaseListLevel(range);\n                return this;\n                // Break blockquote\n            } else if (getNearest(block, root, 'BLOCKQUOTE')) {\n                this.removeQuote(range);\n                return this;\n            }\n        }\n\n        // Otherwise, split at cursor point.\n        node = range.startContainer;\n        const offset = range.startOffset;\n        let splitTag = this.tagAfterSplit[block.nodeName];\n        nodeAfterSplit = split(\n            node,\n            offset,\n            block.parentNode!,\n            this._root,\n        ) as Node;\n\n        const config = this._config;\n        let splitProperties: Record<string, string> | null = null;\n        if (!splitTag) {\n            splitTag = config.blockTag;\n            splitProperties = config.blockAttributes;\n        }\n\n        // Make sure the new node is the correct type.\n        if (!hasTagAttributes(nodeAfterSplit, splitTag, splitProperties)) {\n            block = createElement(splitTag, splitProperties);\n            if ((nodeAfterSplit as HTMLElement).dir) {\n                (block as HTMLElement).dir = (\n                    nodeAfterSplit as HTMLElement\n                ).dir;\n            }\n            replaceWith(nodeAfterSplit, block);\n            block.appendChild(empty(nodeAfterSplit));\n            nodeAfterSplit = block;\n        }\n\n        // Clean up any empty inlines if we hit enter at the beginning of the\n        // block\n        removeZWS(block);\n        removeEmptyInlines(block);\n        fixCursor(block);\n\n        // Focus cursor\n        // If there's a <b>/<i> etc. at the beginning of the split\n        // make sure we focus inside it.\n        while (nodeAfterSplit instanceof Element) {\n            let child = nodeAfterSplit.firstChild;\n            let next;\n\n            // Don't continue links over a block break; unlikely to be the\n            // desired outcome.\n            if (\n                nodeAfterSplit.nodeName === 'A' &&\n                (!nodeAfterSplit.textContent ||\n                    nodeAfterSplit.textContent === ZWS)\n            ) {\n                child = document.createTextNode('') as Text;\n                replaceWith(nodeAfterSplit, child);\n                nodeAfterSplit = child;\n                break;\n            }\n\n            while (child && child instanceof Text && !child.data) {\n                next = child.nextSibling;\n                if (!next || next.nodeName === 'BR') {\n                    break;\n                }\n                detach(child);\n                child = next;\n            }\n\n            // 'BR's essentially don't count; they're a browser hack.\n            // If you try to select the contents of a 'BR', FF will not let\n            // you type anything!\n            if (!child || child.nodeName === 'BR' || child instanceof Text) {\n                break;\n            }\n            nodeAfterSplit = child;\n        }\n        range = createRange(nodeAfterSplit, 0);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    forEachBlock(\n        fn: (el: HTMLElement) => any,\n        mutates: boolean,\n        range?: Range,\n    ): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Save undo checkpoint\n        if (mutates) {\n            this.saveUndoState(range);\n        }\n\n        const root = this._root;\n        let start = getStartBlockOfRange(range, root);\n        const end = getEndBlockOfRange(range, root);\n        if (start && end) {\n            do {\n                if (fn(start) || start === end) {\n                    break;\n                }\n            } while ((start = getNextBlock(start, root)));\n        }\n\n        if (mutates) {\n            this.setSelection(range);\n            // Path may have changed\n            this._updatePath(range, true);\n        }\n        return this;\n    }\n\n    modifyBlocks(modify: (x: DocumentFragment) => Node, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // 1. Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        // 2. Expand range to block boundaries\n        const root = this._root;\n        expandRangeToBlockBoundaries(range, root);\n\n        // 3. Remove range.\n        moveRangeBoundariesUpTree(range, root, root, root);\n        const frag = extractContentsOfRange(range, root, root);\n\n        // 4. Modify tree of fragment and reinsert.\n        if (!range.collapsed) {\n            // After extracting contents, the range edges will still be at the\n            // level we began the spilt. We want to insert directly in the\n            // root, so move the range up there.\n            let node = range.endContainer;\n            if (node === root) {\n                range.collapse(false);\n            } else {\n                while (node.parentNode !== root) {\n                    node = node.parentNode!;\n                }\n                range.setStartBefore(node);\n                range.collapse(true);\n            }\n        }\n        insertNodeInRange(range, modify.call(this, frag));\n\n        // 5. Merge containers at edges\n        if (range.endOffset < range.endContainer.childNodes.length) {\n            mergeContainers(\n                range.endContainer.childNodes[range.endOffset],\n                root,\n            );\n        }\n        mergeContainers(\n            range.startContainer.childNodes[range.startOffset],\n            root,\n        );\n\n        // 6. Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    // ---\n\n    setTextAlignment(alignment: string): Squire {\n        this.forEachBlock((block: HTMLElement) => {\n            const className = block.className\n                .split(/\\s+/)\n                .filter((klass) => {\n                    return !!klass && !/^align/.test(klass);\n                })\n                .join(' ');\n            if (alignment) {\n                block.className = className + ' align-' + alignment;\n                block.style.textAlign = alignment;\n            } else {\n                block.className = className;\n                block.style.textAlign = '';\n            }\n        }, true);\n        return this.focus();\n    }\n\n    setTextDirection(direction: string | null): Squire {\n        this.forEachBlock((block: HTMLElement) => {\n            if (direction) {\n                block.dir = direction;\n            } else {\n                block.removeAttribute('dir');\n            }\n        }, true);\n        return this.focus();\n    }\n\n    // ---\n\n    _getListSelection(\n        range: Range,\n        root: Element,\n    ): [Node, Node | null, Node | null] | null {\n        let list: Node | null = range.commonAncestorContainer;\n        let startLi: Node | null = range.startContainer;\n        let endLi: Node | null = range.endContainer;\n        while (list && list !== root && !/^[OU]L$/.test(list.nodeName)) {\n            list = list.parentNode;\n        }\n        if (!list || list === root) {\n            return null;\n        }\n        if (startLi === list) {\n            startLi = startLi.childNodes[range.startOffset];\n        }\n        if (endLi === list) {\n            endLi = endLi.childNodes[range.endOffset];\n        }\n        while (startLi && startLi.parentNode !== list) {\n            startLi = startLi.parentNode;\n        }\n        while (endLi && endLi.parentNode !== list) {\n            endLi = endLi.parentNode;\n        }\n        return [list, startLi, endLi];\n    }\n\n    increaseListLevel(range?: Range) {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Get start+end li in single common ancestor\n        const root = this._root;\n        const listSelection = this._getListSelection(range, root);\n        if (!listSelection) {\n            return this.focus();\n        }\n        // eslint-disable-next-line prefer-const\n        let [list, startLi, endLi] = listSelection;\n        if (!startLi || startLi === list.firstChild) {\n            return this.focus();\n        }\n\n        // Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        // Increase list depth\n        const type = list.nodeName;\n        let newParent = startLi.previousSibling!;\n        let listAttrs: Record<string, string> | null;\n        let next: Node | null;\n        if (newParent.nodeName !== type) {\n            listAttrs = this._config.tagAttributes[type.toLowerCase()];\n            newParent = createElement(type, listAttrs);\n            list.insertBefore(newParent, startLi);\n        }\n        do {\n            next = startLi === endLi ? null : startLi.nextSibling;\n            newParent.appendChild(startLi);\n        } while ((startLi = next));\n        next = newParent.nextSibling;\n        if (next) {\n            mergeContainers(next, root);\n        }\n\n        // Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    decreaseListLevel(range?: Range) {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        const root = this._root;\n        const listSelection = this._getListSelection(range, root);\n        if (!listSelection) {\n            return this.focus();\n        }\n\n        // eslint-disable-next-line prefer-const\n        let [list, startLi, endLi] = listSelection;\n        if (!startLi) {\n            startLi = list.firstChild;\n        }\n        if (!endLi) {\n            endLi = list.lastChild!;\n        }\n\n        // Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        let next: Node | null;\n        let insertBefore: Node | null = null;\n        if (startLi) {\n            // Find the new parent list node\n            let newParent = list.parentNode!;\n\n            // Split list if necessary\n            insertBefore = !endLi.nextSibling\n                ? list.nextSibling\n                : (split(list, endLi.nextSibling, newParent, root) as Node);\n\n            if (newParent !== root && newParent.nodeName === 'LI') {\n                newParent = newParent.parentNode!;\n                while (insertBefore) {\n                    next = insertBefore.nextSibling;\n                    endLi.appendChild(insertBefore);\n                    insertBefore = next;\n                }\n                insertBefore = list.parentNode!.nextSibling;\n            }\n\n            const makeNotList = !/^[OU]L$/.test(newParent.nodeName);\n            do {\n                next = startLi === endLi ? null : startLi.nextSibling;\n                list.removeChild(startLi);\n                if (makeNotList && startLi.nodeName === 'LI') {\n                    startLi = this.createDefaultBlock([empty(startLi)]);\n                }\n                newParent.insertBefore(startLi!, insertBefore);\n            } while ((startLi = next));\n        }\n\n        if (!list.firstChild) {\n            detach(list);\n        }\n\n        if (insertBefore) {\n            mergeContainers(insertBefore, root);\n        }\n\n        // Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    _makeList(frag: DocumentFragment, type: string): DocumentFragment {\n        const walker = getBlockWalker(frag, this._root);\n        const tagAttributes = this._config.tagAttributes;\n        const listAttrs = tagAttributes[type.toLowerCase()];\n        const listItemAttrs = tagAttributes.li;\n        let node: Node | null;\n        while ((node = walker.nextNode())) {\n            if (node.parentNode! instanceof HTMLLIElement) {\n                node = node.parentNode!;\n                walker.currentNode = node.lastChild!;\n            }\n            if (!(node instanceof HTMLLIElement)) {\n                const newLi = createElement('LI', listItemAttrs);\n                if ((node as HTMLElement).dir) {\n                    newLi.dir = (node as HTMLElement).dir;\n                }\n\n                // Have we replaced the previous block with a new <ul>/<ol>?\n                const prev: ChildNode | null = node.previousSibling;\n                if (prev && prev.nodeName === type) {\n                    prev.appendChild(newLi);\n                    detach(node);\n                    // Otherwise, replace this block with the <ul>/<ol>\n                } else {\n                    replaceWith(node, createElement(type, listAttrs, [newLi]));\n                }\n                newLi.appendChild(empty(node));\n                walker.currentNode = newLi;\n            } else {\n                node = node.parentNode;\n                const tag = node!.nodeName;\n                if (tag !== type && /^[OU]L$/.test(tag)) {\n                    replaceWith(\n                        node!,\n                        createElement(type, listAttrs, [empty(node!)]),\n                    );\n                }\n            }\n        }\n        return frag;\n    }\n\n    makeUnorderedList(): Squire {\n        this.modifyBlocks((frag) => this._makeList(frag, 'UL'));\n        return this.focus();\n    }\n\n    makeOrderedList(): Squire {\n        this.modifyBlocks((frag) => this._makeList(frag, 'OL'));\n        return this.focus();\n    }\n\n    removeList(): Squire {\n        this.modifyBlocks((frag) => {\n            const lists = frag.querySelectorAll('UL, OL');\n            const items = frag.querySelectorAll('LI');\n            const root = this._root;\n            for (let i = 0, l = lists.length; i < l; i += 1) {\n                const list = lists[i];\n                const listFrag = empty(list);\n                fixContainer(listFrag, root);\n                replaceWith(list, listFrag);\n            }\n\n            for (let i = 0, l = items.length; i < l; i += 1) {\n                const item = items[i];\n                if (isBlock(item)) {\n                    replaceWith(item, this.createDefaultBlock([empty(item)]));\n                } else {\n                    fixContainer(item, root);\n                    replaceWith(item, empty(item));\n                }\n            }\n            return frag;\n        });\n        return this.focus();\n    }\n\n    // ---\n\n    increaseQuoteLevel(range?: Range): Squire {\n        this.modifyBlocks(\n            (frag) =>\n                createElement(\n                    'BLOCKQUOTE',\n                    this._config.tagAttributes.blockquote,\n                    [frag],\n                ),\n            range,\n        );\n        return this.focus();\n    }\n\n    decreaseQuoteLevel(range?: Range): Squire {\n        this.modifyBlocks((frag) => {\n            Array.from(frag.querySelectorAll('blockquote'))\n                .filter((el: Node) => {\n                    return !getNearest(el.parentNode, frag, 'BLOCKQUOTE');\n                })\n                .forEach((el: Node) => {\n                    replaceWith(el, empty(el));\n                });\n            return frag;\n        }, range);\n        return this.focus();\n    }\n\n    removeQuote(range?: Range): Squire {\n        this.modifyBlocks(\n            (/* frag */) =>\n                this.createDefaultBlock([\n                    createElement('INPUT', {\n                        id: this.startSelectionId,\n                        type: 'hidden',\n                    }),\n                    createElement('INPUT', {\n                        id: this.endSelectionId,\n                        type: 'hidden',\n                    }),\n                ]),\n            range,\n        );\n        return this.focus();\n    }\n\n    // ---\n\n    code(): Squire {\n        const range = this.getSelection();\n        if (range.collapsed || isContainer(range.commonAncestorContainer)) {\n            this.modifyBlocks((frag) => {\n                const root = this._root;\n                const output = document.createDocumentFragment();\n                const blockWalker = getBlockWalker(frag, root);\n                let node: Element | Text | null;\n                // 1. Extract inline content; drop all blocks and contains.\n                while ((node = blockWalker.nextNode())) {\n                    // 2. Replace <br> with \\n in content\n                    let nodes = node.querySelectorAll('BR');\n                    const brBreaksLine: boolean[] = [];\n                    let l = nodes.length;\n                    // Must calculate whether the <br> breaks a line first,\n                    // because if we have two <br>s next to each other, after\n                    // the first one is converted to a block split, the second\n                    // will be at the end of a block and therefore seem to not\n                    // be a line break. But in its original context it was, so\n                    // we should also convert it to a block split.\n                    for (let i = 0; i < l; i += 1) {\n                        brBreaksLine[i] = isLineBreak(nodes[i], false);\n                    }\n                    while (l--) {\n                        const br = nodes[l];\n                        if (!brBreaksLine[l]) {\n                            detach(br);\n                        } else {\n                            replaceWith(br, document.createTextNode('\\n'));\n                        }\n                    }\n                    // 3. Remove <code>; its format clashes with <pre>\n                    nodes = node.querySelectorAll('CODE');\n                    l = nodes.length;\n                    while (l--) {\n                        replaceWith(nodes[l], empty(nodes[l]));\n                    }\n                    if (output.childNodes.length) {\n                        output.appendChild(document.createTextNode('\\n'));\n                    }\n                    output.appendChild(empty(node));\n                }\n                // 4. Replace nbsp with regular sp\n                const textWalker = new TreeIterator<Text>(output, SHOW_TEXT);\n                while ((node = textWalker.nextNode())) {\n                    // eslint-disable-next-line no-irregular-whitespace\n                    node.data = node.data.replace(/\u00A0/g, ' '); // nbsp -> sp\n                }\n                output.normalize();\n                return fixCursor(\n                    createElement('PRE', this._config.tagAttributes.pre, [\n                        output,\n                    ]),\n                );\n            }, range);\n            this.focus();\n        } else {\n            this.changeFormat(\n                {\n                    tag: 'CODE',\n                    attributes: this._config.tagAttributes.code,\n                },\n                null,\n                range,\n            );\n        }\n        return this;\n    }\n\n    removeCode(): Squire {\n        const range = this.getSelection();\n        const ancestor = range.commonAncestorContainer;\n        const inPre = getNearest(ancestor, this._root, 'PRE');\n        if (inPre) {\n            this.modifyBlocks((frag) => {\n                const root = this._root;\n                const pres = frag.querySelectorAll('PRE');\n                let l = pres.length;\n                while (l--) {\n                    const pre = pres[l];\n                    const walker = new TreeIterator<Text>(pre, SHOW_TEXT);\n                    let node: Text | null;\n                    while ((node = walker.nextNode())) {\n                        let value = node.data;\n                        value = value.replace(/ (?= )/g, '\u00A0'); // sp -> nbsp\n                        const contents = document.createDocumentFragment();\n                        let index: number;\n                        while ((index = value.indexOf('\\n')) > -1) {\n                            contents.appendChild(\n                                document.createTextNode(value.slice(0, index)),\n                            );\n                            contents.appendChild(createElement('BR'));\n                            value = value.slice(index + 1);\n                        }\n                        node.parentNode!.insertBefore(contents, node);\n                        node.data = value;\n                    }\n                    fixContainer(pre, root);\n                    replaceWith(pre, empty(pre));\n                }\n                return frag;\n            }, range);\n            this.focus();\n        } else {\n            this.changeFormat(null, { tag: 'CODE' }, range);\n        }\n        return this;\n    }\n\n    toggleCode(): Squire {\n        if (this.hasFormat('PRE') || this.hasFormat('CODE')) {\n            this.removeCode();\n        } else {\n            this.code();\n        }\n        return this;\n    }\n\n    // ---\n\n    _removeFormatting(\n        root: DocumentFragment | Element,\n        clean: DocumentFragment | Element,\n    ): DocumentFragment | Element {\n        for (\n            let node = root.firstChild, next: ChildNode | null;\n            node;\n            node = next\n        ) {\n            next = node.nextSibling;\n            if (isInline(node)) {\n                if (\n                    node instanceof Text ||\n                    node.nodeName === 'BR' ||\n                    node.nodeName === 'IMG'\n                ) {\n                    clean.appendChild(node);\n                    continue;\n                }\n            } else if (isBlock(node)) {\n                clean.appendChild(\n                    this.createDefaultBlock([\n                        this._removeFormatting(\n                            node as Element,\n                            document.createDocumentFragment(),\n                        ),\n                    ]),\n                );\n                continue;\n            }\n            this._removeFormatting(node as Element, clean);\n        }\n        return clean;\n    }\n\n    removeAllFormatting(range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        if (range.collapsed) {\n            return this.focus();\n        }\n\n        const root = this._root;\n        let stopNode = range.commonAncestorContainer;\n        while (stopNode && !isBlock(stopNode)) {\n            stopNode = stopNode.parentNode!;\n        }\n        if (!stopNode) {\n            expandRangeToBlockBoundaries(range, root);\n            stopNode = root;\n        }\n        if (stopNode instanceof Text) {\n            return this.focus();\n        }\n\n        // Record undo point\n        this.saveUndoState(range);\n\n        // Avoid splitting where we're already at edges.\n        moveRangeBoundariesUpTree(range, stopNode, stopNode, root);\n\n        // Split the selection up to the block, or if whole selection in same\n        // block, expand range boundaries to ends of block and split up to root.\n        const startContainer = range.startContainer;\n        let startOffset = range.startOffset;\n        const endContainer = range.endContainer;\n        let endOffset = range.endOffset;\n\n        // Split end point first to avoid problems when end and start\n        // in same container.\n        const formattedNodes = document.createDocumentFragment();\n        const cleanNodes = document.createDocumentFragment();\n        const nodeAfterSplit = split(endContainer, endOffset, stopNode, root);\n        let nodeInSplit = split(startContainer, startOffset, stopNode, root);\n        let nextNode: ChildNode | null;\n\n        // Then replace contents in split with a cleaned version of the same:\n        // blocks become default blocks, text and leaf nodes survive, everything\n        // else is obliterated.\n        while (nodeInSplit !== nodeAfterSplit) {\n            nextNode = nodeInSplit!.nextSibling;\n            formattedNodes.appendChild(nodeInSplit!);\n            nodeInSplit = nextNode;\n        }\n        this._removeFormatting(formattedNodes, cleanNodes);\n        cleanNodes.normalize();\n        nodeInSplit = cleanNodes.firstChild;\n        nextNode = cleanNodes.lastChild;\n\n        // Restore selection\n        if (nodeInSplit) {\n            stopNode.insertBefore(cleanNodes, nodeAfterSplit);\n            const childNodes = Array.from(stopNode.childNodes) as Node[];\n            startOffset = childNodes.indexOf(nodeInSplit);\n            endOffset = nextNode ? childNodes.indexOf(nextNode) + 1 : 0;\n        } else if (nodeAfterSplit) {\n            const childNodes = Array.from(stopNode.childNodes) as Node[];\n            startOffset = childNodes.indexOf(nodeAfterSplit);\n            endOffset = startOffset;\n        }\n\n        // Merge text nodes at edges, if possible\n        range.setStart(stopNode, startOffset);\n        range.setEnd(stopNode, endOffset);\n        mergeInlines(stopNode, range);\n\n        // And move back down the tree\n        moveRangeBoundariesDownTree(range);\n\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n}\n\n// ---\n\nexport { Squire };\nexport type { SquireConfig };\n", "import { Squire } from './Editor';\n\nexport default Squire;\n"],
-  "mappings": "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",
+  "sourcesContent": ["type NODE_TYPE = 1 | 4 | 5;\nconst SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;\nconst SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;\nconst SHOW_ELEMENT_OR_TEXT = 5; // SHOW_ELEMENT|SHOW_TEXT;\n\nconst always = (): true => true;\n\nclass TreeIterator<T extends Node> {\n    root: Node;\n    currentNode: Node;\n    nodeType: NODE_TYPE;\n    filter: (n: T) => boolean;\n\n    constructor(root: Node, nodeType: NODE_TYPE, filter?: (n: T) => boolean) {\n        this.root = root;\n        this.currentNode = root;\n        this.nodeType = nodeType;\n        this.filter = filter || always;\n    }\n\n    isAcceptableNode(node: Node): boolean {\n        const nodeType = node.nodeType;\n        const nodeFilterType =\n            nodeType === Node.ELEMENT_NODE\n                ? SHOW_ELEMENT\n                : nodeType === Node.TEXT_NODE\n                  ? SHOW_TEXT\n                  : 0;\n        return !!(nodeFilterType & this.nodeType) && this.filter(node as T);\n    }\n\n    nextNode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            node = current.firstChild;\n            while (!node && current) {\n                if (current === root) {\n                    break;\n                }\n                node = current.nextSibling;\n                if (!node) {\n                    current = current.parentNode;\n                }\n            }\n            if (!node) {\n                return null;\n            }\n\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n\n    previousNode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            if (current === root) {\n                return null;\n            }\n            node = current.previousSibling;\n            if (node) {\n                while ((current = node.lastChild)) {\n                    node = current;\n                }\n            } else {\n                node = current.parentNode;\n            }\n            if (!node) {\n                return null;\n            }\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n\n    // Previous node in post-order.\n    previousPONode(): T | null {\n        const root = this.root;\n        let current: Node | null = this.currentNode;\n        let node: Node | null;\n        while (true) {\n            node = current.lastChild;\n            while (!node && current) {\n                if (current === root) {\n                    break;\n                }\n                node = current.previousSibling;\n                if (!node) {\n                    current = current.parentNode;\n                }\n            }\n            if (!node) {\n                return null;\n            }\n            if (this.isAcceptableNode(node)) {\n                this.currentNode = node;\n                return node as T;\n            }\n            current = node;\n        }\n    }\n}\n\n// ---\n\nexport { TreeIterator, SHOW_ELEMENT, SHOW_TEXT, SHOW_ELEMENT_OR_TEXT };\n", "const DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING\nconst ELEMENT_NODE = 1; // Node.ELEMENT_NODE;\nconst TEXT_NODE = 3; // Node.TEXT_NODE;\nconst DOCUMENT_NODE = 9; // Node.DOCUMENT_NODE;\nconst DOCUMENT_FRAGMENT_NODE = 11; // Node.DOCUMENT_FRAGMENT_NODE;\n\nconst ZWS = '\\u200B';\n\nconst ua = navigator.userAgent;\n\nconst isMac = /Mac OS X/.test(ua);\nconst isWin = /Windows NT/.test(ua);\nconst isIOS =\n    /iP(?:ad|hone|od)/.test(ua) || (isMac && !!navigator.maxTouchPoints);\nconst isAndroid = /Android/.test(ua);\n\nconst isGecko = /Gecko\\//.test(ua);\nconst isLegacyEdge = /Edge\\//.test(ua);\nconst isWebKit = !isLegacyEdge && /WebKit\\//.test(ua);\n\nconst ctrlKey = isMac || isIOS ? 'Meta-' : 'Ctrl-';\n\nconst cantFocusEmptyTextNodes = isWebKit;\n\nconst supportsInputEvents =\n    'onbeforeinput' in document && 'inputType' in new InputEvent('input');\n\n// Use [^ \\t\\r\\n] instead of \\S so that nbsp does not count as white-space\nconst notWS = /[^ \\t\\r\\n]/;\n\n// ---\n\nexport {\n    DOCUMENT_POSITION_PRECEDING,\n    ELEMENT_NODE,\n    TEXT_NODE,\n    DOCUMENT_NODE,\n    DOCUMENT_FRAGMENT_NODE,\n    notWS,\n    ZWS,\n    ua,\n    isMac,\n    isWin,\n    isIOS,\n    isAndroid,\n    isGecko,\n    isLegacyEdge,\n    isWebKit,\n    ctrlKey,\n    cantFocusEmptyTextNodes,\n    supportsInputEvents,\n};\n", "import { ELEMENT_NODE, TEXT_NODE, DOCUMENT_FRAGMENT_NODE } from '../Constants';\n\n// ---\n\nconst inlineNodeNames =\n    /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/;\n\nconst leafNodeNames = new Set(['BR', 'HR', 'IFRAME', 'IMG', 'INPUT']);\n\nconst UNKNOWN = 0;\nconst INLINE = 1;\nconst BLOCK = 2;\nconst CONTAINER = 3;\n\n// ---\n\nlet cache: WeakMap<Node, number> = new WeakMap();\n\nconst resetNodeCategoryCache = (): void => {\n    cache = new WeakMap();\n};\n\n// ---\n\nconst isLeaf = (node: Node): boolean => {\n    return leafNodeNames.has(node.nodeName);\n};\n\nconst getNodeCategory = (node: Node): number => {\n    switch (node.nodeType) {\n        case TEXT_NODE:\n            return INLINE;\n        case ELEMENT_NODE:\n        case DOCUMENT_FRAGMENT_NODE:\n            if (cache.has(node)) {\n                return cache.get(node) as number;\n            }\n            break;\n        default:\n            return UNKNOWN;\n    }\n\n    let nodeCategory: number;\n    if (!Array.from(node.childNodes).every(isInline)) {\n        // Malformed HTML can have block tags inside inline tags. Need to treat\n        // these as containers rather than inline. See #239.\n        nodeCategory = CONTAINER;\n    } else if (inlineNodeNames.test(node.nodeName)) {\n        nodeCategory = INLINE;\n    } else {\n        nodeCategory = BLOCK;\n    }\n    cache.set(node, nodeCategory);\n    return nodeCategory;\n};\n\nconst isInline = (node: Node): boolean => {\n    return getNodeCategory(node) === INLINE;\n};\n\nconst isBlock = (node: Node): boolean => {\n    return getNodeCategory(node) === BLOCK;\n};\n\nconst isContainer = (node: Node): boolean => {\n    return getNodeCategory(node) === CONTAINER;\n};\n\n// ---\n\nexport {\n    getNodeCategory,\n    isBlock,\n    isContainer,\n    isInline,\n    isLeaf,\n    leafNodeNames,\n    resetNodeCategoryCache,\n};\n", "import { isLeaf } from './Category';\n\n// ---\n\nconst createElement = (\n    tag: string,\n    props?: Record<string, string> | null,\n    children?: Node[],\n): HTMLElement => {\n    const el = document.createElement(tag);\n    if (props instanceof Array) {\n        children = props;\n        props = null;\n    }\n    if (props) {\n        for (const attr in props) {\n            const value = props[attr];\n            if (value !== undefined) {\n                el.setAttribute(attr, value);\n            }\n        }\n    }\n    if (children) {\n        children.forEach((node) => el.appendChild(node));\n    }\n    return el;\n};\n\n// --- Tests\n\nconst areAlike = (\n    node: HTMLElement | Node,\n    node2: HTMLElement | Node,\n): boolean => {\n    if (isLeaf(node)) {\n        return false;\n    }\n    if (node.nodeType !== node2.nodeType || node.nodeName !== node2.nodeName) {\n        return false;\n    }\n    if (node instanceof HTMLElement && node2 instanceof HTMLElement) {\n        return (\n            node.nodeName !== 'A' &&\n            node.className === node2.className &&\n            node.style.cssText === node2.style.cssText\n        );\n    }\n    return true;\n};\n\nconst hasTagAttributes = (\n    node: Node | Element,\n    tag: string,\n    attributes?: Record<string, string> | null,\n): boolean => {\n    if (node.nodeName !== tag) {\n        return false;\n    }\n    for (const attr in attributes) {\n        if (\n            !('getAttribute' in node) ||\n            node.getAttribute(attr) !== attributes[attr]\n        ) {\n            return false;\n        }\n    }\n    return true;\n};\n\n// --- Traversal\n\nconst getNearest = (\n    node: Node | null,\n    root: Element | DocumentFragment,\n    tag: string,\n    attributes?: Record<string, string> | null,\n): Node | null => {\n    while (node && node !== root) {\n        if (hasTagAttributes(node, tag, attributes)) {\n            return node;\n        }\n        node = node.parentNode;\n    }\n    return null;\n};\n\nconst getNodeBeforeOffset = (node: Node, offset: number): Node => {\n    let children = node.childNodes;\n    while (offset && node instanceof Element) {\n        node = children[offset - 1];\n        children = node.childNodes;\n        offset = children.length;\n    }\n    return node;\n};\n\nconst getNodeAfterOffset = (node: Node, offset: number): Node | null => {\n    let returnNode: Node | null = node;\n    if (returnNode instanceof Element) {\n        const children = returnNode.childNodes;\n        if (offset < children.length) {\n            returnNode = children[offset];\n        } else {\n            while (returnNode && !returnNode.nextSibling) {\n                returnNode = returnNode.parentNode;\n            }\n            if (returnNode) {\n                returnNode = returnNode.nextSibling;\n            }\n        }\n    }\n    return returnNode;\n};\n\nconst getLength = (node: Node): number => {\n    return node instanceof Element || node instanceof DocumentFragment\n        ? node.childNodes.length\n        : node instanceof CharacterData\n          ? node.length\n          : 0;\n};\n\n// --- Manipulation\n\nconst empty = (node: Node): DocumentFragment => {\n    const frag = document.createDocumentFragment();\n    let child = node.firstChild;\n    while (child) {\n        frag.appendChild(child);\n        child = node.firstChild;\n    }\n    return frag;\n};\n\nconst detach = (node: Node): Node => {\n    const parent = node.parentNode;\n    if (parent) {\n        parent.removeChild(node);\n    }\n    return node;\n};\n\nconst replaceWith = (node: Node, node2: Node): void => {\n    const parent = node.parentNode;\n    if (parent) {\n        parent.replaceChild(node2, node);\n    }\n};\n\n// --- Export\n\nexport {\n    areAlike,\n    createElement,\n    detach,\n    empty,\n    getLength,\n    getNearest,\n    getNodeAfterOffset,\n    getNodeBeforeOffset,\n    hasTagAttributes,\n    replaceWith,\n};\n", "import { ZWS, notWS } from '../Constants';\nimport { isInline } from './Category';\nimport { getLength } from './Node';\nimport { SHOW_ELEMENT_OR_TEXT, SHOW_TEXT, TreeIterator } from './TreeIterator';\n\n// ---\n\nconst notWSTextNode = (node: Node): boolean => {\n    return node instanceof Element\n        ? node.nodeName === 'BR'\n        : // okay if data is 'undefined' here.\n          notWS.test((node as CharacterData).data);\n};\n\nconst isLineBreak = (br: Element, isLBIfEmptyBlock: boolean): boolean => {\n    let block = br.parentNode!;\n    while (isInline(block)) {\n        block = block.parentNode!;\n    }\n    const walker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        notWSTextNode,\n    );\n    walker.currentNode = br;\n    return !!walker.nextNode() || (isLBIfEmptyBlock && !walker.previousNode());\n};\n\n// --- Workaround for browsers that can't focus empty text nodes\n\n// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256\n\n// Walk down the tree starting at the root and remove any ZWS. If the node only\n// contained ZWS space then remove it too. We may want to keep one ZWS node at\n// the bottom of the tree so the block can be selected. Define that node as the\n// keepNode.\nconst removeZWS = (root: Node, keepNode?: Node | null): void => {\n    const walker = new TreeIterator<Text>(root, SHOW_TEXT);\n    let textNode: Text | null;\n    let index: number;\n    while ((textNode = walker.nextNode())) {\n        while (\n            (index = textNode.data.indexOf(ZWS)) > -1 &&\n            // eslint-disable-next-line no-unmodified-loop-condition\n            (!keepNode || textNode.parentNode !== keepNode)\n        ) {\n            if (textNode.length === 1) {\n                let node: Node = textNode;\n                let parent = node.parentNode;\n                while (parent) {\n                    parent.removeChild(node);\n                    walker.currentNode = parent;\n                    if (!isInline(parent) || getLength(parent)) {\n                        break;\n                    }\n                    node = parent;\n                    parent = node.parentNode;\n                }\n                break;\n            } else {\n                textNode.deleteData(index, 1);\n            }\n        }\n    }\n};\n\n// ---\n\nexport { isLineBreak, removeZWS };\n", "import { isLeaf } from '../node/Category';\nimport { getLength, getNearest } from '../node/Node';\nimport { isLineBreak } from '../node/Whitespace';\nimport { TEXT_NODE } from '../Constants';\n\n// ---\n\nconst START_TO_START = 0; // Range.START_TO_START\nconst START_TO_END = 1; // Range.START_TO_END\nconst END_TO_END = 2; // Range.END_TO_END\nconst END_TO_START = 3; // Range.END_TO_START\n\nconst isNodeContainedInRange = (\n    range: Range,\n    node: Node,\n    partial: boolean,\n): boolean => {\n    const nodeRange = document.createRange();\n    nodeRange.selectNode(node);\n    if (partial) {\n        // Node must not finish before range starts or start after range\n        // finishes.\n        const nodeEndBeforeStart =\n            range.compareBoundaryPoints(END_TO_START, nodeRange) > -1;\n        const nodeStartAfterEnd =\n            range.compareBoundaryPoints(START_TO_END, nodeRange) < 1;\n        return !nodeEndBeforeStart && !nodeStartAfterEnd;\n    } else {\n        // Node must start after range starts and finish before range\n        // finishes\n        const nodeStartAfterStart =\n            range.compareBoundaryPoints(START_TO_START, nodeRange) < 1;\n        const nodeEndBeforeEnd =\n            range.compareBoundaryPoints(END_TO_END, nodeRange) > -1;\n        return nodeStartAfterStart && nodeEndBeforeEnd;\n    }\n};\n\n/**\n * Moves the range to an equivalent position with the start/end as deep in\n * the tree as possible.\n */\nconst moveRangeBoundariesDownTree = (range: Range): void => {\n    let { startContainer, startOffset, endContainer, endOffset } = range;\n\n    while (!(startContainer instanceof Text)) {\n        let child: ChildNode | null = startContainer.childNodes[startOffset];\n        if (!child || isLeaf(child)) {\n            if (startOffset) {\n                child = startContainer.childNodes[startOffset - 1];\n                if (child instanceof Text) {\n                    // Need a new variable to satisfy TypeScript's type checker\n                    // for some reason.\n                    let textChild: Text = child;\n                    // If we have an empty text node next to another text node,\n                    // just skip and remove it.\n                    let prev: ChildNode | null;\n                    while (\n                        !textChild.length &&\n                        (prev = textChild.previousSibling) &&\n                        prev instanceof Text\n                    ) {\n                        textChild.remove();\n                        textChild = prev;\n                    }\n                    startContainer = textChild;\n                    startOffset = textChild.data.length;\n                }\n            }\n            break;\n        }\n        startContainer = child;\n        startOffset = 0;\n    }\n    if (endOffset) {\n        while (!(endContainer instanceof Text)) {\n            const child = endContainer.childNodes[endOffset - 1];\n            if (!child || isLeaf(child)) {\n                if (\n                    child &&\n                    child.nodeName === 'BR' &&\n                    !isLineBreak(child as Element, false)\n                ) {\n                    endOffset -= 1;\n                    continue;\n                }\n                break;\n            }\n            endContainer = child;\n            endOffset = getLength(endContainer);\n        }\n    } else {\n        while (!(endContainer instanceof Text)) {\n            const child = endContainer.firstChild!;\n            if (!child || isLeaf(child)) {\n                break;\n            }\n            endContainer = child;\n        }\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\nconst moveRangeBoundariesUpTree = (\n    range: Range,\n    startMax: Node,\n    endMax: Node,\n    root: Node,\n): void => {\n    let startContainer = range.startContainer;\n    let startOffset = range.startOffset;\n    let endContainer = range.endContainer;\n    let endOffset = range.endOffset;\n    let parent: Node;\n\n    if (!startMax) {\n        startMax = range.commonAncestorContainer;\n    }\n    if (!endMax) {\n        endMax = startMax;\n    }\n\n    while (\n        !startOffset &&\n        startContainer !== startMax &&\n        startContainer !== root\n    ) {\n        parent = startContainer.parentNode!;\n        startOffset = Array.from(parent.childNodes).indexOf(\n            startContainer as ChildNode,\n        );\n        startContainer = parent;\n    }\n\n    while (true) {\n        if (endContainer === endMax || endContainer === root) {\n            break;\n        }\n        if (\n            endContainer.nodeType !== TEXT_NODE &&\n            endContainer.childNodes[endOffset] &&\n            endContainer.childNodes[endOffset].nodeName === 'BR' &&\n            !isLineBreak(endContainer.childNodes[endOffset] as Element, false)\n        ) {\n            endOffset += 1;\n        }\n        if (endOffset !== getLength(endContainer)) {\n            break;\n        }\n        parent = endContainer.parentNode!;\n        endOffset =\n            Array.from(parent.childNodes).indexOf(endContainer as ChildNode) +\n            1;\n        endContainer = parent;\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\nconst moveRangeBoundaryOutOf = (\n    range: Range,\n    tag: string,\n    root: Element,\n): Range => {\n    let parent = getNearest(range.endContainer, root, tag);\n    if (parent && (parent = parent.parentNode)) {\n        const clone = range.cloneRange();\n        moveRangeBoundariesUpTree(clone, parent, parent, root);\n        if (clone.endContainer === parent) {\n            range.setStart(clone.endContainer, clone.endOffset);\n            range.setEnd(clone.endContainer, clone.endOffset);\n        }\n    }\n    return range;\n};\n\n// ---\n\nexport {\n    isNodeContainedInRange,\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n    moveRangeBoundaryOutOf,\n};\n", "import { ZWS, cantFocusEmptyTextNodes } from '../Constants';\nimport {\n    createElement,\n    getNearest,\n    areAlike,\n    getLength,\n    detach,\n    empty,\n} from './Node';\nimport { isInline, isContainer } from './Category';\n\n// ---\n\nconst fixCursor = (node: Node): Node => {\n    // In Webkit and Gecko, block level elements are collapsed and\n    // unfocusable if they have no content. To remedy this, a <BR> must be\n    // inserted. In Opera and IE, we just need a textnode in order for the\n    // cursor to appear.\n    let fixer: Element | Text | null = null;\n\n    if (node instanceof Text) {\n        return node;\n    }\n\n    if (isInline(node)) {\n        let child = node.firstChild;\n        if (cantFocusEmptyTextNodes) {\n            while (child && child instanceof Text && !child.data) {\n                node.removeChild(child);\n                child = node.firstChild;\n            }\n        }\n        if (!child) {\n            if (cantFocusEmptyTextNodes) {\n                fixer = document.createTextNode(ZWS);\n            } else {\n                fixer = document.createTextNode('');\n            }\n        }\n    } else if (\n        (node instanceof Element || node instanceof DocumentFragment) &&\n        !node.querySelector('BR')\n    ) {\n        fixer = createElement('BR');\n        let parent: Element | DocumentFragment = node;\n        let child: Element | null;\n        while ((child = parent.lastElementChild) && !isInline(child)) {\n            parent = child;\n        }\n        node = parent;\n    }\n    if (fixer) {\n        try {\n            node.appendChild(fixer);\n        } catch (error) {}\n    }\n\n    return node;\n};\n\n// Recursively examine container nodes and wrap any inline children.\nconst fixContainer = (\n    container: Node,\n    root: Element | DocumentFragment,\n): Node => {\n    let wrapper: HTMLElement | null = null;\n    Array.from(container.childNodes).forEach((child) => {\n        const isBR = child.nodeName === 'BR';\n        if (!isBR && isInline(child)) {\n            if (!wrapper) {\n                wrapper = createElement('DIV');\n            }\n            wrapper.appendChild(child);\n        } else if (isBR || wrapper) {\n            if (!wrapper) {\n                wrapper = createElement('DIV');\n            }\n            fixCursor(wrapper);\n            if (isBR) {\n                container.replaceChild(wrapper, child);\n            } else {\n                container.insertBefore(wrapper, child);\n            }\n            wrapper = null;\n        }\n        if (isContainer(child)) {\n            fixContainer(child, root);\n        }\n    });\n    if (wrapper) {\n        container.appendChild(fixCursor(wrapper));\n    }\n    return container;\n};\n\nconst split = (\n    node: Node,\n    offset: number | Node | null,\n    stopNode: Node,\n    root: Element | DocumentFragment,\n): Node | null => {\n    if (node instanceof Text && node !== stopNode) {\n        if (typeof offset !== 'number') {\n            throw new Error('Offset must be a number to split text node!');\n        }\n        if (!node.parentNode) {\n            throw new Error('Cannot split text node with no parent!');\n        }\n        return split(node.parentNode, node.splitText(offset), stopNode, root);\n    }\n\n    let nodeAfterSplit: Node | null =\n        typeof offset === 'number'\n            ? offset < node.childNodes.length\n                ? node.childNodes[offset]\n                : null\n            : offset;\n    const parent = node.parentNode;\n    if (!parent || node === stopNode || !(node instanceof Element)) {\n        return nodeAfterSplit;\n    }\n\n    // Clone node without children\n    const clone = node.cloneNode(false) as Element;\n\n    // Add right-hand siblings to the clone\n    while (nodeAfterSplit) {\n        const next = nodeAfterSplit.nextSibling;\n        clone.appendChild(nodeAfterSplit);\n        nodeAfterSplit = next;\n    }\n\n    // Maintain li numbering if inside a quote.\n    if (\n        node instanceof HTMLOListElement &&\n        getNearest(node, root, 'BLOCKQUOTE')\n    ) {\n        (clone as HTMLOListElement).start =\n            (+node.start || 1) + node.childNodes.length - 1;\n    }\n\n    // DO NOT NORMALISE. This may undo the fixCursor() call\n    // of a node lower down the tree!\n    // We need something in the element in order for the cursor to appear.\n    fixCursor(node);\n    fixCursor(clone);\n\n    // Inject clone after original node\n    parent.insertBefore(clone, node.nextSibling);\n\n    // Keep on splitting up the tree\n    return split(parent, clone, stopNode, root);\n};\n\nconst _mergeInlines = (\n    node: Node,\n    fakeRange: {\n        startContainer: Node;\n        startOffset: number;\n        endContainer: Node;\n        endOffset: number;\n    },\n): void => {\n    const children = node.childNodes;\n    let l = children.length;\n    const frags: DocumentFragment[] = [];\n    while (l--) {\n        const child = children[l];\n        const prev = l ? children[l - 1] : null;\n        if (prev && isInline(child) && areAlike(child, prev)) {\n            if (fakeRange.startContainer === child) {\n                fakeRange.startContainer = prev;\n                fakeRange.startOffset += getLength(prev);\n            }\n            if (fakeRange.endContainer === child) {\n                fakeRange.endContainer = prev;\n                fakeRange.endOffset += getLength(prev);\n            }\n            if (fakeRange.startContainer === node) {\n                if (fakeRange.startOffset > l) {\n                    fakeRange.startOffset -= 1;\n                } else if (fakeRange.startOffset === l) {\n                    fakeRange.startContainer = prev;\n                    fakeRange.startOffset = getLength(prev);\n                }\n            }\n            if (fakeRange.endContainer === node) {\n                if (fakeRange.endOffset > l) {\n                    fakeRange.endOffset -= 1;\n                } else if (fakeRange.endOffset === l) {\n                    fakeRange.endContainer = prev;\n                    fakeRange.endOffset = getLength(prev);\n                }\n            }\n            detach(child);\n            if (child instanceof Text) {\n                (prev as Text).appendData(child.data);\n            } else {\n                frags.push(empty(child));\n            }\n        } else if (child instanceof Element) {\n            let frag: DocumentFragment | undefined;\n            while ((frag = frags.pop())) {\n                child.appendChild(frag);\n            }\n            _mergeInlines(child, fakeRange);\n        }\n    }\n};\n\nconst mergeInlines = (node: Node, range: Range): void => {\n    const element = node instanceof Text ? node.parentNode : node;\n    if (element instanceof Element) {\n        const fakeRange = {\n            startContainer: range.startContainer,\n            startOffset: range.startOffset,\n            endContainer: range.endContainer,\n            endOffset: range.endOffset,\n        };\n        _mergeInlines(element, fakeRange);\n        range.setStart(fakeRange.startContainer, fakeRange.startOffset);\n        range.setEnd(fakeRange.endContainer, fakeRange.endOffset);\n    }\n};\n\nconst mergeWithBlock = (\n    block: Node,\n    next: Node,\n    range: Range,\n    root: Element,\n): void => {\n    let container = next;\n    let parent: Node | null;\n    let offset: number;\n    while (\n        (parent = container.parentNode) &&\n        parent !== root &&\n        parent instanceof Element &&\n        parent.childNodes.length === 1\n    ) {\n        container = parent;\n    }\n    detach(container);\n\n    offset = block.childNodes.length;\n\n    // Remove extra <BR> fixer if present.\n    const last = block.lastChild;\n    if (last && last.nodeName === 'BR') {\n        block.removeChild(last);\n        offset -= 1;\n    }\n\n    block.appendChild(empty(next));\n\n    range.setStart(block, offset);\n    range.collapse(true);\n    mergeInlines(block, range);\n};\n\nconst mergeContainers = (node: Node, root: Element): void => {\n    const prev = node.previousSibling;\n    const first = node.firstChild;\n    const isListItem = node.nodeName === 'LI';\n\n    // Do not merge LIs, unless it only contains a UL\n    if (isListItem && (!first || !/^[OU]L$/.test(first.nodeName))) {\n        return;\n    }\n\n    if (prev && areAlike(prev, node)) {\n        if (!isContainer(prev)) {\n            if (isListItem) {\n                const block = createElement('DIV');\n                block.appendChild(empty(prev));\n                prev.appendChild(block);\n            } else {\n                return;\n            }\n        }\n        detach(node);\n        const needsFix = !isContainer(node);\n        prev.appendChild(empty(node));\n        if (needsFix) {\n            fixContainer(prev, root);\n        }\n        if (first) {\n            mergeContainers(first, root);\n        }\n    } else if (isListItem) {\n        const block = createElement('DIV');\n        node.insertBefore(block, first);\n        fixCursor(block);\n    }\n};\n\n// ---\n\nexport {\n    fixContainer,\n    fixCursor,\n    mergeContainers,\n    mergeInlines,\n    mergeWithBlock,\n    split,\n};\n", "import { notWS } from './Constants';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from './node/TreeIterator';\nimport { createElement, empty, detach, replaceWith } from './node/Node';\nimport { isInline, isLeaf } from './node/Category';\nimport { fixContainer } from './node/MergeSplit';\nimport { isLineBreak } from './node/Whitespace';\n\nimport type { SquireConfig } from './Editor';\n\n// ---\n\ntype StyleRewriter = (\n    node: HTMLElement,\n    parent: Node,\n    config: SquireConfig,\n) => HTMLElement;\n\n// ---\n\nconst styleToSemantic: Record<\n    string,\n    { regexp: RegExp; replace: (x: any, y: string) => HTMLElement }\n> = {\n    'font-weight': {\n        regexp: /^bold|^700/i,\n        replace(): HTMLElement {\n            return createElement('B');\n        },\n    },\n    'font-style': {\n        regexp: /^italic/i,\n        replace(): HTMLElement {\n            return createElement('I');\n        },\n    },\n    'font-family': {\n        regexp: notWS,\n        replace(\n            classNames: { fontFamily: string },\n            family: string,\n        ): HTMLElement {\n            return createElement('SPAN', {\n                class: classNames.fontFamily,\n                style: 'font-family:' + family,\n            });\n        },\n    },\n    'font-size': {\n        regexp: notWS,\n        replace(classNames: { fontSize: string }, size: string): HTMLElement {\n            return createElement('SPAN', {\n                class: classNames.fontSize,\n                style: 'font-size:' + size,\n            });\n        },\n    },\n    'text-decoration': {\n        regexp: /^underline/i,\n        replace(): HTMLElement {\n            return createElement('U');\n        },\n    },\n};\n\nconst replaceStyles = (\n    node: HTMLElement,\n    _: Node,\n    config: SquireConfig,\n): HTMLElement => {\n    const style = node.style;\n    let newTreeBottom: HTMLElement | undefined;\n    let newTreeTop: HTMLElement | undefined;\n\n    for (const attr in styleToSemantic) {\n        const converter = styleToSemantic[attr];\n        const css = style.getPropertyValue(attr);\n        if (css && converter.regexp.test(css)) {\n            const el = converter.replace(config.classNames, css);\n            if (\n                el.nodeName === node.nodeName &&\n                el.className === node.className\n            ) {\n                continue;\n            }\n            if (!newTreeTop) {\n                newTreeTop = el;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(el);\n            }\n            newTreeBottom = el;\n            node.style.removeProperty(attr);\n        }\n    }\n\n    if (newTreeTop && newTreeBottom) {\n        newTreeBottom.appendChild(empty(node));\n        if (node.style.cssText) {\n            node.appendChild(newTreeTop);\n        } else {\n            replaceWith(node, newTreeTop);\n        }\n    }\n\n    return newTreeBottom || node;\n};\n\nconst replaceWithTag = (tag: string) => {\n    return (node: HTMLElement, parent: Node) => {\n        const el = createElement(tag);\n        const attributes = node.attributes;\n        for (let i = 0, l = attributes.length; i < l; i += 1) {\n            const attribute = attributes[i];\n            el.setAttribute(attribute.name, attribute.value);\n        }\n        parent.replaceChild(el, node);\n        el.appendChild(empty(node));\n        return el;\n    };\n};\n\nconst fontSizes: Record<string, string> = {\n    '1': '10',\n    '2': '13',\n    '3': '16',\n    '4': '18',\n    '5': '24',\n    '6': '32',\n    '7': '48',\n};\n\nconst stylesRewriters: Record<string, StyleRewriter> = {\n    STRONG: replaceWithTag('B'),\n    EM: replaceWithTag('I'),\n    INS: replaceWithTag('U'),\n    STRIKE: replaceWithTag('S'),\n    SPAN: replaceStyles,\n    FONT: (\n        node: HTMLElement,\n        parent: Node,\n        config: SquireConfig,\n    ): HTMLElement => {\n        const font = node as HTMLFontElement;\n        const face = font.face;\n        const size = font.size;\n        let color = font.color;\n        const classNames = config.classNames;\n        let fontSpan: HTMLElement;\n        let sizeSpan: HTMLElement;\n        let colorSpan: HTMLElement;\n        let newTreeBottom: HTMLElement | undefined;\n        let newTreeTop: HTMLElement | undefined;\n        if (face) {\n            fontSpan = createElement('SPAN', {\n                class: classNames.fontFamily,\n                style: 'font-family:' + face,\n            });\n            newTreeTop = fontSpan;\n            newTreeBottom = fontSpan;\n        }\n        if (size) {\n            sizeSpan = createElement('SPAN', {\n                class: classNames.fontSize,\n                style: 'font-size:' + fontSizes[size] + 'px',\n            });\n            if (!newTreeTop) {\n                newTreeTop = sizeSpan;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(sizeSpan);\n            }\n            newTreeBottom = sizeSpan;\n        }\n        if (color && /^#?([\\dA-F]{3}){1,2}$/i.test(color)) {\n            if (color.charAt(0) !== '#') {\n                color = '#' + color;\n            }\n            colorSpan = createElement('SPAN', {\n                class: classNames.color,\n                style: 'color:' + color,\n            });\n            if (!newTreeTop) {\n                newTreeTop = colorSpan;\n            }\n            if (newTreeBottom) {\n                newTreeBottom.appendChild(colorSpan);\n            }\n            newTreeBottom = colorSpan;\n        }\n        if (!newTreeTop || !newTreeBottom) {\n            newTreeTop = newTreeBottom = createElement('SPAN');\n        }\n        parent.replaceChild(newTreeTop, font);\n        newTreeBottom.appendChild(empty(font));\n        return newTreeBottom;\n    },\n    TT: (node: Node, parent: Node, config: SquireConfig): HTMLElement => {\n        const el = createElement('SPAN', {\n            class: config.classNames.fontFamily,\n            style: 'font-family:menlo,consolas,\"courier new\",monospace',\n        });\n        parent.replaceChild(el, node);\n        el.appendChild(empty(node));\n        return el;\n    },\n};\n\nconst allowedBlock =\n    /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/;\n\nconst blacklist = /^(?:HEAD|META|STYLE)/;\n\n/*\n    Two purposes:\n\n    1. Remove nodes we don't want, such as weird <o:p> tags, comment nodes\n       and whitespace nodes.\n    2. Convert inline tags into our preferred format.\n*/\nconst cleanTree = (\n    node: Node,\n    config: SquireConfig,\n    preserveWS?: boolean,\n): Node => {\n    const children = node.childNodes;\n\n    let nonInlineParent = node;\n    while (isInline(nonInlineParent)) {\n        nonInlineParent = nonInlineParent.parentNode!;\n    }\n    const walker = new TreeIterator<Element | Text>(\n        nonInlineParent,\n        SHOW_ELEMENT_OR_TEXT,\n    );\n\n    for (let i = 0, l = children.length; i < l; i += 1) {\n        let child = children[i];\n        const nodeName = child.nodeName;\n        const rewriter = stylesRewriters[nodeName];\n        if (child instanceof HTMLElement) {\n            const childLength = child.childNodes.length;\n            if (rewriter) {\n                child = rewriter(child, node, config);\n            } else if (blacklist.test(nodeName)) {\n                node.removeChild(child);\n                i -= 1;\n                l -= 1;\n                continue;\n            } else if (!allowedBlock.test(nodeName) && !isInline(child)) {\n                i -= 1;\n                l += childLength - 1;\n                node.replaceChild(empty(child), child);\n                continue;\n            }\n            if (childLength) {\n                cleanTree(child, config, preserveWS || nodeName === 'PRE');\n            }\n        } else {\n            if (child instanceof Text) {\n                let data = child.data;\n                const startsWithWS = !notWS.test(data.charAt(0));\n                const endsWithWS = !notWS.test(data.charAt(data.length - 1));\n                if (preserveWS || (!startsWithWS && !endsWithWS)) {\n                    continue;\n                }\n                // Iterate through the nodes; if we hit some other content\n                // before the start of a new block we don't trim\n                if (startsWithWS) {\n                    walker.currentNode = child;\n                    let sibling;\n                    while ((sibling = walker.previousPONode())) {\n                        if (\n                            sibling.nodeName === 'IMG' ||\n                            (sibling instanceof Text &&\n                                notWS.test(sibling.data))\n                        ) {\n                            break;\n                        }\n                        if (!isInline(sibling)) {\n                            sibling = null;\n                            break;\n                        }\n                    }\n                    data = data.replace(/^[ \\t\\r\\n]+/g, sibling ? ' ' : '');\n                }\n                if (endsWithWS) {\n                    walker.currentNode = child;\n                    let sibling;\n                    while ((sibling = walker.nextNode())) {\n                        if (\n                            sibling.nodeName === 'IMG' ||\n                            (sibling instanceof Text &&\n                                notWS.test(sibling.data))\n                        ) {\n                            break;\n                        }\n                        if (!isInline(sibling)) {\n                            sibling = null;\n                            break;\n                        }\n                    }\n                    data = data.replace(/[ \\t\\r\\n]+$/g, sibling ? ' ' : '');\n                }\n                if (data) {\n                    child.data = data;\n                    continue;\n                }\n            }\n            node.removeChild(child);\n            i -= 1;\n            l -= 1;\n        }\n    }\n    return node;\n};\n\n// ---\n\nconst removeEmptyInlines = (node: Node): void => {\n    const children = node.childNodes;\n    let l = children.length;\n    while (l--) {\n        const child = children[l];\n        if (child instanceof Element && !isLeaf(child)) {\n            removeEmptyInlines(child);\n            if (isInline(child) && !child.firstChild) {\n                node.removeChild(child);\n            }\n        } else if (child instanceof Text && !child.data) {\n            node.removeChild(child);\n        }\n    }\n};\n\n// ---\n\n// <br> elements are treated specially, and differently depending on the\n// browser, when in rich text editor mode. When adding HTML from external\n// sources, we must remove them, replacing the ones that actually affect\n// line breaks by wrapping the inline text in a <div>. Browsers that want <br>\n// elements at the end of each block will then have them added back in a later\n// fixCursor method call.\nconst cleanupBRs = (\n    node: Element | DocumentFragment,\n    root: Element,\n    keepForBlankLine: boolean,\n): void => {\n    const brs: NodeListOf<HTMLBRElement> = node.querySelectorAll('BR');\n    const brBreaksLine: boolean[] = [];\n    let l = brs.length;\n\n    // Must calculate whether the <br> breaks a line first, because if we\n    // have two <br>s next to each other, after the first one is converted\n    // to a block split, the second will be at the end of a block and\n    // therefore seem to not be a line break. But in its original context it\n    // was, so we should also convert it to a block split.\n    for (let i = 0; i < l; i += 1) {\n        brBreaksLine[i] = isLineBreak(brs[i], keepForBlankLine);\n    }\n    while (l--) {\n        const br = brs[l];\n        // Cleanup may have removed it\n        const parent = br.parentNode;\n        if (!parent) {\n            continue;\n        }\n        // If it doesn't break a line, just remove it; it's not doing\n        // anything useful. We'll add it back later if required by the\n        // browser. If it breaks a line, wrap the content in div tags\n        // and replace the brs.\n        if (!brBreaksLine[l]) {\n            detach(br);\n        } else if (!isInline(parent)) {\n            fixContainer(parent, root);\n        }\n    }\n};\n\n// ---\n\nconst escapeHTML = (text: string): string => {\n    return text\n        .split('&')\n        .join('&amp;')\n        .split('<')\n        .join('&lt;')\n        .split('>')\n        .join('&gt;')\n        .split('\"')\n        .join('&quot;');\n};\n\n// ---\n\nexport { cleanTree, cleanupBRs, isLineBreak, removeEmptyInlines, escapeHTML };\n", "import { TreeIterator, SHOW_ELEMENT } from './TreeIterator';\nimport { isBlock } from './Category';\n\n// ---\n\nconst getBlockWalker = (\n    node: Node,\n    root: Element | DocumentFragment,\n): TreeIterator<HTMLElement> => {\n    const walker = new TreeIterator<HTMLElement>(root, SHOW_ELEMENT, isBlock);\n    walker.currentNode = node;\n    return walker;\n};\n\nconst getPreviousBlock = (\n    node: Node,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const block = getBlockWalker(node, root).previousNode();\n    return block !== root ? block : null;\n};\n\nconst getNextBlock = (\n    node: Node,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const block = getBlockWalker(node, root).nextNode();\n    return block !== root ? block : null;\n};\n\nconst isEmptyBlock = (block: Element): boolean => {\n    return !block.textContent && !block.querySelector('IMG');\n};\n\n// ---\n\nexport { getBlockWalker, getPreviousBlock, getNextBlock, isEmptyBlock };\n", "import { isInline, isBlock } from '../node/Category';\nimport { getPreviousBlock, getNextBlock } from '../node/Block';\nimport { getNodeBeforeOffset, getNodeAfterOffset } from '../node/Node';\nimport { ZWS, notWS } from '../Constants';\nimport { isNodeContainedInRange } from './Boundaries';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from '../node/TreeIterator';\n\n// ---\n\n// Returns the first block at least partially contained by the range,\n// or null if no block is contained by the range.\nconst getStartBlockOfRange = (\n    range: Range,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const container = range.startContainer;\n    let block: HTMLElement | null;\n\n    // If inline, get the containing block.\n    if (isInline(container)) {\n        block = getPreviousBlock(container, root);\n    } else if (\n        container !== root &&\n        container instanceof HTMLElement &&\n        isBlock(container)\n    ) {\n        block = container;\n    } else {\n        const node = getNodeBeforeOffset(container, range.startOffset);\n        block = getNextBlock(node, root);\n    }\n    // Check the block actually intersects the range\n    return block && isNodeContainedInRange(range, block, true) ? block : null;\n};\n\n// Returns the last block at least partially contained by the range,\n// or null if no block is contained by the range.\nconst getEndBlockOfRange = (\n    range: Range,\n    root: Element | DocumentFragment,\n): HTMLElement | null => {\n    const container = range.endContainer;\n    let block: HTMLElement | null;\n\n    // If inline, get the containing block.\n    if (isInline(container)) {\n        block = getPreviousBlock(container, root);\n    } else if (\n        container !== root &&\n        container instanceof HTMLElement &&\n        isBlock(container)\n    ) {\n        block = container;\n    } else {\n        let node = getNodeAfterOffset(container, range.endOffset);\n        if (!node || !root.contains(node)) {\n            node = root;\n            let child: Node | null;\n            while ((child = node.lastChild)) {\n                node = child;\n            }\n        }\n        block = getPreviousBlock(node, root);\n    }\n    // Check the block actually intersects the range\n    return block && isNodeContainedInRange(range, block, true) ? block : null;\n};\n\nconst isContent = (node: Element | Text): boolean => {\n    return node instanceof Text\n        ? notWS.test(node.data)\n        : node.nodeName === 'IMG';\n};\n\nconst rangeDoesStartAtBlockBoundary = (\n    range: Range,\n    root: Element,\n): boolean => {\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    let nodeAfterCursor: Node | null;\n\n    // If in the middle or end of a text node, we're not at the boundary.\n    if (startContainer instanceof Text) {\n        const text = startContainer.data;\n        for (let i = startOffset; i > 0; i -= 1) {\n            if (text.charAt(i - 1) !== ZWS) {\n                return false;\n            }\n        }\n        nodeAfterCursor = startContainer;\n    } else {\n        nodeAfterCursor = getNodeAfterOffset(startContainer, startOffset);\n        if (nodeAfterCursor && !root.contains(nodeAfterCursor)) {\n            nodeAfterCursor = null;\n        }\n        // The cursor was right at the end of the document\n        if (!nodeAfterCursor) {\n            nodeAfterCursor = getNodeBeforeOffset(startContainer, startOffset);\n            if (nodeAfterCursor instanceof Text && nodeAfterCursor.length) {\n                return false;\n            }\n        }\n    }\n\n    // Otherwise, look for any previous content in the same block.\n    const block = getStartBlockOfRange(range, root);\n    if (!block) {\n        return false;\n    }\n    const contentWalker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        isContent,\n    );\n    contentWalker.currentNode = nodeAfterCursor;\n\n    return !contentWalker.previousNode();\n};\n\nconst rangeDoesEndAtBlockBoundary = (range: Range, root: Element): boolean => {\n    const endContainer = range.endContainer;\n    const endOffset = range.endOffset;\n    let currentNode: Node;\n\n    // If in a text node with content, and not at the end, we're not\n    // at the boundary. Ignore ZWS.\n    if (endContainer instanceof Text) {\n        const text = endContainer.data;\n        const length = text.length;\n        for (let i = endOffset; i < length; i += 1) {\n            if (text.charAt(i) !== ZWS) {\n                return false;\n            }\n        }\n        currentNode = endContainer;\n    } else {\n        currentNode = getNodeBeforeOffset(endContainer, endOffset);\n    }\n\n    // Otherwise, look for any further content in the same block.\n    const block = getEndBlockOfRange(range, root);\n    if (!block) {\n        return false;\n    }\n    const contentWalker = new TreeIterator<Element | Text>(\n        block,\n        SHOW_ELEMENT_OR_TEXT,\n        isContent,\n    );\n    contentWalker.currentNode = currentNode;\n    return !contentWalker.nextNode();\n};\n\nconst expandRangeToBlockBoundaries = (range: Range, root: Element): void => {\n    const start = getStartBlockOfRange(range, root);\n    const end = getEndBlockOfRange(range, root);\n    let parent: Node;\n\n    if (start && end) {\n        parent = start.parentNode!;\n        range.setStart(parent, Array.from(parent.childNodes).indexOf(start));\n        parent = end.parentNode!;\n        range.setEnd(parent, Array.from(parent.childNodes).indexOf(end) + 1);\n    }\n};\n\n// ---\n\nexport {\n    getStartBlockOfRange,\n    getEndBlockOfRange,\n    rangeDoesStartAtBlockBoundary,\n    rangeDoesEndAtBlockBoundary,\n    expandRangeToBlockBoundaries,\n};\n", "import { cleanupBRs } from '../Clean';\nimport {\n    split,\n    fixCursor,\n    mergeWithBlock,\n    fixContainer,\n    mergeContainers,\n} from '../node/MergeSplit';\nimport { detach, getNearest, getLength } from '../node/Node';\nimport { TreeIterator, SHOW_ELEMENT_OR_TEXT } from '../node/TreeIterator';\nimport { isInline, isContainer, isLeaf } from '../node/Category';\nimport { getNextBlock, isEmptyBlock, getPreviousBlock } from '../node/Block';\nimport {\n    getStartBlockOfRange,\n    getEndBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n    rangeDoesStartAtBlockBoundary,\n} from './Block';\nimport {\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n} from './Boundaries';\n\n// ---\n\nfunction createRange(startContainer: Node, startOffset: number): Range;\nfunction createRange(\n    startContainer: Node,\n    startOffset: number,\n    endContainer: Node,\n    endOffset: number,\n): Range;\nfunction createRange(\n    startContainer: Node,\n    startOffset: number,\n    endContainer?: Node,\n    endOffset?: number,\n): Range {\n    const range = document.createRange();\n    range.setStart(startContainer, startOffset);\n    if (endContainer && typeof endOffset === 'number') {\n        range.setEnd(endContainer, endOffset);\n    } else {\n        range.setEnd(startContainer, startOffset);\n    }\n    return range;\n}\n\nconst insertNodeInRange = (range: Range, node: Node): void => {\n    // Insert at start.\n    let { startContainer, startOffset, endContainer, endOffset } = range;\n    let children: NodeListOf<ChildNode>;\n\n    // If part way through a text node, split it.\n    if (startContainer instanceof Text) {\n        const parent = startContainer.parentNode!;\n        children = parent.childNodes;\n        if (startOffset === startContainer.length) {\n            startOffset = Array.from(children).indexOf(startContainer) + 1;\n            if (range.collapsed) {\n                endContainer = parent;\n                endOffset = startOffset;\n            }\n        } else {\n            if (startOffset) {\n                const afterSplit = startContainer.splitText(startOffset);\n                if (endContainer === startContainer) {\n                    endOffset -= startOffset;\n                    endContainer = afterSplit;\n                } else if (endContainer === parent) {\n                    endOffset += 1;\n                }\n                startContainer = afterSplit;\n            }\n            startOffset = Array.from(children).indexOf(\n                startContainer as ChildNode,\n            );\n        }\n        startContainer = parent;\n    } else {\n        children = startContainer.childNodes;\n    }\n\n    const childCount = children.length;\n\n    if (startOffset === childCount) {\n        startContainer.appendChild(node);\n    } else {\n        startContainer.insertBefore(node, children[startOffset]);\n    }\n\n    if (startContainer === endContainer) {\n        endOffset += children.length - childCount;\n    }\n\n    range.setStart(startContainer, startOffset);\n    range.setEnd(endContainer, endOffset);\n};\n\n/**\n * Removes the contents of the range and returns it as a DocumentFragment.\n * The range at the end will be at the same position, with the edges just\n * before/after the split. If the start/end have the same parents, it will\n * be collapsed.\n */\nconst extractContentsOfRange = (\n    range: Range,\n    common: Node | null,\n    root: Element,\n): DocumentFragment => {\n    const frag = document.createDocumentFragment();\n    if (range.collapsed) {\n        return frag;\n    }\n\n    if (!common) {\n        common = range.commonAncestorContainer;\n    }\n    if (common instanceof Text) {\n        common = common.parentNode!;\n    }\n\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n\n    let endContainer = split(range.endContainer, range.endOffset, common, root);\n    let endOffset = 0;\n\n    let node = split(startContainer, startOffset, common, root);\n    while (node && node !== endContainer) {\n        const next = node.nextSibling;\n        frag.appendChild(node);\n        node = next;\n    }\n\n    // Merge text nodes if adjacent\n    if (startContainer instanceof Text && endContainer instanceof Text) {\n        startContainer.appendData(endContainer.data);\n        detach(endContainer);\n        endContainer = startContainer;\n        endOffset = startOffset;\n    }\n\n    range.setStart(startContainer, startOffset);\n    if (endContainer) {\n        range.setEnd(endContainer, endOffset);\n    } else {\n        // endContainer will be null if at end of parent's child nodes list.\n        range.setEnd(common, common.childNodes.length);\n    }\n\n    fixCursor(common);\n\n    return frag;\n};\n\n/**\n * Returns the next/prev node that's part of the same inline content.\n */\nconst getAdjacentInlineNode = (\n    iterator: TreeIterator<Node>,\n    method: 'nextNode' | 'previousPONode',\n    node: Node,\n): Node | null => {\n    iterator.currentNode = node;\n    let nextNode: Node | null;\n    while ((nextNode = iterator[method]())) {\n        if (nextNode instanceof Text || isLeaf(nextNode)) {\n            return nextNode;\n        }\n        if (!isInline(nextNode)) {\n            return null;\n        }\n    }\n    return null;\n};\n\nconst deleteContentsOfRange = (\n    range: Range,\n    root: Element,\n): DocumentFragment => {\n    const startBlock = getStartBlockOfRange(range, root);\n    let endBlock = getEndBlockOfRange(range, root);\n    const needsMerge = startBlock !== endBlock;\n\n    // Move boundaries up as much as possible without exiting block,\n    // to reduce need to split.\n    if (startBlock && endBlock) {\n        moveRangeBoundariesDownTree(range);\n        moveRangeBoundariesUpTree(range, startBlock, endBlock, root);\n    }\n\n    // Remove selected range\n    const frag = extractContentsOfRange(range, null, root);\n\n    // Move boundaries back down tree as far as possible.\n    moveRangeBoundariesDownTree(range);\n\n    // If we split into two different blocks, merge the blocks.\n    if (needsMerge) {\n        // endBlock will have been split, so need to refetch\n        endBlock = getEndBlockOfRange(range, root);\n        if (startBlock && endBlock && startBlock !== endBlock) {\n            mergeWithBlock(startBlock, endBlock, range, root);\n        }\n    }\n\n    // Ensure block has necessary children\n    if (startBlock) {\n        fixCursor(startBlock);\n    }\n\n    // Ensure root has a block-level element in it.\n    const child = root.firstChild;\n    if (!child || child.nodeName === 'BR') {\n        fixCursor(root);\n        if (root.firstChild) {\n            range.selectNodeContents(root.firstChild);\n        }\n    }\n\n    range.collapse(true);\n\n    // Now we may need to swap a space for a nbsp if the browser is going\n    // to swallow it due to HTML whitespace rules:\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    const iterator = new TreeIterator(root, SHOW_ELEMENT_OR_TEXT);\n\n    // Find the character after cursor point\n    let afterNode: Node | null = startContainer;\n    let afterOffset = startOffset;\n    if (!(afterNode instanceof Text) || afterOffset === afterNode.data.length) {\n        afterNode = getAdjacentInlineNode(iterator, 'nextNode', afterNode);\n        afterOffset = 0;\n    }\n\n    // Find the character before cursor point\n    let beforeNode: Node | null = startContainer;\n    let beforeOffset = startOffset - 1;\n    if (!(beforeNode instanceof Text) || beforeOffset === -1) {\n        beforeNode = getAdjacentInlineNode(\n            iterator,\n            'previousPONode',\n            afterNode ||\n                (startContainer instanceof Text\n                    ? startContainer\n                    : startContainer.childNodes[startOffset] || startContainer),\n        );\n        if (beforeNode instanceof Text) {\n            beforeOffset = beforeNode.data.length;\n        }\n    }\n\n    // If range starts at block boundary and character after cursor point\n    // is a space, replace with nbsp\n    let node = null;\n    let offset = 0;\n    if (\n        afterNode instanceof Text &&\n        afterNode.data.charAt(afterOffset) === ' ' &&\n        rangeDoesStartAtBlockBoundary(range, root)\n    ) {\n        node = afterNode;\n        offset = afterOffset;\n    } else if (\n        beforeNode instanceof Text &&\n        beforeNode.data.charAt(beforeOffset) === ' '\n    ) {\n        // If character before cursor point is a space, replace with nbsp\n        // if either:\n        // a) There is a space after it; or\n        // b) The point after is the end of the block\n        if (\n            (afterNode instanceof Text &&\n                afterNode.data.charAt(afterOffset) === ' ') ||\n            rangeDoesEndAtBlockBoundary(range, root)\n        ) {\n            node = beforeNode;\n            offset = beforeOffset;\n        }\n    }\n    if (node) {\n        node.replaceData(offset, 1, '\u00A0'); // nbsp\n    }\n    // Range needs to be put back in place\n    range.setStart(startContainer, startOffset);\n    range.collapse(true);\n\n    return frag;\n};\n\n// Contents of range will be deleted.\n// After method, range will be around inserted content\nconst insertTreeFragmentIntoRange = (\n    range: Range,\n    frag: DocumentFragment,\n    root: Element,\n): void => {\n    const firstInFragIsInline = frag.firstChild && isInline(frag.firstChild);\n    let node: Node | null;\n\n    // Fixup content: ensure no top-level inline, and add cursor fix elements.\n    fixContainer(frag, root);\n    node = frag;\n    while ((node = getNextBlock(node, root))) {\n        fixCursor(node);\n    }\n\n    // Delete any selected content.\n    if (!range.collapsed) {\n        deleteContentsOfRange(range, root);\n    }\n\n    // Move range down into text nodes.\n    moveRangeBoundariesDownTree(range);\n    range.collapse(false); // collapse to end\n\n    // Where will we split up to? First blockquote parent, otherwise root.\n    const stopPoint =\n        getNearest(range.endContainer, root, 'BLOCKQUOTE') || root;\n\n    // Merge the contents of the first block in the frag with the focused block.\n    // If there are contents in the block after the focus point, collect this\n    // up to insert in the last block later. This preserves the style that was\n    // present in this bit of the page.\n    //\n    // If the block being inserted into is empty though, replace it instead of\n    // merging if the fragment had block contents.\n    // e.g. <blockquote><p>Foo</p></blockquote>\n    // This seems a reasonable approximation of user intent.\n    let block = getStartBlockOfRange(range, root);\n    let blockContentsAfterSplit: DocumentFragment | null = null;\n    const firstBlockInFrag = getNextBlock(frag, frag);\n    const replaceBlock = !firstInFragIsInline && !!block && isEmptyBlock(block);\n    if (\n        block &&\n        firstBlockInFrag &&\n        !replaceBlock &&\n        // Don't merge table cells or PRE elements into block\n        !getNearest(firstBlockInFrag, frag, 'PRE') &&\n        !getNearest(firstBlockInFrag, frag, 'TABLE')\n    ) {\n        moveRangeBoundariesUpTree(range, block, block, root);\n        range.collapse(true); // collapse to start\n        let container = range.endContainer;\n        let offset = range.endOffset;\n        // Remove trailing <br> \u2013 we don't want this considered content to be\n        // inserted again later\n        cleanupBRs(block as HTMLElement, root, false);\n        if (isInline(container)) {\n            // Split up to block parent.\n            const nodeAfterSplit = split(\n                container,\n                offset,\n                getPreviousBlock(container, root) || root,\n                root,\n            ) as Node;\n            container = nodeAfterSplit.parentNode!;\n            offset = Array.from(container.childNodes).indexOf(\n                nodeAfterSplit as ChildNode,\n            );\n        }\n        if (/*isBlock( container ) && */ offset !== getLength(container)) {\n            // Collect any inline contents of the block after the range point\n            blockContentsAfterSplit = document.createDocumentFragment();\n            while ((node = container.childNodes[offset])) {\n                blockContentsAfterSplit.appendChild(node);\n            }\n        }\n        // And merge the first block in.\n        mergeWithBlock(container, firstBlockInFrag, range, root);\n\n        // And where we will insert\n        offset =\n            Array.from(container.parentNode!.childNodes).indexOf(\n                container as ChildNode,\n            ) + 1;\n        container = container.parentNode!;\n        range.setEnd(container, offset);\n    }\n\n    // Is there still any content in the fragment?\n    if (getLength(frag)) {\n        if (replaceBlock && block) {\n            range.setEndBefore(block);\n            range.collapse(false);\n            detach(block);\n        }\n        moveRangeBoundariesUpTree(range, stopPoint, stopPoint, root);\n        // Now split after block up to blockquote (if a parent) or root\n        let nodeAfterSplit = split(\n            range.endContainer,\n            range.endOffset,\n            stopPoint,\n            root,\n        ) as Node | null;\n        const nodeBeforeSplit = nodeAfterSplit\n            ? nodeAfterSplit.previousSibling\n            : stopPoint.lastChild;\n        stopPoint.insertBefore(frag, nodeAfterSplit);\n        if (nodeAfterSplit) {\n            range.setEndBefore(nodeAfterSplit);\n        } else {\n            range.setEnd(stopPoint, getLength(stopPoint));\n        }\n        block = getEndBlockOfRange(range, root);\n\n        // Get a reference that won't be invalidated if we merge containers.\n        moveRangeBoundariesDownTree(range);\n        const container = range.endContainer;\n        const offset = range.endOffset;\n\n        // Merge inserted containers with edges of split\n        if (nodeAfterSplit && isContainer(nodeAfterSplit)) {\n            mergeContainers(nodeAfterSplit, root);\n        }\n        nodeAfterSplit = nodeBeforeSplit && nodeBeforeSplit.nextSibling;\n        if (nodeAfterSplit && isContainer(nodeAfterSplit)) {\n            mergeContainers(nodeAfterSplit, root);\n        }\n        range.setEnd(container, offset);\n    }\n\n    // Insert inline content saved from before.\n    if (blockContentsAfterSplit && block) {\n        const tempRange = range.cloneRange();\n        fixCursor(blockContentsAfterSplit);\n        mergeWithBlock(block, blockContentsAfterSplit, tempRange, root);\n        range.setEnd(tempRange.endContainer, tempRange.endOffset);\n    }\n    moveRangeBoundariesDownTree(range);\n};\n\n// ---\n\nexport {\n    createRange,\n    deleteContentsOfRange,\n    extractContentsOfRange,\n    insertNodeInRange,\n    insertTreeFragmentIntoRange,\n};\n", "import { SHOW_ELEMENT_OR_TEXT, TreeIterator } from '../node/TreeIterator';\nimport { isNodeContainedInRange } from './Boundaries';\nimport { isInline } from '../node/Category';\n\n// ---\n\nconst getTextContentsOfRange = (range: Range) => {\n    if (range.collapsed) {\n        return '';\n    }\n    const startContainer = range.startContainer;\n    const endContainer = range.endContainer;\n    const walker = new TreeIterator<Element | Text>(\n        range.commonAncestorContainer,\n        SHOW_ELEMENT_OR_TEXT,\n        (node) => {\n            return isNodeContainedInRange(range, node, true);\n        },\n    );\n    walker.currentNode = startContainer;\n\n    let node: Node | null = startContainer;\n    let textContent = '';\n    let addedTextInBlock = false;\n    let value: string;\n\n    if (\n        (!(node instanceof Element) && !(node instanceof Text)) ||\n        !walker.filter(node)\n    ) {\n        node = walker.nextNode();\n    }\n\n    while (node) {\n        if (node instanceof Text) {\n            value = node.data;\n            if (value && /\\S/.test(value)) {\n                if (node === endContainer) {\n                    value = value.slice(0, range.endOffset);\n                }\n                if (node === startContainer) {\n                    value = value.slice(range.startOffset);\n                }\n                textContent += value;\n                addedTextInBlock = true;\n            }\n        } else if (\n            node.nodeName === 'BR' ||\n            (addedTextInBlock && !isInline(node))\n        ) {\n            textContent += '\\n';\n            addedTextInBlock = false;\n        }\n        node = walker.nextNode();\n    }\n    // Replace nbsp with regular space;\n    // eslint-disable-next-line no-irregular-whitespace\n    textContent = textContent.replace(/\u00A0/g, ' ');\n\n    return textContent;\n};\n\n// ---\n\nexport { getTextContentsOfRange };\n", "import { isWin, isGecko, isLegacyEdge, notWS } from './Constants';\nimport { createElement, detach } from './node/Node';\nimport { getStartBlockOfRange, getEndBlockOfRange } from './range/Block';\nimport { createRange, deleteContentsOfRange } from './range/InsertDelete';\nimport {\n    moveRangeBoundariesDownTree,\n    moveRangeBoundariesUpTree,\n} from './range/Boundaries';\n\nimport type { Squire } from './Editor';\nimport { getTextContentsOfRange } from './range/Contents';\n\n// ---\n\nconst indexOf = Array.prototype.indexOf;\n\nconst extractRangeToClipboard = (\n    event: ClipboardEvent,\n    range: Range,\n    root: HTMLElement,\n    removeRangeFromDocument: boolean,\n    toCleanHTML: null | ((html: string) => string),\n    toPlainText: null | ((html: string) => string),\n    plainTextOnly: boolean,\n): boolean => {\n    // Edge only seems to support setting plain text as of 2016-03-11.\n    const clipboardData = event.clipboardData;\n    if (isLegacyEdge || !clipboardData) {\n        return false;\n    }\n    // First get the plain text version from the range (unless we have a custom\n    // HTML -> Text conversion fn)\n    let text = toPlainText ? '' : getTextContentsOfRange(range);\n\n    // Clipboard content should include all parents within block, or all\n    // parents up to root if selection across blocks\n    const startBlock = getStartBlockOfRange(range, root);\n    const endBlock = getEndBlockOfRange(range, root);\n    let copyRoot = root;\n\n    // If the content is not in well-formed blocks, the start and end block\n    // may be the same, but actually the range goes outside it. Must check!\n    if (\n        startBlock === endBlock &&\n        startBlock?.contains(range.commonAncestorContainer)\n    ) {\n        copyRoot = startBlock;\n    }\n\n    // Extract the contents\n    let contents: Node;\n    if (removeRangeFromDocument) {\n        contents = deleteContentsOfRange(range, root);\n    } else {\n        // Clone range to mutate, then move up as high as possible without\n        // passing the copy root node.\n        range = range.cloneRange();\n        moveRangeBoundariesDownTree(range);\n        moveRangeBoundariesUpTree(range, copyRoot, copyRoot, root);\n        contents = range.cloneContents();\n    }\n\n    // Add any other parents not in extracted content, up to copy root\n    let parent = range.commonAncestorContainer;\n    if (parent instanceof Text) {\n        parent = parent.parentNode!;\n    }\n    while (parent && parent !== copyRoot) {\n        const newContents = parent.cloneNode(false);\n        newContents.appendChild(contents);\n        contents = newContents;\n        parent = parent.parentNode!;\n    }\n\n    // Get HTML version of data\n    let html: string | undefined;\n    if (\n        contents.childNodes.length === 1 &&\n        contents.childNodes[0] instanceof Text\n    ) {\n        // Replace nbsp with regular space;\n        // eslint-disable-next-line no-irregular-whitespace\n        text = contents.childNodes[0].data.replace(/\u00A0/g, ' ');\n        plainTextOnly = true;\n    } else {\n        const node = createElement('DIV') as HTMLDivElement;\n        node.appendChild(contents);\n        html = node.innerHTML;\n        if (toCleanHTML) {\n            html = toCleanHTML(html);\n        }\n    }\n\n    // Get Text version of data if converting from HTML\n    if (toPlainText && html !== undefined) {\n        text = toPlainText(html);\n    }\n\n    // Firefox (and others?) returns unix line endings (\\n) even on Windows.\n    // If on Windows, normalise to \\r\\n, since Notepad and some other crappy\n    // apps do not understand just \\n.\n    if (isWin) {\n        text = text.replace(/\\r?\\n/g, '\\r\\n');\n    }\n\n    // Set clipboard data\n    if (!plainTextOnly && html && text !== html) {\n        clipboardData.setData('text/html', html);\n    }\n    clipboardData.setData('text/plain', text);\n    event.preventDefault();\n\n    return true;\n};\n\n// ---\n\nconst _onCut = function (this: Squire, event: ClipboardEvent): void {\n    const range: Range = this.getSelection();\n    const root: HTMLElement = this._root;\n\n    // Nothing to do\n    if (range.collapsed) {\n        event.preventDefault();\n        return;\n    }\n\n    // Save undo checkpoint\n    this.saveUndoState(range);\n\n    const handled = extractRangeToClipboard(\n        event,\n        range,\n        root,\n        true,\n        this._config.willCutCopy,\n        this._config.toPlainText,\n        false,\n    );\n    if (!handled) {\n        setTimeout(() => {\n            try {\n                // If all content removed, ensure div at start of root.\n                this._ensureBottomLine();\n            } catch (error) {\n                this._config.didError(error);\n            }\n        }, 0);\n    }\n\n    this.setSelection(range);\n};\n\nconst _onCopy = function (this: Squire, event: ClipboardEvent): void {\n    extractRangeToClipboard(\n        event,\n        this.getSelection(),\n        this._root,\n        false,\n        this._config.willCutCopy,\n        this._config.toPlainText,\n        false,\n    );\n};\n\n// Need to monitor for shift key like this, as event.shiftKey is not available\n// in paste event.\nconst _monitorShiftKey = function (this: Squire, event: KeyboardEvent): void {\n    this._isShiftDown = event.shiftKey;\n};\n\nconst _onPaste = function (this: Squire, event: ClipboardEvent): void {\n    const clipboardData = event.clipboardData;\n    const items = clipboardData?.items;\n    const choosePlain: boolean | undefined = this._isShiftDown;\n    let hasRTF = false;\n    let hasImage = false;\n    let plainItem: null | DataTransferItem = null;\n    let htmlItem: null | DataTransferItem = null;\n\n    // Current HTML5 Clipboard interface\n    // ---------------------------------\n    // https://html.spec.whatwg.org/multipage/interaction.html\n    if (items) {\n        let l = items.length;\n        while (l--) {\n            const item = items[l];\n            const type = item.type;\n            if (type === 'text/html') {\n                htmlItem = item;\n                // iOS copy URL gives you type text/uri-list which is just a list\n                // of 1 or more URLs separated by new lines. Can just treat as\n                // plain text.\n            } else if (type === 'text/plain' || type === 'text/uri-list') {\n                plainItem = item;\n            } else if (type === 'text/rtf') {\n                hasRTF = true;\n            } else if (/^image\\/.*/.test(type)) {\n                hasImage = true;\n            }\n        }\n\n        // Treat image paste as a drop of an image file. When you copy\n        // an image in Chrome/Firefox (at least), it copies the image data\n        // but also an HTML version (referencing the original URL of the image)\n        // and a plain text version.\n        //\n        // However, when you copy in Excel, you get html, rtf, text, image;\n        // in this instance you want the html version! So let's try using\n        // the presence of text/rtf as an indicator to choose the html version\n        // over the image.\n        if (hasImage && !(hasRTF && htmlItem)) {\n            event.preventDefault();\n            this.fireEvent('pasteImage', {\n                clipboardData,\n            });\n            return;\n        }\n\n        // Edge only provides access to plain text as of 2016-03-11 and gives no\n        // indication there should be an HTML part. However, it does support\n        // access to image data, so we check for that first. Otherwise though,\n        // fall through to fallback clipboard handling methods\n        if (!isLegacyEdge) {\n            event.preventDefault();\n            if (htmlItem && (!choosePlain || !plainItem)) {\n                htmlItem.getAsString((html) => {\n                    this.insertHTML(html, true);\n                });\n            } else if (plainItem) {\n                plainItem.getAsString((text) => {\n                    // If we have a selection and text is solely a URL,\n                    // just make the text a link.\n                    let isLink = false;\n                    const range = this.getSelection();\n                    if (!range.collapsed && notWS.test(range.toString())) {\n                        const match = this.linkRegExp.exec(text);\n                        isLink = !!match && match[0].length === text.length;\n                    }\n                    if (isLink) {\n                        this.makeLink(text);\n                    } else {\n                        this.insertPlainText(text, true);\n                    }\n                });\n            }\n            return;\n        }\n    }\n\n    // Old interface\n    // -------------\n\n    // Safari (and indeed many other OS X apps) copies stuff as text/rtf\n    // rather than text/html; even from a webpage in Safari. The only way\n    // to get an HTML version is to fallback to letting the browser insert\n    // the content. Same for getting image data. *Sigh*.\n    //\n    // Firefox is even worse: it doesn't even let you know that there might be\n    // an RTF version on the clipboard, but it will also convert to HTML if you\n    // let the browser insert the content. I've filed\n    // https://bugzilla.mozilla.org/show_bug.cgi?id=1254028\n    const types = clipboardData?.types;\n    if (\n        !isLegacyEdge &&\n        types &&\n        (indexOf.call(types, 'text/html') > -1 ||\n            (!isGecko &&\n                indexOf.call(types, 'text/plain') > -1 &&\n                indexOf.call(types, 'text/rtf') < 0))\n    ) {\n        event.preventDefault();\n        // Abiword on Linux copies a plain text and html version, but the HTML\n        // version is the empty string! So always try to get HTML, but if none,\n        // insert plain text instead. On iOS, Facebook (and possibly other\n        // apps?) copy links as type text/uri-list, but also insert a **blank**\n        // text/plain item onto the clipboard. Why? Who knows.\n        let data;\n        if (!choosePlain && (data = clipboardData.getData('text/html'))) {\n            this.insertHTML(data, true);\n        } else if (\n            (data = clipboardData.getData('text/plain')) ||\n            (data = clipboardData.getData('text/uri-list'))\n        ) {\n            this.insertPlainText(data, true);\n        }\n        return;\n    }\n\n    // No interface. Includes all versions of IE :(\n    // --------------------------------------------\n\n    const body = document.body;\n    const range = this.getSelection();\n    const startContainer = range.startContainer;\n    const startOffset = range.startOffset;\n    const endContainer = range.endContainer;\n    const endOffset = range.endOffset;\n\n    // We need to position the pasteArea in the visible portion of the screen\n    // to stop the browser auto-scrolling.\n    let pasteArea: Element = createElement('DIV', {\n        contenteditable: 'true',\n        style: 'position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;',\n    });\n    body.appendChild(pasteArea);\n    range.selectNodeContents(pasteArea);\n    this.setSelection(range);\n\n    // A setTimeout of 0 means this is added to the back of the\n    // single javascript thread, so it will be executed after the\n    // paste event.\n    setTimeout(() => {\n        try {\n            // Get the pasted content and clean\n            let html = '';\n            let next: Element = pasteArea;\n            let first: Node | null;\n\n            // #88: Chrome can apparently split the paste area if certain\n            // content is inserted; gather them all up.\n            while ((pasteArea = next)) {\n                next = pasteArea.nextSibling as Element;\n                detach(pasteArea);\n                // Safari and IE like putting extra divs around things.\n                first = pasteArea.firstChild;\n                if (\n                    first &&\n                    first === pasteArea.lastChild &&\n                    first instanceof HTMLDivElement\n                ) {\n                    pasteArea = first;\n                }\n                html += pasteArea.innerHTML;\n            }\n\n            this.setSelection(\n                createRange(\n                    startContainer,\n                    startOffset,\n                    endContainer,\n                    endOffset,\n                ),\n            );\n\n            if (html) {\n                this.insertHTML(html, true);\n            }\n        } catch (error) {\n            this._config.didError(error);\n        }\n    }, 0);\n};\n\n// On Windows you can drag an drop text. We can't handle this ourselves, because\n// as far as I can see, there's no way to get the drop insertion point. So just\n// save an undo state and hope for the best.\nconst _onDrop = function (this: Squire, event: DragEvent): void {\n    // it's possible for dataTransfer to be null, let's avoid it.\n    if (!event.dataTransfer) {\n        return;\n    }\n    const types = event.dataTransfer.types;\n    let l = types.length;\n    let hasPlain = false;\n    let hasHTML = false;\n    while (l--) {\n        switch (types[l]) {\n            case 'text/plain':\n                hasPlain = true;\n                break;\n            case 'text/html':\n                hasHTML = true;\n                break;\n            default:\n                return;\n        }\n    }\n    if (hasHTML || (hasPlain && this.saveUndoState)) {\n        this.saveUndoState();\n    }\n};\n\n// ---\n\nexport {\n    extractRangeToClipboard,\n    _onCut,\n    _onCopy,\n    _monitorShiftKey,\n    _onPaste,\n    _onDrop,\n};\n", "import type { Squire } from '../Editor';\n\n// ---\n\nconst Enter = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    event.preventDefault();\n    self.splitBlock(event.shiftKey, range);\n};\n\n// ---\n\nexport { Enter };\n", "import { ZWS } from '../Constants';\nimport { getPreviousBlock } from '../node/Block';\nimport { isInline, isBlock } from '../node/Category';\nimport { fixCursor } from '../node/MergeSplit';\nimport { createElement, detach, getNearest } from '../node/Node';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\n// If you delete the content inside a span with a font styling, Webkit will\n// replace it with a <font> tag (!). If you delete all the text inside a\n// link in Opera, it won't delete the link. Let's make things consistent. If\n// you delete all text inside an inline tag, remove the inline tag.\nconst afterDelete = (self: Squire, range?: Range): void => {\n    try {\n        if (!range) {\n            range = self.getSelection();\n        }\n        let node = range!.startContainer;\n        // Climb the tree from the focus point while we are inside an empty\n        // inline element\n        if (node instanceof Text) {\n            node = node.parentNode!;\n        }\n        let parent = node;\n        while (\n            isInline(parent) &&\n            (!parent.textContent || parent.textContent === ZWS)\n        ) {\n            node = parent;\n            parent = node.parentNode!;\n        }\n        // If focused in empty inline element\n        if (node !== parent) {\n            // Move focus to just before empty inline(s)\n            range!.setStart(\n                parent,\n                Array.from(parent.childNodes as NodeListOf<Node>).indexOf(node),\n            );\n            range!.collapse(true);\n            // Remove empty inline(s)\n            parent.removeChild(node);\n            // Fix cursor in block\n            if (!isBlock(parent)) {\n                parent = getPreviousBlock(parent, self._root) || self._root;\n            }\n            fixCursor(parent);\n            // Move cursor into text node\n            moveRangeBoundariesDownTree(range!);\n        }\n        // If you delete the last character in the sole <div> in Chrome,\n        // it removes the div and replaces it with just a <br> inside the\n        // root. Detach the <br>; the _ensureBottomLine call will insert a new\n        // block.\n        if (\n            node === self._root &&\n            (node = node.firstChild!) &&\n            node.nodeName === 'BR'\n        ) {\n            detach(node);\n        }\n        self._ensureBottomLine();\n        self.setSelection(range);\n        self._updatePath(range, true);\n    } catch (error) {\n        self._config.didError(error);\n    }\n};\n\nconst detachUneditableNode = (node: Node, root: Element): void => {\n    let parent: Node | null;\n    while ((parent = node.parentNode)) {\n        if (parent === root || (parent as HTMLElement).isContentEditable) {\n            break;\n        }\n        node = parent;\n    }\n    detach(node);\n};\n\n// ---\n\nconst linkifyText = (self: Squire, textNode: Text, offset: number): void => {\n    if (getNearest(textNode, self._root, 'A')) {\n        return;\n    }\n    const data = textNode.data || '';\n    const searchFrom =\n        Math.max(\n            data.lastIndexOf(' ', offset - 1),\n            data.lastIndexOf('\u00A0', offset - 1),\n        ) + 1;\n    const searchText = data.slice(searchFrom, offset);\n    const match = self.linkRegExp.exec(searchText);\n    if (match) {\n        // Record an undo point\n        const selection = self.getSelection();\n        self._docWasChanged();\n        self._recordUndoState(selection);\n        self._getRangeAndRemoveBookmark(selection);\n\n        const index = searchFrom + match.index;\n        const endIndex = index + match[0].length;\n        const needsSelectionUpdate = selection.startContainer === textNode;\n        const newSelectionOffset = selection.startOffset - endIndex;\n        if (index) {\n            textNode = textNode.splitText(index);\n        }\n\n        const defaultAttributes = self._config.tagAttributes.a;\n        const link = createElement(\n            'A',\n            Object.assign(\n                {\n                    href: match[1]\n                        ? /^(?:ht|f)tps?:/i.test(match[1])\n                            ? match[1]\n                            : 'http://' + match[1]\n                        : 'mailto:' + match[0],\n                },\n                defaultAttributes,\n            ),\n        );\n        link.textContent = data.slice(index, endIndex);\n        textNode.parentNode!.insertBefore(link, textNode);\n        textNode.data = data.slice(endIndex);\n\n        if (needsSelectionUpdate) {\n            selection.setStart(textNode, newSelectionOffset);\n            selection.setEnd(textNode, newSelectionOffset);\n        }\n        self.setSelection(selection);\n    }\n};\n\n// ---\n\nexport { afterDelete, detachUneditableNode, linkifyText };\n", "import type { Squire } from '../Editor';\nimport { getPreviousBlock } from '../node/Block';\nimport {\n    fixContainer,\n    mergeContainers,\n    mergeWithBlock,\n} from '../node/MergeSplit';\nimport { getNearest } from '../node/Node';\nimport {\n    getStartBlockOfRange,\n    rangeDoesStartAtBlockBoundary,\n} from '../range/Block';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport { afterDelete, detachUneditableNode } from './KeyHelpers';\n\n// ---\n\nconst Backspace = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root: Element = self._root;\n    self._removeZWS();\n    // Record undo checkpoint.\n    self.saveUndoState(range);\n    if (!range.collapsed) {\n        // If not collapsed, delete contents\n        event.preventDefault();\n        deleteContentsOfRange(range, root);\n        afterDelete(self, range);\n    } else if (rangeDoesStartAtBlockBoundary(range, root)) {\n        // If at beginning of block, merge with previous\n        event.preventDefault();\n        const startBlock = getStartBlockOfRange(range, root);\n        if (!startBlock) {\n            return;\n        }\n        let current = startBlock;\n        // In case inline data has somehow got between blocks.\n        fixContainer(current.parentNode!, root);\n        // Now get previous block\n        const previous = getPreviousBlock(current, root);\n        // Must not be at the very beginning of the text area.\n        if (previous) {\n            // If not editable, just delete whole block.\n            if (!(previous as HTMLElement).isContentEditable) {\n                detachUneditableNode(previous, root);\n                return;\n            }\n            // Otherwise merge.\n            mergeWithBlock(previous, current, range, root);\n            // If deleted line between containers, merge newly adjacent\n            // containers.\n            current = previous.parentNode as HTMLElement;\n            while (current !== root && !current.nextSibling) {\n                current = current.parentNode as HTMLElement;\n            }\n            if (\n                current !== root &&\n                (current = current.nextSibling as HTMLElement)\n            ) {\n                mergeContainers(current, root);\n            }\n            self.setSelection(range);\n            // If at very beginning of text area, allow backspace\n            // to break lists/blockquote.\n        } else if (current) {\n            if (\n                getNearest(current, root, 'UL') ||\n                getNearest(current, root, 'OL')\n            ) {\n                // Break list\n                self.decreaseListLevel(range);\n                return;\n            } else if (getNearest(current, root, 'BLOCKQUOTE')) {\n                // Break blockquote\n                self.removeQuote(range);\n                return;\n            }\n            self.setSelection(range);\n            self._updatePath(range, true);\n        }\n    } else {\n        // If deleting text inside a link that looks like a URL, delink.\n        // This is to allow you to easily correct auto-linked text.\n        moveRangeBoundariesDownTree(range);\n        const text = range.startContainer;\n        const offset = range.startOffset;\n        const a = text.parentNode;\n        if (\n            text instanceof Text &&\n            a instanceof HTMLAnchorElement &&\n            offset &&\n            a.href.includes(text.data)\n        ) {\n            text.deleteData(offset - 1, 1);\n            self.setSelection(range);\n            self.removeLink();\n            event.preventDefault();\n        } else {\n            // Otherwise, leave to browser but check afterwards whether it has\n            // left behind an empty inline tag.\n            self.setSelection(range);\n            setTimeout(() => {\n                afterDelete(self);\n            }, 0);\n        }\n    }\n};\n\n// ---\n\nexport { Backspace };\n", "import { getNextBlock } from '../node/Block';\nimport {\n    fixContainer,\n    mergeWithBlock,\n    mergeContainers,\n} from '../node/MergeSplit';\nimport { detach } from '../node/Node';\nimport {\n    rangeDoesEndAtBlockBoundary,\n    getStartBlockOfRange,\n} from '../range/Block';\nimport {\n    moveRangeBoundariesUpTree,\n    moveRangeBoundariesDownTree,\n} from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport { afterDelete, detachUneditableNode } from './KeyHelpers';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\nconst Delete = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    let current: Node | null;\n    let next: Node | null;\n    let originalRange: Range;\n    let cursorContainer: Node;\n    let cursorOffset: number;\n    let nodeAfterCursor: Node;\n    self._removeZWS();\n    // Record undo checkpoint.\n    self.saveUndoState(range);\n    // If not collapsed, delete contents\n    if (!range.collapsed) {\n        event.preventDefault();\n        deleteContentsOfRange(range, root);\n        afterDelete(self, range);\n        // If at end of block, merge next into this block\n    } else if (rangeDoesEndAtBlockBoundary(range, root)) {\n        event.preventDefault();\n        current = getStartBlockOfRange(range, root);\n        if (!current) {\n            return;\n        }\n        // In case inline data has somehow got between blocks.\n        fixContainer(current.parentNode!, root);\n        // Now get next block\n        next = getNextBlock(current, root);\n        // Must not be at the very end of the text area.\n        if (next) {\n            // If not editable, just delete whole block.\n            if (!(next as HTMLElement).isContentEditable) {\n                detachUneditableNode(next, root);\n                return;\n            }\n            // Otherwise merge.\n            mergeWithBlock(current, next, range, root);\n            // If deleted line between containers, merge newly adjacent\n            // containers.\n            next = current.parentNode!;\n            while (next !== root && !next.nextSibling) {\n                next = next.parentNode!;\n            }\n            if (next !== root && (next = next.nextSibling)) {\n                mergeContainers(next, root);\n            }\n            self.setSelection(range);\n            self._updatePath(range, true);\n        }\n        // Otherwise, leave to browser but check afterwards whether it has\n        // left behind an empty inline tag.\n    } else {\n        // But first check if the cursor is just before an IMG tag. If so,\n        // delete it ourselves, because the browser won't if it is not\n        // inline.\n        originalRange = range.cloneRange();\n        moveRangeBoundariesUpTree(range, root, root, root);\n        cursorContainer = range.endContainer;\n        cursorOffset = range.endOffset;\n        if (cursorContainer instanceof Element) {\n            nodeAfterCursor = cursorContainer.childNodes[cursorOffset];\n            if (nodeAfterCursor && nodeAfterCursor.nodeName === 'IMG') {\n                event.preventDefault();\n                detach(nodeAfterCursor);\n                moveRangeBoundariesDownTree(range);\n                afterDelete(self, range);\n                return;\n            }\n        }\n        self.setSelection(originalRange);\n        setTimeout(() => {\n            afterDelete(self);\n        }, 0);\n    }\n};\n\n// ---\n\nexport { Delete };\n", "import {\n    rangeDoesStartAtBlockBoundary,\n    getStartBlockOfRange,\n} from '../range/Block';\nimport { getNearest } from '../node/Node';\n\nimport type { Squire } from '../Editor';\n\n// ---\n\nconst Tab = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    self._removeZWS();\n    // If no selection and at start of block\n    if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) {\n        let node: Node = getStartBlockOfRange(range, root)!;\n        // Iterate through the block's parents\n        let parent: Node | null;\n        while ((parent = node.parentNode)) {\n            // If we find a UL or OL (so are in a list, node must be an LI)\n            if (parent.nodeName === 'UL' || parent.nodeName === 'OL') {\n                // Then increase the list level\n                event.preventDefault();\n                self.increaseListLevel(range);\n                break;\n            }\n            node = parent;\n        }\n    }\n};\n\nconst ShiftTab = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    const root = self._root;\n    self._removeZWS();\n    // If no selection and at start of block\n    if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) {\n        // Break list\n        const node = range.startContainer;\n        if (getNearest(node, root, 'UL') || getNearest(node, root, 'OL')) {\n            event.preventDefault();\n            self.decreaseListLevel(range);\n        }\n    }\n};\n\n// ---\n\nexport { Tab, ShiftTab };\n", "import { detach, getLength } from '../node/Node';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\n\nimport type { Squire } from '../Editor';\nimport { linkifyText } from './KeyHelpers';\nimport {\n    getStartBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n} from '../range/Block';\nimport { SHOW_TEXT, TreeIterator } from '../node/TreeIterator';\nimport { ZWS } from '../Constants';\n\n// ---\n\nconst Space = (self: Squire, event: KeyboardEvent, range: Range): void => {\n    let node: Node | null;\n    const root = self._root;\n    self._recordUndoState(range);\n    self._getRangeAndRemoveBookmark(range);\n\n    // Delete the selection if not collapsed\n    if (!range.collapsed) {\n        deleteContentsOfRange(range, root);\n        self._ensureBottomLine();\n        self.setSelection(range);\n        self._updatePath(range, true);\n    } else if (rangeDoesEndAtBlockBoundary(range, root)) {\n        const block = getStartBlockOfRange(range, root);\n        if (block && block.nodeName !== 'PRE') {\n            const text = block.textContent?.trimEnd().replace(ZWS, '');\n            if (text === '*' || text === '1.') {\n                event.preventDefault();\n                self.insertPlainText(' ', false);\n                self._docWasChanged();\n                self.saveUndoState(range);\n                const walker = new TreeIterator<Text>(block, SHOW_TEXT);\n                let textNode: Text | null;\n                while ((textNode = walker.nextNode())) {\n                    detach(textNode);\n                }\n                if (text === '*') {\n                    self.makeUnorderedList();\n                } else {\n                    self.makeOrderedList();\n                }\n                return;\n            }\n        }\n    }\n\n    // If the cursor is at the end of a link (<a>foo|</a>) then move it\n    // outside of the link (<a>foo</a>|) so that the space is not part of\n    // the link text.\n    node = range.endContainer;\n    if (range.endOffset === getLength(node)) {\n        do {\n            if (node.nodeName === 'A') {\n                range.setStartAfter(node);\n                break;\n            }\n        } while (\n            !node.nextSibling &&\n            (node = node.parentNode) &&\n            node !== root\n        );\n    }\n\n    // Linkify text\n    if (self._config.addLinks) {\n        const linkRange = range.cloneRange();\n        moveRangeBoundariesDownTree(linkRange);\n        const textNode = linkRange.startContainer as Text;\n        const offset = linkRange.startOffset;\n        setTimeout(() => {\n            linkifyText(self, textNode, offset);\n        }, 0);\n    }\n\n    self.setSelection(range);\n};\n\n// ---\n\nexport { Space };\n", "import {\n    isMac,\n    isWin,\n    isIOS,\n    ctrlKey,\n    supportsInputEvents,\n} from '../Constants';\nimport { deleteContentsOfRange } from '../range/InsertDelete';\nimport type { Squire } from '../Editor';\nimport { Enter } from './Enter';\nimport { Backspace } from './Backspace';\nimport { Delete } from './Delete';\nimport { ShiftTab, Tab } from './Tab';\nimport { Space } from './Space';\nimport { rangeDoesEndAtBlockBoundary } from '../range/Block';\nimport { moveRangeBoundariesDownTree } from '../range/Boundaries';\n\n// ---\n\nconst _onKey = function (this: Squire, event: KeyboardEvent): void {\n    // Ignore key events where event.isComposing, to stop us from blatting\n    // Kana-Kanji conversion\n    if (event.defaultPrevented || event.isComposing) {\n        return;\n    }\n\n    // We need to apply the Backspace/delete handlers regardless of\n    // control key modifiers.\n    let key = event.key;\n    let modifiers = '';\n    const code = event.code;\n    // If pressing a number key + Shift, make sure we handle it as the number\n    // key and not whatever different character the shift might turn it into.\n    if (/^Digit\\d$/.test(code)) {\n        key = code.slice(-1);\n    }\n    if (key !== 'Backspace' && key !== 'Delete') {\n        if (event.altKey) {\n            modifiers += 'Alt-';\n        }\n        if (event.ctrlKey) {\n            modifiers += 'Ctrl-';\n        }\n        if (event.metaKey) {\n            modifiers += 'Meta-';\n        }\n        if (event.shiftKey) {\n            modifiers += 'Shift-';\n        }\n    }\n    // However, on Windows, Shift-Delete is apparently \"cut\" (WTF right?), so\n    // we want to let the browser handle Shift-Delete in this situation.\n    if (isWin && event.shiftKey && key === 'Delete') {\n        modifiers += 'Shift-';\n    }\n    key = modifiers + key;\n\n    const range: Range = this.getSelection();\n    if (this._keyHandlers[key]) {\n        this._keyHandlers[key](this, event, range);\n    } else if (\n        !range.collapsed &&\n        !event.ctrlKey &&\n        !event.metaKey &&\n        key.length === 1\n    ) {\n        // Record undo checkpoint.\n        this.saveUndoState(range);\n        // Delete the selection\n        deleteContentsOfRange(range, this._root);\n        this._ensureBottomLine();\n        this.setSelection(range);\n        this._updatePath(range, true);\n    }\n};\n\n// ---\n\ntype KeyHandler = (self: Squire, event: KeyboardEvent, range: Range) => void;\n\nconst keyHandlers: Record<string, KeyHandler> = {\n    'Backspace': Backspace,\n    'Delete': Delete,\n    'Tab': Tab,\n    'Shift-Tab': ShiftTab,\n    ' ': Space,\n    'ArrowLeft'(self: Squire): void {\n        self._removeZWS();\n    },\n    'ArrowRight'(self: Squire, event: KeyboardEvent, range: Range): void {\n        self._removeZWS();\n        // Allow right arrow to always break out of <code> block.\n        const root = self.getRoot();\n        if (rangeDoesEndAtBlockBoundary(range, root)) {\n            moveRangeBoundariesDownTree(range);\n            let node: Node | null = range.endContainer;\n            do {\n                if (node.nodeName === 'CODE') {\n                    let next = node.nextSibling;\n                    if (!(next instanceof Text)) {\n                        const textNode = document.createTextNode('\u00A0'); // nbsp\n                        node.parentNode!.insertBefore(textNode, next);\n                        next = textNode;\n                    }\n                    range.setStart(next, 1);\n                    self.setSelection(range);\n                    event.preventDefault();\n                    break;\n                }\n            } while (\n                !node.nextSibling &&\n                (node = node.parentNode) &&\n                node !== root\n            );\n        }\n    },\n};\n\nif (!supportsInputEvents) {\n    keyHandlers.Enter = Enter;\n    keyHandlers['Shift-Enter'] = Enter;\n}\n\n// System standard for page up/down on Mac/iOS is to just scroll, not move the\n// cursor. On Linux/Windows, it should move the cursor, but some browsers don't\n// implement this natively. Override to support it.\nif (!isMac && !isIOS) {\n    keyHandlers.PageUp = (self: Squire) => {\n        self.moveCursorToStart();\n    };\n    keyHandlers.PageDown = (self: Squire) => {\n        self.moveCursorToEnd();\n    };\n}\n\n// ---\n\nconst mapKeyToFormat = (\n    tag: string,\n    remove?: { tag: string } | null,\n): KeyHandler => {\n    remove = remove || null;\n    return (self: Squire, event: Event) => {\n        event.preventDefault();\n        const range = self.getSelection();\n        if (self.hasFormat(tag, null, range)) {\n            self.changeFormat(null, { tag }, range);\n        } else {\n            self.changeFormat({ tag }, remove, range);\n        }\n    };\n};\n\nkeyHandlers[ctrlKey + 'b'] = mapKeyToFormat('B');\nkeyHandlers[ctrlKey + 'i'] = mapKeyToFormat('I');\nkeyHandlers[ctrlKey + 'u'] = mapKeyToFormat('U');\nkeyHandlers[ctrlKey + 'Shift-7'] = mapKeyToFormat('S');\nkeyHandlers[ctrlKey + 'Shift-5'] = mapKeyToFormat('SUB', { tag: 'SUP' });\nkeyHandlers[ctrlKey + 'Shift-6'] = mapKeyToFormat('SUP', { tag: 'SUB' });\n\nkeyHandlers[ctrlKey + 'Shift-8'] = (\n    self: Squire,\n    event: KeyboardEvent,\n): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (!/(?:^|>)UL/.test(path)) {\n        self.makeUnorderedList();\n    } else {\n        self.removeList();\n    }\n};\nkeyHandlers[ctrlKey + 'Shift-9'] = (\n    self: Squire,\n    event: KeyboardEvent,\n): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (!/(?:^|>)OL/.test(path)) {\n        self.makeOrderedList();\n    } else {\n        self.removeList();\n    }\n};\n\nkeyHandlers[ctrlKey + '['] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) {\n        self.decreaseQuoteLevel();\n    } else {\n        self.decreaseListLevel();\n    }\n};\nkeyHandlers[ctrlKey + ']'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    const path = self.getPath();\n    if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) {\n        self.increaseQuoteLevel();\n    } else {\n        self.increaseListLevel();\n    }\n};\n\nkeyHandlers[ctrlKey + 'd'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    self.toggleCode();\n};\n\nkeyHandlers[ctrlKey + 'z'] = (self: Squire, event: KeyboardEvent): void => {\n    event.preventDefault();\n    self.undo();\n};\nkeyHandlers[ctrlKey + 'y'] =\n    // Depending on platform, the Shift may cause the key to come through as\n    // upper case, but sometimes not. Just add both as shortcuts \u2014 the browser\n    // will only ever fire one or the other.\n    keyHandlers[ctrlKey + 'Shift-z'] =\n    keyHandlers[ctrlKey + 'Shift-Z'] =\n        (self: Squire, event: KeyboardEvent): void => {\n            event.preventDefault();\n            self.redo();\n        };\n\nexport { _onKey, keyHandlers };\n", "import {\n    TreeIterator,\n    SHOW_TEXT,\n    SHOW_ELEMENT_OR_TEXT,\n} from './node/TreeIterator';\nimport {\n    createElement,\n    detach,\n    empty,\n    getNearest,\n    hasTagAttributes,\n    replaceWith,\n} from './node/Node';\nimport {\n    isLeaf,\n    isInline,\n    resetNodeCategoryCache,\n    isContainer,\n    isBlock,\n} from './node/Category';\nimport { isLineBreak, removeZWS } from './node/Whitespace';\nimport {\n    moveRangeBoundariesDownTree,\n    isNodeContainedInRange,\n    moveRangeBoundaryOutOf,\n    moveRangeBoundariesUpTree,\n} from './range/Boundaries';\nimport {\n    createRange,\n    deleteContentsOfRange,\n    extractContentsOfRange,\n    insertNodeInRange,\n    insertTreeFragmentIntoRange,\n} from './range/InsertDelete';\nimport {\n    fixContainer,\n    fixCursor,\n    mergeContainers,\n    mergeInlines,\n    split,\n} from './node/MergeSplit';\nimport { getBlockWalker, getNextBlock, isEmptyBlock } from './node/Block';\nimport { cleanTree, cleanupBRs, escapeHTML, removeEmptyInlines } from './Clean';\nimport { cantFocusEmptyTextNodes, ZWS } from './Constants';\nimport {\n    expandRangeToBlockBoundaries,\n    getEndBlockOfRange,\n    getStartBlockOfRange,\n    rangeDoesEndAtBlockBoundary,\n    rangeDoesStartAtBlockBoundary,\n} from './range/Block';\nimport {\n    _monitorShiftKey,\n    _onCopy,\n    _onCut,\n    _onDrop,\n    _onPaste,\n} from './Clipboard';\nimport { keyHandlers, _onKey } from './keyboard/KeyHandlers';\nimport { linkifyText } from './keyboard/KeyHelpers';\nimport { getTextContentsOfRange } from './range/Contents';\n\ndeclare const DOMPurify: any;\n\n// ---\n\ntype EventHandler = { handleEvent: (e: Event) => void } | ((e: Event) => void);\n\ntype KeyHandlerFunction = (x: Squire, y: KeyboardEvent, z: Range) => void;\n\ntype TagAttributes = {\n    [key: string]: { [key: string]: string };\n};\n\ninterface SquireConfig {\n    blockTag: string;\n    blockAttributes: null | Record<string, string>;\n    tagAttributes: TagAttributes;\n    classNames: {\n        color: string;\n        fontFamily: string;\n        fontSize: string;\n        highlight: string;\n    };\n    undo: {\n        documentSizeThreshold: number;\n        undoLimit: number;\n    };\n    addLinks: boolean;\n    willCutCopy: null | ((html: string) => string);\n    toPlainText: null | ((html: string) => string);\n    sanitizeToDOMFragment: (html: string, editor: Squire) => DocumentFragment;\n    didError: (x: any) => void;\n}\n\n// ---\n\nclass Squire {\n    _root: HTMLElement;\n    _config: SquireConfig;\n\n    _isFocused: boolean;\n    _lastSelection: Range;\n    _willRestoreSelection: boolean;\n    _mayHaveZWS: boolean;\n\n    _lastAnchorNode: Node | null;\n    _lastFocusNode: Node | null;\n    _path: string;\n\n    _events: Map<string, Array<EventHandler>>;\n\n    _undoIndex: number;\n    _undoStack: Array<string>;\n    _undoStackLength: number;\n    _isInUndoState: boolean;\n    _ignoreChange: boolean;\n    _ignoreAllChanges: boolean;\n\n    _isShiftDown: boolean;\n    _keyHandlers: Record<string, KeyHandlerFunction>;\n\n    _mutation: MutationObserver;\n\n    constructor(root: HTMLElement, config?: Partial<SquireConfig>) {\n        this._root = root;\n\n        this._config = this._makeConfig(config);\n\n        this._isFocused = false;\n        this._lastSelection = createRange(root, 0);\n        this._willRestoreSelection = false;\n        this._mayHaveZWS = false;\n\n        this._lastAnchorNode = null;\n        this._lastFocusNode = null;\n        this._path = '';\n\n        this._events = new Map();\n\n        this._undoIndex = -1;\n        this._undoStack = [];\n        this._undoStackLength = 0;\n        this._isInUndoState = false;\n        this._ignoreChange = false;\n        this._ignoreAllChanges = false;\n\n        // Add event listeners\n        this.addEventListener('selectionchange', this._updatePathOnEvent);\n\n        // On blur, restore focus except if the user taps or clicks to focus a\n        // specific point. Can't actually use click event because focus happens\n        // before click, so use mousedown/touchstart\n        this.addEventListener('blur', this._enableRestoreSelection);\n        this.addEventListener('mousedown', this._disableRestoreSelection);\n        this.addEventListener('touchstart', this._disableRestoreSelection);\n        this.addEventListener('focus', this._restoreSelection);\n\n        // On blur, cleanup any ZWS/empty inlines\n        this.addEventListener('blur', this._removeZWS);\n\n        // Clipboard support\n        this._isShiftDown = false;\n        this.addEventListener('cut', _onCut as (e: Event) => void);\n        this.addEventListener('copy', _onCopy as (e: Event) => void);\n        this.addEventListener('paste', _onPaste as (e: Event) => void);\n        this.addEventListener('drop', _onDrop as (e: Event) => void);\n        this.addEventListener(\n            'keydown',\n            _monitorShiftKey as (e: Event) => void,\n        );\n        this.addEventListener('keyup', _monitorShiftKey as (e: Event) => void);\n\n        // Keyboard support\n        this.addEventListener('keydown', _onKey as (e: Event) => void);\n        this._keyHandlers = Object.create(keyHandlers);\n\n        const mutation = new MutationObserver(() => this._docWasChanged());\n        mutation.observe(root, {\n            childList: true,\n            attributes: true,\n            characterData: true,\n            subtree: true,\n        });\n        this._mutation = mutation;\n\n        // Make it editable\n        root.setAttribute('contenteditable', 'true');\n\n        // Modern browsers let you override their default content editable\n        // handling!\n        this.addEventListener(\n            'beforeinput',\n            this._beforeInput as (e: Event) => void,\n        );\n\n        this.setHTML('');\n    }\n\n    destroy(): void {\n        this._events.forEach((_, type) => {\n            this.removeEventListener(type);\n        });\n\n        this._mutation.disconnect();\n\n        this._undoIndex = -1;\n        this._undoStack = [];\n        this._undoStackLength = 0;\n    }\n\n    _makeConfig(userConfig?: object): SquireConfig {\n        const config = {\n            blockTag: 'DIV',\n            blockAttributes: null,\n            tagAttributes: {},\n            classNames: {\n                color: 'color',\n                fontFamily: 'font',\n                fontSize: 'size',\n                highlight: 'highlight',\n            },\n            undo: {\n                documentSizeThreshold: -1, // -1 means no threshold\n                undoLimit: -1, // -1 means no limit\n            },\n            addLinks: true,\n            willCutCopy: null,\n            toPlainText: null,\n            sanitizeToDOMFragment: (\n                html: string,\n                /* editor: Squire, */\n            ): DocumentFragment => {\n                const frag = DOMPurify.sanitize(html, {\n                    ALLOW_UNKNOWN_PROTOCOLS: true,\n                    WHOLE_DOCUMENT: false,\n                    RETURN_DOM: true,\n                    RETURN_DOM_FRAGMENT: true,\n                    FORCE_BODY: false,\n                });\n                return frag\n                    ? document.importNode(frag, true)\n                    : document.createDocumentFragment();\n            },\n            didError: (error: any): void => console.log(error),\n        };\n        if (userConfig) {\n            Object.assign(config, userConfig);\n            config.blockTag = config.blockTag.toUpperCase();\n        }\n\n        return config;\n    }\n\n    setKeyHandler(key: string, fn: KeyHandlerFunction) {\n        this._keyHandlers[key] = fn;\n        return this;\n    }\n\n    _beforeInput(event: InputEvent): void {\n        switch (event.inputType) {\n            case 'insertLineBreak':\n                event.preventDefault();\n                this.splitBlock(true);\n                break;\n            case 'insertParagraph':\n                event.preventDefault();\n                this.splitBlock(false);\n                break;\n            case 'insertOrderedList':\n                event.preventDefault();\n                this.makeOrderedList();\n                break;\n            case 'insertUnoderedList':\n                event.preventDefault();\n                this.makeUnorderedList();\n                break;\n            case 'historyUndo':\n                event.preventDefault();\n                this.undo();\n                break;\n            case 'historyRedo':\n                event.preventDefault();\n                this.redo();\n                break;\n            case 'formatBold':\n                event.preventDefault();\n                this.bold();\n                break;\n            case 'formaItalic':\n                event.preventDefault();\n                this.italic();\n                break;\n            case 'formatUnderline':\n                event.preventDefault();\n                this.underline();\n                break;\n            case 'formatStrikeThrough':\n                event.preventDefault();\n                this.strikethrough();\n                break;\n            case 'formatSuperscript':\n                event.preventDefault();\n                this.superscript();\n                break;\n            case 'formatSubscript':\n                event.preventDefault();\n                this.subscript();\n                break;\n            case 'formatJustifyFull':\n            case 'formatJustifyCenter':\n            case 'formatJustifyRight':\n            case 'formatJustifyLeft': {\n                event.preventDefault();\n                let alignment = event.inputType.slice(13).toLowerCase();\n                if (alignment === 'full') {\n                    alignment = 'justify';\n                }\n                this.setTextAlignment(alignment);\n                break;\n            }\n            case 'formatRemove':\n                event.preventDefault();\n                this.removeAllFormatting();\n                break;\n            case 'formatSetBlockTextDirection': {\n                event.preventDefault();\n                let dir = event.data;\n                if (dir === 'null') {\n                    dir = null;\n                }\n                this.setTextDirection(dir);\n                break;\n            }\n            case 'formatBackColor':\n                event.preventDefault();\n                this.setHighlightColor(event.data);\n                break;\n            case 'formatFontColor':\n                event.preventDefault();\n                this.setTextColor(event.data);\n                break;\n            case 'formatFontName':\n                event.preventDefault();\n                this.setFontFace(event.data);\n                break;\n        }\n    }\n\n    // --- Events\n\n    handleEvent(event: Event): void {\n        this.fireEvent(event.type, event);\n    }\n\n    fireEvent(type: string, detail?: Event | object): Squire {\n        let handlers = this._events.get(type);\n        // UI code, especially modal views, may be monitoring for focus events\n        // and immediately removing focus. In certain conditions, this can\n        // cause the focus event to fire after the blur event, which can cause\n        // an infinite loop. So we detect whether we're actually\n        // focused/blurred before firing.\n        if (/^(?:focus|blur)/.test(type)) {\n            const isFocused = this._root === document.activeElement;\n            if (type === 'focus') {\n                if (!isFocused || this._isFocused) {\n                    return this;\n                }\n                this._isFocused = true;\n            } else {\n                if (isFocused || !this._isFocused) {\n                    return this;\n                }\n                this._isFocused = false;\n            }\n        }\n        if (handlers) {\n            const event: Event =\n                detail instanceof Event\n                    ? detail\n                    : new CustomEvent(type, {\n                          detail,\n                      });\n            // Clone handlers array, so any handlers added/removed do not\n            // affect it.\n            handlers = handlers.slice();\n            for (const handler of handlers) {\n                try {\n                    if ('handleEvent' in handler) {\n                        handler.handleEvent(event);\n                    } else {\n                        handler.call(this, event);\n                    }\n                } catch (error) {\n                    this._config.didError(error);\n                }\n            }\n        }\n        return this;\n    }\n\n    /**\n     * Subscribing to these events won't automatically add a listener to the\n     * document node, since these events are fired in a custom manner by the\n     * editor code.\n     */\n    customEvents = new Set([\n        'pathChange',\n        'select',\n        'input',\n        'pasteImage',\n        'undoStateChange',\n    ]);\n\n    addEventListener(type: string, fn: EventHandler): Squire {\n        let handlers = this._events.get(type);\n        let target: Document | HTMLElement = this._root;\n        if (!handlers) {\n            handlers = [];\n            this._events.set(type, handlers);\n            if (!this.customEvents.has(type)) {\n                if (type === 'selectionchange') {\n                    target = document;\n                }\n                target.addEventListener(type, this, true);\n            }\n        }\n        handlers.push(fn);\n        return this;\n    }\n\n    removeEventListener(type: string, fn?: EventHandler): Squire {\n        const handlers = this._events.get(type);\n        let target: Document | HTMLElement = this._root;\n        if (handlers) {\n            if (fn) {\n                let l = handlers.length;\n                while (l--) {\n                    if (handlers[l] === fn) {\n                        handlers.splice(l, 1);\n                    }\n                }\n            } else {\n                handlers.length = 0;\n            }\n            if (!handlers.length) {\n                this._events.delete(type);\n                if (!this.customEvents.has(type)) {\n                    if (type === 'selectionchange') {\n                        target = document;\n                    }\n                    target.removeEventListener(type, this, true);\n                }\n            }\n        }\n        return this;\n    }\n\n    // --- Focus\n\n    focus(): Squire {\n        this._root.focus({ preventScroll: true });\n        return this;\n    }\n\n    blur(): Squire {\n        this._root.blur();\n        return this;\n    }\n\n    // --- Selection and bookmarking\n\n    _enableRestoreSelection(): void {\n        this._willRestoreSelection = true;\n    }\n\n    _disableRestoreSelection(): void {\n        this._willRestoreSelection = false;\n    }\n\n    _restoreSelection() {\n        if (this._willRestoreSelection) {\n            this.setSelection(this._lastSelection);\n        }\n    }\n\n    // ---\n\n    _removeZWS(): void {\n        if (!this._mayHaveZWS) {\n            return;\n        }\n        removeZWS(this._root);\n        this._mayHaveZWS = false;\n    }\n\n    // ---\n\n    startSelectionId = 'squire-selection-start';\n    endSelectionId = 'squire-selection-end';\n\n    _saveRangeToBookmark(range: Range): void {\n        let startNode = createElement('INPUT', {\n            id: this.startSelectionId,\n            type: 'hidden',\n        });\n        let endNode = createElement('INPUT', {\n            id: this.endSelectionId,\n            type: 'hidden',\n        });\n        let temp: HTMLElement;\n\n        insertNodeInRange(range, startNode);\n        range.collapse(false);\n        insertNodeInRange(range, endNode);\n\n        // In a collapsed range, the start is sometimes inserted after the end!\n        if (\n            startNode.compareDocumentPosition(endNode) &\n            Node.DOCUMENT_POSITION_PRECEDING\n        ) {\n            startNode.id = this.endSelectionId;\n            endNode.id = this.startSelectionId;\n            temp = startNode;\n            startNode = endNode;\n            endNode = temp;\n        }\n\n        range.setStartAfter(startNode);\n        range.setEndBefore(endNode);\n    }\n\n    _getRangeAndRemoveBookmark(range?: Range): Range | null {\n        const root = this._root;\n        const start = root.querySelector('#' + this.startSelectionId);\n        const end = root.querySelector('#' + this.endSelectionId);\n\n        if (start && end) {\n            let startContainer: Node = start.parentNode!;\n            let endContainer: Node = end.parentNode!;\n            const startOffset = Array.from(startContainer.childNodes).indexOf(\n                start,\n            );\n            let endOffset = Array.from(endContainer.childNodes).indexOf(end);\n\n            if (startContainer === endContainer) {\n                endOffset -= 1;\n            }\n\n            start.remove();\n            end.remove();\n\n            if (!range) {\n                range = document.createRange();\n            }\n            range.setStart(startContainer, startOffset);\n            range.setEnd(endContainer, endOffset);\n\n            // Merge any text nodes we split\n            mergeInlines(startContainer, range);\n            if (startContainer !== endContainer) {\n                mergeInlines(endContainer, range);\n            }\n\n            // If we didn't split a text node, we should move into any adjacent\n            // text node to current selection point\n            if (range.collapsed) {\n                startContainer = range.startContainer;\n                if (startContainer instanceof Text) {\n                    endContainer = startContainer.childNodes[range.startOffset];\n                    if (!endContainer || !(endContainer instanceof Text)) {\n                        endContainer =\n                            startContainer.childNodes[range.startOffset - 1];\n                    }\n                    if (endContainer && endContainer instanceof Text) {\n                        range.setStart(endContainer, 0);\n                        range.collapse(true);\n                    }\n                }\n            }\n        }\n        return range || null;\n    }\n\n    getSelection(): Range {\n        const selection = window.getSelection();\n        const root = this._root;\n        let range: Range | null = null;\n        // If not focused, always rely on cached selection; another function may\n        // have set it but the DOM is not modified until focus again\n        if (this._isFocused && selection && selection.rangeCount) {\n            range = selection.getRangeAt(0).cloneRange();\n            const startContainer = range.startContainer;\n            const endContainer = range.endContainer;\n            // FF can return the selection as being inside an <img>. WTF?\n            if (startContainer && isLeaf(startContainer)) {\n                range.setStartBefore(startContainer);\n            }\n            if (endContainer && isLeaf(endContainer)) {\n                range.setEndBefore(endContainer);\n            }\n        }\n        if (range && root.contains(range.commonAncestorContainer)) {\n            this._lastSelection = range;\n        } else {\n            range = this._lastSelection;\n            // Check the editor is in the live document; if not, the range has\n            // probably been rewritten by the browser and is bogus\n            if (!document.contains(range.commonAncestorContainer)) {\n                range = null;\n            }\n        }\n        if (!range) {\n            range = createRange(root.firstElementChild || root, 0);\n        }\n        return range;\n    }\n\n    setSelection(range: Range): Squire {\n        this._lastSelection = range;\n        // If we're setting selection, that automatically, and synchronously,\n        // triggers a focus event. So just store the selection and mark it as\n        // needing restore on focus.\n        if (!this._isFocused) {\n            this._enableRestoreSelection();\n        } else {\n            const selection = window.getSelection();\n            if (selection) {\n                if ('setBaseAndExtent' in Selection.prototype) {\n                    selection.setBaseAndExtent(\n                        range.startContainer,\n                        range.startOffset,\n                        range.endContainer,\n                        range.endOffset,\n                    );\n                } else {\n                    selection.removeAllRanges();\n                    selection.addRange(range);\n                }\n            }\n        }\n        return this;\n    }\n\n    // ---\n\n    _moveCursorTo(toStart: boolean): Squire {\n        const root = this._root;\n        const range = createRange(root, toStart ? 0 : root.childNodes.length);\n        moveRangeBoundariesDownTree(range);\n        this.setSelection(range);\n        return this;\n    }\n\n    moveCursorToStart(): Squire {\n        return this._moveCursorTo(true);\n    }\n\n    moveCursorToEnd(): Squire {\n        return this._moveCursorTo(false);\n    }\n\n    // ---\n\n    getCursorPosition(): DOMRect {\n        const range = this.getSelection();\n        let rect = range.getBoundingClientRect();\n        // If the range is outside of the viewport, some browsers at least\n        // will return 0 for all the values; need to get a DOM node to find\n        // the position instead.\n        if (rect && !rect.top) {\n            this._ignoreChange = true;\n            const node = createElement('SPAN');\n            node.textContent = ZWS;\n            insertNodeInRange(range, node);\n            rect = node.getBoundingClientRect();\n            const parent = node.parentNode!;\n            parent.removeChild(node);\n            mergeInlines(parent, range);\n        }\n        return rect;\n    }\n\n    // --- Path\n\n    getPath(): string {\n        return this._path;\n    }\n\n    _updatePathOnEvent(): void {\n        if (this._isFocused) {\n            this._updatePath(this.getSelection());\n        }\n    }\n\n    _updatePath(range: Range, force?: boolean): void {\n        const anchor = range.startContainer;\n        const focus = range.endContainer;\n        let newPath: string;\n        if (\n            force ||\n            anchor !== this._lastAnchorNode ||\n            focus !== this._lastFocusNode\n        ) {\n            this._lastAnchorNode = anchor;\n            this._lastFocusNode = focus;\n            newPath =\n                anchor && focus\n                    ? anchor === focus\n                        ? this._getPath(focus)\n                        : '(selection)'\n                    : '';\n            if (this._path !== newPath) {\n                this._path = newPath;\n                this.fireEvent('pathChange', {\n                    path: newPath,\n                });\n            }\n        }\n        this.fireEvent(range.collapsed ? 'cursor' : 'select', {\n            range: range,\n        });\n    }\n\n    _getPath(node: Node) {\n        const root = this._root;\n        const config = this._config;\n        let path = '';\n        if (node && node !== root) {\n            const parent = node.parentNode;\n            path = parent ? this._getPath(parent) : '';\n            if (node instanceof HTMLElement) {\n                const id = node.id;\n                const classList = node.classList;\n                const classNames = Array.from(classList).sort();\n                const dir = node.dir;\n                const styleNames = config.classNames;\n                path += (path ? '>' : '') + node.nodeName;\n                if (id) {\n                    path += '#' + id;\n                }\n                if (classNames.length) {\n                    path += '.';\n                    path += classNames.join('.');\n                }\n                if (dir) {\n                    path += '[dir=' + dir + ']';\n                }\n                if (classList.contains(styleNames.highlight)) {\n                    path +=\n                        '[backgroundColor=' +\n                        node.style.backgroundColor.replace(/ /g, '') +\n                        ']';\n                }\n                if (classList.contains(styleNames.color)) {\n                    path +=\n                        '[color=' + node.style.color.replace(/ /g, '') + ']';\n                }\n                if (classList.contains(styleNames.fontFamily)) {\n                    path +=\n                        '[fontFamily=' +\n                        node.style.fontFamily.replace(/ /g, '') +\n                        ']';\n                }\n                if (classList.contains(styleNames.fontSize)) {\n                    path += '[fontSize=' + node.style.fontSize + ']';\n                }\n            }\n        }\n        return path;\n    }\n\n    // --- History\n\n    modifyDocument(modificationFn: () => void): Squire {\n        const mutation = this._mutation;\n        if (mutation) {\n            if (mutation.takeRecords().length) {\n                this._docWasChanged();\n            }\n            mutation.disconnect();\n        }\n\n        this._ignoreAllChanges = true;\n        modificationFn();\n        this._ignoreAllChanges = false;\n\n        if (mutation) {\n            mutation.observe(this._root, {\n                childList: true,\n                attributes: true,\n                characterData: true,\n                subtree: true,\n            });\n            this._ignoreChange = false;\n        }\n\n        return this;\n    }\n\n    _docWasChanged(): void {\n        resetNodeCategoryCache();\n        this._mayHaveZWS = true;\n        if (this._ignoreAllChanges) {\n            return;\n        }\n\n        if (this._ignoreChange) {\n            this._ignoreChange = false;\n            return;\n        }\n        if (this._isInUndoState) {\n            this._isInUndoState = false;\n            this.fireEvent('undoStateChange', {\n                canUndo: true,\n                canRedo: false,\n            });\n        }\n        this.fireEvent('input');\n    }\n\n    /**\n     * Leaves bookmark.\n     */\n    _recordUndoState(range: Range, replace?: boolean): Squire {\n        const isInUndoState = this._isInUndoState;\n        if (!isInUndoState || replace) {\n            // Advance pointer to new position\n            let undoIndex = this._undoIndex + 1;\n            const undoStack = this._undoStack;\n            const undoConfig = this._config.undo;\n            const undoThreshold = undoConfig.documentSizeThreshold;\n            const undoLimit = undoConfig.undoLimit;\n\n            // Truncate stack if longer (i.e. if has been previously undone)\n            if (undoIndex < this._undoStackLength) {\n                undoStack.length = this._undoStackLength = undoIndex;\n            }\n\n            // Add bookmark\n            if (range) {\n                this._saveRangeToBookmark(range);\n            }\n\n            // Don't record if we're already in an undo state\n            if (isInUndoState) {\n                return this;\n            }\n\n            // Get data\n            const html = this._getRawHTML();\n\n            // If this document is above the configured size threshold,\n            // limit the number of saved undo states.\n            // Threshold is in bytes, JS uses 2 bytes per character\n            if (replace) {\n                undoIndex -= 1;\n            }\n            if (undoThreshold > -1 && html.length * 2 > undoThreshold) {\n                if (undoLimit > -1 && undoIndex > undoLimit) {\n                    undoStack.splice(0, undoIndex - undoLimit);\n                    undoIndex = undoLimit;\n                    this._undoStackLength = undoLimit;\n                }\n            }\n\n            // Save data\n            undoStack[undoIndex] = html;\n            this._undoIndex = undoIndex;\n            this._undoStackLength += 1;\n            this._isInUndoState = true;\n        }\n        return this;\n    }\n\n    saveUndoState(range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        this._recordUndoState(range, this._isInUndoState);\n        this._getRangeAndRemoveBookmark(range);\n\n        return this;\n    }\n\n    undo(): Squire {\n        // Sanity check: must not be at beginning of the history stack\n        if (this._undoIndex !== 0 || !this._isInUndoState) {\n            // Make sure any changes since last checkpoint are saved.\n            this._recordUndoState(this.getSelection(), false);\n            this._undoIndex -= 1;\n            this._setRawHTML(this._undoStack[this._undoIndex]);\n            const range = this._getRangeAndRemoveBookmark();\n            if (range) {\n                this.setSelection(range);\n            }\n            this._isInUndoState = true;\n            this.fireEvent('undoStateChange', {\n                canUndo: this._undoIndex !== 0,\n                canRedo: true,\n            });\n            this.fireEvent('input');\n        }\n        return this.focus();\n    }\n\n    redo(): Squire {\n        // Sanity check: must not be at end of stack and must be in an undo\n        // state.\n        const undoIndex = this._undoIndex;\n        const undoStackLength = this._undoStackLength;\n        if (undoIndex + 1 < undoStackLength && this._isInUndoState) {\n            this._undoIndex += 1;\n            this._setRawHTML(this._undoStack[this._undoIndex]);\n            const range = this._getRangeAndRemoveBookmark();\n            if (range) {\n                this.setSelection(range);\n            }\n            this.fireEvent('undoStateChange', {\n                canUndo: true,\n                canRedo: undoIndex + 2 < undoStackLength,\n            });\n            this.fireEvent('input');\n        }\n        return this.focus();\n    }\n\n    // --- Get and set data\n\n    getRoot(): HTMLElement {\n        return this._root;\n    }\n\n    _getRawHTML(): string {\n        return this._root.innerHTML;\n    }\n\n    _setRawHTML(html: string): Squire {\n        const root = this._root;\n        root.innerHTML = html;\n\n        let node: Element | null = root;\n        const child = node.firstChild;\n        if (!child || child.nodeName === 'BR') {\n            const block = this.createDefaultBlock();\n            if (child) {\n                node.replaceChild(block, child);\n            } else {\n                node.appendChild(block);\n            }\n        } else {\n            while ((node = getNextBlock(node, root))) {\n                fixCursor(node);\n            }\n        }\n\n        this._ignoreChange = true;\n\n        return this;\n    }\n\n    getHTML(withBookmark?: boolean): string {\n        let range: Range | undefined;\n        if (withBookmark) {\n            range = this.getSelection();\n            this._saveRangeToBookmark(range);\n        }\n        const html = this._getRawHTML().replace(/\\u200B/g, '');\n        if (withBookmark) {\n            this._getRangeAndRemoveBookmark(range);\n        }\n        return html;\n    }\n\n    setHTML(html: string): Squire {\n        // Parse HTML into DOM tree\n        const frag = this._config.sanitizeToDOMFragment(html, this);\n        const root = this._root;\n\n        // Fixup DOM tree\n        cleanTree(frag, this._config);\n        cleanupBRs(frag, root, false);\n        fixContainer(frag, root);\n\n        // Fix cursor\n        let node: DocumentFragment | HTMLElement | null = frag;\n        let child = node.firstChild;\n        if (!child || child.nodeName === 'BR') {\n            const block = this.createDefaultBlock();\n            if (child) {\n                node.replaceChild(block, child);\n            } else {\n                node.appendChild(block);\n            }\n        } else {\n            while ((node = getNextBlock(node, root))) {\n                fixCursor(node);\n            }\n        }\n\n        // Don't fire an input event\n        this._ignoreChange = true;\n\n        // Remove existing root children and insert new content\n        while ((child = root.lastChild)) {\n            root.removeChild(child);\n        }\n        root.appendChild(frag);\n\n        // Reset the undo stack\n        this._undoIndex = -1;\n        this._undoStack.length = 0;\n        this._undoStackLength = 0;\n        this._isInUndoState = false;\n\n        // Record undo state\n        const range =\n            this._getRangeAndRemoveBookmark() ||\n            createRange(root.firstElementChild || root, 0);\n        this.saveUndoState(range);\n\n        // Set inital selection\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    /**\n     * Insert HTML at the cursor location. If the selection is not collapsed\n     * insertTreeFragmentIntoRange will delete the selection so that it is\n     * replaced by the html being inserted.\n     */\n    insertHTML(html: string, isPaste?: boolean): Squire {\n        // Parse\n        const config = this._config;\n        let frag = config.sanitizeToDOMFragment(html, this);\n\n        // Record undo checkpoint\n        const range = this.getSelection();\n        this.saveUndoState(range);\n\n        try {\n            const root = this._root;\n\n            if (config.addLinks) {\n                this.addDetectedLinks(frag, frag);\n            }\n            cleanTree(frag, this._config);\n            cleanupBRs(frag, root, false);\n            removeEmptyInlines(frag);\n            frag.normalize();\n\n            let node: HTMLElement | DocumentFragment | null = frag;\n            while ((node = getNextBlock(node, frag))) {\n                fixCursor(node);\n            }\n\n            let doInsert = true;\n            if (isPaste) {\n                const event = new CustomEvent('willPaste', {\n                    cancelable: true,\n                    detail: {\n                        fragment: frag,\n                    },\n                });\n                this.fireEvent('willPaste', event);\n                frag = event.detail.fragment;\n                doInsert = !event.defaultPrevented;\n            }\n\n            if (doInsert) {\n                insertTreeFragmentIntoRange(range, frag, root);\n                range.collapse(false);\n\n                // After inserting the fragment, check whether the cursor is\n                // inside an <a> element and if so if there is an equivalent\n                // cursor position after the <a> element. If there is, move it\n                // there.\n                moveRangeBoundaryOutOf(range, 'A', root);\n\n                this._ensureBottomLine();\n            }\n\n            this.setSelection(range);\n            this._updatePath(range, true);\n            // Safari sometimes loses focus after paste. Weird.\n            if (isPaste) {\n                this.focus();\n            }\n        } catch (error) {\n            this._config.didError(error);\n        }\n        return this;\n    }\n\n    insertElement(el: Element, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        range.collapse(true);\n        if (isInline(el)) {\n            insertNodeInRange(range, el);\n            range.setStartAfter(el);\n        } else {\n            // Get containing block node.\n            const root = this._root;\n            const startNode: HTMLElement | null = getStartBlockOfRange(\n                range,\n                root,\n            );\n            let splitNode: Element | Node = startNode || root;\n\n            let nodeAfterSplit: Node | null = null;\n            // While at end of container node, move up DOM tree.\n            while (splitNode !== root && !splitNode.nextSibling) {\n                splitNode = splitNode.parentNode!;\n            }\n            // If in the middle of a container node, split up to root.\n            if (splitNode !== root) {\n                const parent = splitNode.parentNode!;\n                nodeAfterSplit = split(\n                    parent,\n                    splitNode.nextSibling,\n                    root,\n                    root,\n                ) as Node;\n            }\n\n            // If the startNode was empty remove it so that we don't end up\n            // with two blank lines.\n            if (startNode && isEmptyBlock(startNode)) {\n                detach(startNode);\n            }\n\n            // Insert element and blank line.\n            root.insertBefore(el, nodeAfterSplit);\n            const blankLine = this.createDefaultBlock();\n            root.insertBefore(blankLine, nodeAfterSplit);\n\n            // Move cursor to blank line after inserted element.\n            range.setStart(blankLine, 0);\n            range.setEnd(blankLine, 0);\n            moveRangeBoundariesDownTree(range);\n        }\n        this.focus();\n        this.setSelection(range);\n        this._updatePath(range);\n\n        return this;\n    }\n\n    insertImage(\n        src: string,\n        attributes: Record<string, string>,\n    ): HTMLImageElement {\n        const img = createElement(\n            'IMG',\n            Object.assign(\n                {\n                    src: src,\n                },\n                attributes,\n            ),\n        ) as HTMLImageElement;\n        this.insertElement(img);\n        return img;\n    }\n\n    insertPlainText(plainText: string, isPaste: boolean): Squire {\n        const range = this.getSelection();\n        if (\n            range.collapsed &&\n            getNearest(range.startContainer, this._root, 'PRE')\n        ) {\n            const startContainer: Node = range.startContainer;\n            let offset = range.startOffset;\n            let textNode: Text;\n            if (!startContainer || !(startContainer instanceof Text)) {\n                const text = document.createTextNode('');\n                startContainer.insertBefore(\n                    text,\n                    startContainer.childNodes[offset],\n                );\n                textNode = text;\n                offset = 0;\n            } else {\n                textNode = startContainer;\n            }\n            let doInsert = true;\n            if (isPaste) {\n                const event = new CustomEvent('willPaste', {\n                    cancelable: true,\n                    detail: {\n                        text: plainText,\n                    },\n                });\n                this.fireEvent('willPaste', event);\n                plainText = event.detail.text;\n                doInsert = !event.defaultPrevented;\n            }\n\n            if (doInsert) {\n                textNode.insertData(offset, plainText);\n                range.setStart(textNode, offset + plainText.length);\n                range.collapse(true);\n            }\n            this.setSelection(range);\n            return this;\n        }\n        const lines = plainText.split('\\n');\n        const config = this._config;\n        const tag = config.blockTag;\n        const attributes = config.blockAttributes;\n        const closeBlock = '</' + tag + '>';\n        let openBlock = '<' + tag;\n\n        for (const attr in attributes) {\n            openBlock += ' ' + attr + '=\"' + escapeHTML(attributes[attr]) + '\"';\n        }\n        openBlock += '>';\n\n        for (let i = 0, l = lines.length; i < l; i += 1) {\n            let line = lines[i];\n            line = escapeHTML(line).replace(/ (?=(?: |$))/g, '&nbsp;');\n            // We don't wrap the first line in the block, so if it gets inserted\n            // into a blank line it keeps that line's formatting.\n            // Wrap each line in <div></div>\n            if (i) {\n                line = openBlock + (line || '<BR>') + closeBlock;\n            }\n            lines[i] = line;\n        }\n        return this.insertHTML(lines.join(''), isPaste);\n    }\n\n    getSelectedText(range?: Range): string {\n        return getTextContentsOfRange(range || this.getSelection());\n    }\n\n    // --- Inline formatting\n\n    /**\n     * Extracts the font-family and font-size (if any) of the element\n     * holding the cursor. If there's a selection, returns an empty object.\n     */\n    getFontInfo(range?: Range): Record<string, string | undefined> {\n        const fontInfo = {\n            color: undefined,\n            backgroundColor: undefined,\n            fontFamily: undefined,\n            fontSize: undefined,\n        } as Record<string, string | undefined>;\n\n        if (!range) {\n            range = this.getSelection();\n        }\n        moveRangeBoundariesDownTree(range);\n\n        let seenAttributes = 0;\n        let element: Node | null = range.commonAncestorContainer;\n        if (range.collapsed || element instanceof Text) {\n            if (element instanceof Text) {\n                element = element.parentNode!;\n            }\n            while (seenAttributes < 4 && element) {\n                const style = (element as HTMLElement).style;\n                if (style) {\n                    const color = style.color;\n                    if (!fontInfo.color && color) {\n                        fontInfo.color = color;\n                        seenAttributes += 1;\n                    }\n                    const backgroundColor = style.backgroundColor;\n                    if (!fontInfo.backgroundColor && backgroundColor) {\n                        fontInfo.backgroundColor = backgroundColor;\n                        seenAttributes += 1;\n                    }\n                    const fontFamily = style.fontFamily;\n                    if (!fontInfo.fontFamily && fontFamily) {\n                        fontInfo.fontFamily = fontFamily;\n                        seenAttributes += 1;\n                    }\n                    const fontSize = style.fontSize;\n                    if (!fontInfo.fontSize && fontSize) {\n                        fontInfo.fontSize = fontSize;\n                        seenAttributes += 1;\n                    }\n                }\n                element = element.parentNode;\n            }\n        }\n        return fontInfo;\n    }\n\n    /**\n     * Looks for matching tag and attributes, so won't work if <strong>\n     * instead of <b> etc.\n     */\n    hasFormat(\n        tag: string,\n        attributes?: Record<string, string> | null,\n        range?: Range,\n    ): boolean {\n        // 1. Normalise the arguments and get selection\n        tag = tag.toUpperCase();\n        if (!attributes) {\n            attributes = {};\n        }\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Move range up one level in the DOM tree if at the edge of a text\n        // node, so we don't consider it included when it's not really.\n        if (\n            !range.collapsed &&\n            range.startContainer instanceof Text &&\n            range.startOffset === range.startContainer.length &&\n            range.startContainer.nextSibling\n        ) {\n            range.setStartBefore(range.startContainer.nextSibling);\n        }\n        if (\n            !range.collapsed &&\n            range.endContainer instanceof Text &&\n            range.endOffset === 0 &&\n            range.endContainer.previousSibling\n        ) {\n            range.setEndAfter(range.endContainer.previousSibling);\n        }\n\n        // If the common ancestor is inside the tag we require, we definitely\n        // have the format.\n        const root = this._root;\n        const common = range.commonAncestorContainer;\n        if (getNearest(common, root, tag, attributes)) {\n            return true;\n        }\n\n        // If common ancestor is a text node and doesn't have the format, we\n        // definitely don't have it.\n        if (common instanceof Text) {\n            return false;\n        }\n\n        // Otherwise, check each text node at least partially contained within\n        // the selection and make sure all of them have the format we want.\n        const walker = new TreeIterator<Text>(common, SHOW_TEXT, (node) => {\n            return isNodeContainedInRange(range!, node, true);\n        });\n\n        let seenNode = false;\n        let node: Node | null;\n        while ((node = walker.nextNode())) {\n            if (!getNearest(node, root, tag, attributes)) {\n                return false;\n            }\n            seenNode = true;\n        }\n\n        return seenNode;\n    }\n\n    changeFormat(\n        add: { tag: string; attributes?: Record<string, string> } | null,\n        remove?: { tag: string; attributes?: Record<string, string> } | null,\n        range?: Range,\n        partial?: boolean,\n    ): Squire {\n        // Normalise the arguments and get selection\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Save undo checkpoint\n        this.saveUndoState(range);\n\n        if (remove) {\n            range = this._removeFormat(\n                remove.tag.toUpperCase(),\n                remove.attributes || {},\n                range,\n                partial,\n            );\n        }\n        if (add) {\n            range = this._addFormat(\n                add.tag.toUpperCase(),\n                add.attributes || {},\n                range,\n            );\n        }\n\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    _addFormat(\n        tag: string,\n        attributes: Record<string, string> | null,\n        range: Range,\n    ): Range {\n        // If the range is collapsed we simply insert the node by wrapping\n        // it round the range and focus it.\n        const root = this._root;\n        if (range.collapsed) {\n            const el = fixCursor(createElement(tag, attributes));\n            insertNodeInRange(range, el);\n            const focusNode = el.firstChild || el;\n            // Focus after the ZWS if present\n            const focusOffset =\n                focusNode instanceof Text ? focusNode.length : 0;\n            range.setStart(focusNode, focusOffset);\n            range.collapse(true);\n\n            // Clean up any previous formats that may have been set on this\n            // block that are unused.\n            let block = el;\n            while (isInline(block)) {\n                block = block.parentNode!;\n            }\n            removeZWS(block, el);\n            // Otherwise we find all the textnodes in the range (splitting\n            // partially selected nodes) and if they're not already formatted\n            // correctly we wrap them in the appropriate tag.\n        } else {\n            // Create an iterator to walk over all the text nodes under this\n            // ancestor which are in the range and not already formatted\n            // correctly.\n            //\n            // In Blink/WebKit, empty blocks may have no text nodes, just a\n            // <br>. Therefore we wrap this in the tag as well, as this will\n            // then cause it to apply when the user types something in the\n            // block, which is presumably what was intended.\n            //\n            // IMG tags are included because we may want to create a link around\n            // them, and adding other styles is harmless.\n            const walker = new TreeIterator<Element | Text>(\n                range.commonAncestorContainer,\n                SHOW_ELEMENT_OR_TEXT,\n                (node: Node) => {\n                    return (\n                        (node instanceof Text ||\n                            node.nodeName === 'BR' ||\n                            node.nodeName === 'IMG') &&\n                        isNodeContainedInRange(range, node, true)\n                    );\n                },\n            );\n\n            // Start at the beginning node of the range and iterate through\n            // all the nodes in the range that need formatting.\n            let { startContainer, startOffset, endContainer, endOffset } =\n                range;\n\n            // Make sure we start with a valid node.\n            walker.currentNode = startContainer;\n            if (\n                (!(startContainer instanceof Element) &&\n                    !(startContainer instanceof Text)) ||\n                !walker.filter(startContainer)\n            ) {\n                const next = walker.nextNode();\n                // If there are no interesting nodes in the selection, abort\n                if (!next) {\n                    return range;\n                }\n                startContainer = next;\n                startOffset = 0;\n            }\n\n            do {\n                let node = walker.currentNode;\n                const needsFormat = !getNearest(node, root, tag, attributes);\n                if (needsFormat) {\n                    // <br> can never be a container node, so must have a text\n                    // node if node == (end|start)Container\n                    if (\n                        node === endContainer &&\n                        (node as Text).length > endOffset\n                    ) {\n                        (node as Text).splitText(endOffset);\n                    }\n                    if (node === startContainer && startOffset) {\n                        node = (node as Text).splitText(startOffset);\n                        if (endContainer === startContainer) {\n                            endContainer = node;\n                            endOffset -= startOffset;\n                        } else if (endContainer === startContainer.parentNode) {\n                            endOffset += 1;\n                        }\n                        startContainer = node;\n                        startOffset = 0;\n                    }\n                    const el = createElement(tag, attributes);\n                    replaceWith(node, el);\n                    el.appendChild(node);\n                }\n            } while (walker.nextNode());\n\n            // Now set the selection to as it was before\n            range = createRange(\n                startContainer,\n                startOffset,\n                endContainer,\n                endOffset,\n            );\n        }\n        return range;\n    }\n\n    _removeFormat(\n        tag: string,\n        attributes: Record<string, string>,\n        range: Range,\n        partial?: boolean,\n    ): Range {\n        // Add bookmark\n        this._saveRangeToBookmark(range);\n\n        // We need a node in the selection to break the surrounding\n        // formatted text.\n        let fixer: Node | Text | null | undefined;\n        if (range.collapsed) {\n            if (cantFocusEmptyTextNodes) {\n                fixer = document.createTextNode(ZWS);\n            } else {\n                fixer = document.createTextNode('');\n            }\n            insertNodeInRange(range, fixer!);\n        }\n\n        // Find block-level ancestor of selection\n        let root = range.commonAncestorContainer;\n        while (isInline(root)) {\n            root = root.parentNode!;\n        }\n\n        // Find text nodes inside formatTags that are not in selection and\n        // add an extra tag with the same formatting.\n        const startContainer = range.startContainer;\n        const startOffset = range.startOffset;\n        const endContainer = range.endContainer;\n        const endOffset = range.endOffset;\n        const toWrap: [Node, Node][] = [];\n        const examineNode = (node: Node, exemplar: Node) => {\n            // If the node is completely contained by the range then\n            // we're going to remove all formatting so ignore it.\n            if (isNodeContainedInRange(range, node, false)) {\n                return;\n            }\n\n            let child: Node;\n            let next: Node;\n\n            // If not at least partially contained, wrap entire contents\n            // in a clone of the tag we're removing and we're done.\n            if (!isNodeContainedInRange(range, node, true)) {\n                // Ignore bookmarks and empty text nodes\n                if (\n                    !(node instanceof HTMLInputElement) &&\n                    (!(node instanceof Text) || node.data)\n                ) {\n                    toWrap.push([exemplar, node]);\n                }\n                return;\n            }\n\n            // Split any partially selected text nodes.\n            if (node instanceof Text) {\n                if (node === endContainer && endOffset !== node.length) {\n                    toWrap.push([exemplar, node.splitText(endOffset)]);\n                }\n                if (node === startContainer && startOffset) {\n                    node.splitText(startOffset);\n                    toWrap.push([exemplar, node]);\n                }\n            } else {\n                // If not a text node, recurse onto all children.\n                // Beware, the tree may be rewritten with each call\n                // to examineNode, hence find the next sibling first.\n                for (child = node.firstChild!; child; child = next) {\n                    next = child.nextSibling!;\n                    examineNode(child, exemplar);\n                }\n            }\n        };\n        const formatTags = Array.from(\n            (root as Element).getElementsByTagName(tag),\n        ).filter((el: Node): boolean => {\n            return (\n                isNodeContainedInRange(range, el, true) &&\n                hasTagAttributes(el, tag, attributes)\n            );\n        });\n\n        if (!partial) {\n            formatTags.forEach((node: Node) => {\n                examineNode(node, node);\n            });\n        }\n\n        // Now wrap unselected nodes in the tag\n        toWrap.forEach(([el, node]) => {\n            el = el.cloneNode(false);\n            replaceWith(node, el);\n            el.appendChild(node);\n        });\n        // and remove old formatting tags.\n        formatTags.forEach((el: Element) => {\n            replaceWith(el, empty(el));\n        });\n\n        if (cantFocusEmptyTextNodes && fixer) {\n            // Clean up any previous ZWS in this block. They are not needed,\n            // and this works around a Chrome bug where it doesn't render the\n            // text in some situations with multiple ZWS(!)\n            fixer = fixer.parentNode;\n            let block = fixer;\n            while (block && isInline(block)) {\n                block = block.parentNode;\n            }\n            if (block) {\n                removeZWS(block, fixer);\n            }\n        }\n\n        // Merge adjacent inlines:\n        this._getRangeAndRemoveBookmark(range);\n        if (fixer) {\n            range.collapse(false);\n        }\n        mergeInlines(root, range);\n\n        return range;\n    }\n\n    // ---\n\n    bold(): Squire {\n        return this.changeFormat({ tag: 'B' });\n    }\n\n    removeBold(): Squire {\n        return this.changeFormat(null, { tag: 'B' });\n    }\n\n    italic(): Squire {\n        return this.changeFormat({ tag: 'I' });\n    }\n\n    removeItalic(): Squire {\n        return this.changeFormat(null, { tag: 'I' });\n    }\n\n    underline(): Squire {\n        return this.changeFormat({ tag: 'U' });\n    }\n\n    removeUnderline(): Squire {\n        return this.changeFormat(null, { tag: 'U' });\n    }\n\n    strikethrough(): Squire {\n        return this.changeFormat({ tag: 'S' });\n    }\n\n    removeStrikethrough(): Squire {\n        return this.changeFormat(null, { tag: 'S' });\n    }\n\n    subscript(): Squire {\n        return this.changeFormat({ tag: 'SUB' }, { tag: 'SUP' });\n    }\n\n    removeSubscript(): Squire {\n        return this.changeFormat(null, { tag: 'SUB' });\n    }\n\n    superscript(): Squire {\n        return this.changeFormat({ tag: 'SUP' }, { tag: 'SUB' });\n    }\n\n    removeSuperscript(): Squire {\n        return this.changeFormat(null, { tag: 'SUP' });\n    }\n\n    // ---\n\n    makeLink(url: string, attributes?: Record<string, string>): Squire {\n        const range = this.getSelection();\n        if (range.collapsed) {\n            let protocolEnd = url.indexOf(':') + 1;\n            if (protocolEnd) {\n                while (url[protocolEnd] === '/') {\n                    protocolEnd += 1;\n                }\n            }\n            insertNodeInRange(\n                range,\n                document.createTextNode(url.slice(protocolEnd)),\n            );\n        }\n        attributes = Object.assign(\n            {\n                href: url,\n            },\n            this._config.tagAttributes.a,\n            attributes,\n        );\n\n        return this.changeFormat(\n            {\n                tag: 'A',\n                attributes: attributes as Record<string, string>,\n            },\n            {\n                tag: 'A',\n            },\n            range,\n        );\n    }\n\n    removeLink(): Squire {\n        return this.changeFormat(\n            null,\n            {\n                tag: 'A',\n            },\n            this.getSelection(),\n            true,\n        );\n    }\n\n    /*\n    linkRegExp = new RegExp(\n        // Only look on boundaries\n        '\\\\b(?:' +\n        // Capture group 1: URLs\n        '(' +\n            // Add links to URLS\n            // Starts with:\n            '(?:' +\n                // http(s):// or ftp://\n                '(?:ht|f)tps?:\\\\/\\\\/' +\n                // or\n                '|' +\n                // www.\n                'www\\\\d{0,3}[.]' +\n                // or\n                '|' +\n                // foo90.com/\n                '[a-z0-9][a-z0-9.\\\\-]*[.][a-z]{2,}\\\\/' +\n            ')' +\n            // Then we get one or more:\n            '(?:' +\n                // Run of non-spaces, non ()<>\n                '[^\\\\s()<>]+' +\n                // or\n                '|' +\n                // balanced parentheses (one level deep only)\n                '\\\\([^\\\\s()<>]+\\\\)' +\n            ')+' +\n            // And we finish with\n            '(?:' +\n                // Not a space or punctuation character\n                '[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]' +\n                // or\n                '|' +\n                // Balanced parentheses.\n                '\\\\([^\\\\s()<>]+\\\\)' +\n            ')' +\n        // Capture group 2: Emails\n        ')|(' +\n            // Add links to emails\n            '[\\\\w\\\\-.%+]+@(?:[\\\\w\\\\-]+\\\\.)+[a-z]{2,}\\\\b' +\n            // Allow query parameters in the mailto: style\n            '(?:' +\n                '[?][^&?\\\\s]+=[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+' +\n                '(?:&[^&?\\\\s]+=[^\\\\s?&`!()\\\\[\\\\]{};:\\'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+)*' +\n            ')?' +\n        '))',\n        'i'\n    );\n    */\n    linkRegExp =\n        /\\b(?:((?:(?:ht|f)tps?:\\/\\/|www\\d{0,3}[.]|[a-z0-9][a-z0-9.\\-]*[.][a-z]{2,}\\/)(?:[^\\s()<>]+|\\([^\\s()<>]+\\))+(?:[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]|\\([^\\s()<>]+\\)))|([\\w\\-.%+]+@(?:[\\w\\-]+\\.)+[a-z]{2,}\\b(?:[?][^&?\\s]+=[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+(?:&[^&?\\s]+=[^\\s?&`!()\\[\\]{};:'\".,<>\u00AB\u00BB\u201C\u201D\u2018\u2019]+)*)?))/i;\n\n    addDetectedLinks(\n        searchInNode: DocumentFragment | Node,\n        root?: DocumentFragment | HTMLElement,\n    ): Squire {\n        const walker = new TreeIterator<Text>(\n            searchInNode,\n            SHOW_TEXT,\n            (node) => !getNearest(node, root || this._root, 'A'),\n        );\n        const linkRegExp = this.linkRegExp;\n        const defaultAttributes = this._config.tagAttributes.a;\n        let node: Text | null;\n        while ((node = walker.nextNode())) {\n            const parent = node.parentNode!;\n            let data = node.data;\n            let match: RegExpExecArray | null;\n            while ((match = linkRegExp.exec(data))) {\n                const index = match.index;\n                const endIndex = index + match[0].length;\n                if (index) {\n                    parent.insertBefore(\n                        document.createTextNode(data.slice(0, index)),\n                        node,\n                    );\n                }\n                const child = createElement(\n                    'A',\n                    Object.assign(\n                        {\n                            href: match[1]\n                                ? /^(?:ht|f)tps?:/i.test(match[1])\n                                    ? match[1]\n                                    : 'http://' + match[1]\n                                : 'mailto:' + match[0],\n                        },\n                        defaultAttributes,\n                    ),\n                );\n                child.textContent = data.slice(index, endIndex);\n                parent.insertBefore(child, node);\n                node.data = data = data.slice(endIndex);\n            }\n        }\n        return this;\n    }\n\n    // ---\n\n    setFontFace(name: string | null): Squire {\n        const className = this._config.classNames.fontFamily;\n        return this.changeFormat(\n            name\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'font-family: ' + name + ', sans-serif;',\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setFontSize(size: string | null): Squire {\n        const className = this._config.classNames.fontSize;\n        return this.changeFormat(\n            size\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style:\n                              'font-size: ' +\n                              (typeof size === 'number' ? size + 'px' : size),\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setTextColor(color: string | null): Squire {\n        const className = this._config.classNames.color;\n        return this.changeFormat(\n            color\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'color:' + color,\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    setHighlightColor(color: string | null): Squire {\n        const className = this._config.classNames.highlight;\n        return this.changeFormat(\n            color\n                ? {\n                      tag: 'SPAN',\n                      attributes: {\n                          class: className,\n                          style: 'background-color:' + color,\n                      },\n                  }\n                : null,\n            {\n                tag: 'SPAN',\n                attributes: { class: className },\n            },\n        );\n    }\n\n    // --- Block formatting\n\n    _ensureBottomLine(): void {\n        const root = this._root;\n        const last = root.lastElementChild;\n        if (\n            !last ||\n            last.nodeName !== this._config.blockTag ||\n            !isBlock(last)\n        ) {\n            root.appendChild(this.createDefaultBlock());\n        }\n    }\n\n    createDefaultBlock(children?: Node[]): HTMLElement {\n        const config = this._config;\n        return fixCursor(\n            createElement(config.blockTag, config.blockAttributes, children),\n        ) as HTMLElement;\n    }\n\n    tagAfterSplit: Record<string, string> = {\n        DT: 'DD',\n        DD: 'DT',\n        LI: 'LI',\n        PRE: 'PRE',\n    };\n\n    splitBlock(lineBreakOnly: boolean, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        const root = this._root;\n        let block: Node | Element | null;\n        let parent: Node | null;\n        let node: Node;\n        let nodeAfterSplit: Node;\n\n        // Save undo checkpoint and remove any zws so we don't think there's\n        // content in an empty block.\n        this._recordUndoState(range);\n        this._removeZWS();\n        this._getRangeAndRemoveBookmark(range);\n\n        // Selected text is overwritten, therefore delete the contents\n        // to collapse selection.\n        if (!range.collapsed) {\n            deleteContentsOfRange(range, root);\n        }\n\n        // Linkify text\n        if (this._config.addLinks) {\n            moveRangeBoundariesDownTree(range);\n            const textNode = range.startContainer as Text;\n            const offset = range.startOffset;\n            setTimeout(() => {\n                linkifyText(this, textNode, offset);\n            }, 0);\n        }\n\n        block = getStartBlockOfRange(range, root);\n\n        // Inside a PRE, insert literal newline, unless on blank line.\n        if (block && (parent = getNearest(block, root, 'PRE'))) {\n            moveRangeBoundariesDownTree(range);\n            node = range.startContainer;\n            const offset = range.startOffset;\n            if (!(node instanceof Text)) {\n                node = document.createTextNode('');\n                parent.insertBefore(node, parent.firstChild);\n            }\n            // If blank line: split and insert default block\n            if (\n                !lineBreakOnly &&\n                node instanceof Text &&\n                (node.data.charAt(offset - 1) === '\\n' ||\n                    rangeDoesStartAtBlockBoundary(range, root)) &&\n                (node.data.charAt(offset) === '\\n' ||\n                    rangeDoesEndAtBlockBoundary(range, root))\n            ) {\n                node.deleteData(offset && offset - 1, offset ? 2 : 1);\n                nodeAfterSplit = split(\n                    node,\n                    offset && offset - 1,\n                    root,\n                    root,\n                ) as Node;\n                node = nodeAfterSplit.previousSibling!;\n                if (!node.textContent) {\n                    detach(node);\n                }\n                node = this.createDefaultBlock();\n                nodeAfterSplit.parentNode!.insertBefore(node, nodeAfterSplit);\n                if (!nodeAfterSplit.textContent) {\n                    detach(nodeAfterSplit);\n                }\n                range.setStart(node, 0);\n            } else {\n                (node as Text).insertData(offset, '\\n');\n                fixCursor(parent);\n                // Firefox bug: if you set the selection in the text node after\n                // the new line, it draws the cursor before the line break still\n                // but if you set the selection to the equivalent position\n                // in the parent, it works.\n                if ((node as Text).length === offset + 1) {\n                    range.setStartAfter(node);\n                } else {\n                    range.setStart(node, offset + 1);\n                }\n            }\n            range.collapse(true);\n            this.setSelection(range);\n            this._updatePath(range, true);\n            this._docWasChanged();\n            return this;\n        }\n\n        // If this is a malformed bit of document or in a table;\n        // just play it safe and insert a <br>.\n        if (!block || lineBreakOnly || /^T[HD]$/.test(block.nodeName)) {\n            // If inside an <a>, move focus out\n            moveRangeBoundaryOutOf(range, 'A', root);\n            insertNodeInRange(range, createElement('BR'));\n            range.collapse(false);\n            this.setSelection(range);\n            this._updatePath(range, true);\n            return this;\n        }\n\n        // If in a list, we'll split the LI instead.\n        if ((parent = getNearest(block, root, 'LI'))) {\n            block = parent;\n        }\n\n        if (isEmptyBlock(block as Element)) {\n            if (\n                getNearest(block, root, 'UL') ||\n                getNearest(block, root, 'OL')\n            ) {\n                // Break list\n                this.decreaseListLevel(range);\n                return this;\n                // Break blockquote\n            } else if (getNearest(block, root, 'BLOCKQUOTE')) {\n                this.removeQuote(range);\n                return this;\n            }\n        }\n\n        // Otherwise, split at cursor point.\n        node = range.startContainer;\n        const offset = range.startOffset;\n        let splitTag = this.tagAfterSplit[block.nodeName];\n        nodeAfterSplit = split(\n            node,\n            offset,\n            block.parentNode!,\n            this._root,\n        ) as Node;\n\n        const config = this._config;\n        let splitProperties: Record<string, string> | null = null;\n        if (!splitTag) {\n            splitTag = config.blockTag;\n            splitProperties = config.blockAttributes;\n        }\n\n        // Make sure the new node is the correct type.\n        if (!hasTagAttributes(nodeAfterSplit, splitTag, splitProperties)) {\n            block = createElement(splitTag, splitProperties);\n            if ((nodeAfterSplit as HTMLElement).dir) {\n                (block as HTMLElement).dir = (\n                    nodeAfterSplit as HTMLElement\n                ).dir;\n            }\n            replaceWith(nodeAfterSplit, block);\n            block.appendChild(empty(nodeAfterSplit));\n            nodeAfterSplit = block;\n        }\n\n        // Clean up any empty inlines if we hit enter at the beginning of the\n        // block\n        removeZWS(block);\n        removeEmptyInlines(block);\n        fixCursor(block);\n\n        // Focus cursor\n        // If there's a <b>/<i> etc. at the beginning of the split\n        // make sure we focus inside it.\n        while (nodeAfterSplit instanceof Element) {\n            let child = nodeAfterSplit.firstChild;\n            let next;\n\n            // Don't continue links over a block break; unlikely to be the\n            // desired outcome.\n            if (\n                nodeAfterSplit.nodeName === 'A' &&\n                (!nodeAfterSplit.textContent ||\n                    nodeAfterSplit.textContent === ZWS)\n            ) {\n                child = document.createTextNode('') as Text;\n                replaceWith(nodeAfterSplit, child);\n                nodeAfterSplit = child;\n                break;\n            }\n\n            while (child && child instanceof Text && !child.data) {\n                next = child.nextSibling;\n                if (!next || next.nodeName === 'BR') {\n                    break;\n                }\n                detach(child);\n                child = next;\n            }\n\n            // 'BR's essentially don't count; they're a browser hack.\n            // If you try to select the contents of a 'BR', FF will not let\n            // you type anything!\n            if (!child || child.nodeName === 'BR' || child instanceof Text) {\n                break;\n            }\n            nodeAfterSplit = child;\n        }\n        range = createRange(nodeAfterSplit, 0);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    forEachBlock(\n        fn: (el: HTMLElement) => any,\n        mutates: boolean,\n        range?: Range,\n    ): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Save undo checkpoint\n        if (mutates) {\n            this.saveUndoState(range);\n        }\n\n        const root = this._root;\n        let start = getStartBlockOfRange(range, root);\n        const end = getEndBlockOfRange(range, root);\n        if (start && end) {\n            do {\n                if (fn(start) || start === end) {\n                    break;\n                }\n            } while ((start = getNextBlock(start, root)));\n        }\n\n        if (mutates) {\n            this.setSelection(range);\n            // Path may have changed\n            this._updatePath(range, true);\n        }\n        return this;\n    }\n\n    modifyBlocks(modify: (x: DocumentFragment) => Node, range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // 1. Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        // 2. Expand range to block boundaries\n        const root = this._root;\n        expandRangeToBlockBoundaries(range, root);\n\n        // 3. Remove range.\n        moveRangeBoundariesUpTree(range, root, root, root);\n        const frag = extractContentsOfRange(range, root, root);\n\n        // 4. Modify tree of fragment and reinsert.\n        if (!range.collapsed) {\n            // After extracting contents, the range edges will still be at the\n            // level we began the spilt. We want to insert directly in the\n            // root, so move the range up there.\n            let node = range.endContainer;\n            if (node === root) {\n                range.collapse(false);\n            } else {\n                while (node.parentNode !== root) {\n                    node = node.parentNode!;\n                }\n                range.setStartBefore(node);\n                range.collapse(true);\n            }\n        }\n        insertNodeInRange(range, modify.call(this, frag));\n\n        // 5. Merge containers at edges\n        if (range.endOffset < range.endContainer.childNodes.length) {\n            mergeContainers(\n                range.endContainer.childNodes[range.endOffset],\n                root,\n            );\n        }\n        mergeContainers(\n            range.startContainer.childNodes[range.startOffset],\n            root,\n        );\n\n        // 6. Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this;\n    }\n\n    // ---\n\n    setTextAlignment(alignment: string): Squire {\n        this.forEachBlock((block: HTMLElement) => {\n            const className = block.className\n                .split(/\\s+/)\n                .filter((klass) => {\n                    return !!klass && !/^align/.test(klass);\n                })\n                .join(' ');\n            if (alignment) {\n                block.className = className + ' align-' + alignment;\n                block.style.textAlign = alignment;\n            } else {\n                block.className = className;\n                block.style.textAlign = '';\n            }\n        }, true);\n        return this.focus();\n    }\n\n    setTextDirection(direction: string | null): Squire {\n        this.forEachBlock((block: HTMLElement) => {\n            if (direction) {\n                block.dir = direction;\n            } else {\n                block.removeAttribute('dir');\n            }\n        }, true);\n        return this.focus();\n    }\n\n    // ---\n\n    _getListSelection(\n        range: Range,\n        root: Element,\n    ): [Node, Node | null, Node | null] | null {\n        let list: Node | null = range.commonAncestorContainer;\n        let startLi: Node | null = range.startContainer;\n        let endLi: Node | null = range.endContainer;\n        while (list && list !== root && !/^[OU]L$/.test(list.nodeName)) {\n            list = list.parentNode;\n        }\n        if (!list || list === root) {\n            return null;\n        }\n        if (startLi === list) {\n            startLi = startLi.childNodes[range.startOffset];\n        }\n        if (endLi === list) {\n            endLi = endLi.childNodes[range.endOffset];\n        }\n        while (startLi && startLi.parentNode !== list) {\n            startLi = startLi.parentNode;\n        }\n        while (endLi && endLi.parentNode !== list) {\n            endLi = endLi.parentNode;\n        }\n        return [list, startLi, endLi];\n    }\n\n    increaseListLevel(range?: Range) {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        // Get start+end li in single common ancestor\n        const root = this._root;\n        const listSelection = this._getListSelection(range, root);\n        if (!listSelection) {\n            return this.focus();\n        }\n        // eslint-disable-next-line prefer-const\n        let [list, startLi, endLi] = listSelection;\n        if (!startLi || startLi === list.firstChild) {\n            return this.focus();\n        }\n\n        // Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        // Increase list depth\n        const type = list.nodeName;\n        let newParent = startLi.previousSibling!;\n        let listAttrs: Record<string, string> | null;\n        let next: Node | null;\n        if (newParent.nodeName !== type) {\n            listAttrs = this._config.tagAttributes[type.toLowerCase()];\n            newParent = createElement(type, listAttrs);\n            list.insertBefore(newParent, startLi);\n        }\n        do {\n            next = startLi === endLi ? null : startLi.nextSibling;\n            newParent.appendChild(startLi);\n        } while ((startLi = next));\n        next = newParent.nextSibling;\n        if (next) {\n            mergeContainers(next, root);\n        }\n\n        // Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    decreaseListLevel(range?: Range) {\n        if (!range) {\n            range = this.getSelection();\n        }\n\n        const root = this._root;\n        const listSelection = this._getListSelection(range, root);\n        if (!listSelection) {\n            return this.focus();\n        }\n\n        // eslint-disable-next-line prefer-const\n        let [list, startLi, endLi] = listSelection;\n        if (!startLi) {\n            startLi = list.firstChild;\n        }\n        if (!endLi) {\n            endLi = list.lastChild!;\n        }\n\n        // Save undo checkpoint and bookmark selection\n        this._recordUndoState(range, this._isInUndoState);\n\n        let next: Node | null;\n        let insertBefore: Node | null = null;\n        if (startLi) {\n            // Find the new parent list node\n            let newParent = list.parentNode!;\n\n            // Split list if necessary\n            insertBefore = !endLi.nextSibling\n                ? list.nextSibling\n                : (split(list, endLi.nextSibling, newParent, root) as Node);\n\n            if (newParent !== root && newParent.nodeName === 'LI') {\n                newParent = newParent.parentNode!;\n                while (insertBefore) {\n                    next = insertBefore.nextSibling;\n                    endLi.appendChild(insertBefore);\n                    insertBefore = next;\n                }\n                insertBefore = list.parentNode!.nextSibling;\n            }\n\n            const makeNotList = !/^[OU]L$/.test(newParent.nodeName);\n            do {\n                next = startLi === endLi ? null : startLi.nextSibling;\n                list.removeChild(startLi);\n                if (makeNotList && startLi.nodeName === 'LI') {\n                    startLi = this.createDefaultBlock([empty(startLi)]);\n                }\n                newParent.insertBefore(startLi!, insertBefore);\n            } while ((startLi = next));\n        }\n\n        if (!list.firstChild) {\n            detach(list);\n        }\n\n        if (insertBefore) {\n            mergeContainers(insertBefore, root);\n        }\n\n        // Restore selection\n        this._getRangeAndRemoveBookmark(range);\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n\n    _makeList(frag: DocumentFragment, type: string): DocumentFragment {\n        const walker = getBlockWalker(frag, this._root);\n        const tagAttributes = this._config.tagAttributes;\n        const listAttrs = tagAttributes[type.toLowerCase()];\n        const listItemAttrs = tagAttributes.li;\n        let node: Node | null;\n        while ((node = walker.nextNode())) {\n            if (node.parentNode! instanceof HTMLLIElement) {\n                node = node.parentNode!;\n                walker.currentNode = node.lastChild!;\n            }\n            if (!(node instanceof HTMLLIElement)) {\n                const newLi = createElement('LI', listItemAttrs);\n                if ((node as HTMLElement).dir) {\n                    newLi.dir = (node as HTMLElement).dir;\n                }\n\n                // Have we replaced the previous block with a new <ul>/<ol>?\n                const prev: ChildNode | null = node.previousSibling;\n                if (prev && prev.nodeName === type) {\n                    prev.appendChild(newLi);\n                    detach(node);\n                    // Otherwise, replace this block with the <ul>/<ol>\n                } else {\n                    replaceWith(node, createElement(type, listAttrs, [newLi]));\n                }\n                newLi.appendChild(empty(node));\n                walker.currentNode = newLi;\n            } else {\n                node = node.parentNode;\n                const tag = node!.nodeName;\n                if (tag !== type && /^[OU]L$/.test(tag)) {\n                    replaceWith(\n                        node!,\n                        createElement(type, listAttrs, [empty(node!)]),\n                    );\n                }\n            }\n        }\n        return frag;\n    }\n\n    makeUnorderedList(): Squire {\n        this.modifyBlocks((frag) => this._makeList(frag, 'UL'));\n        return this.focus();\n    }\n\n    makeOrderedList(): Squire {\n        this.modifyBlocks((frag) => this._makeList(frag, 'OL'));\n        return this.focus();\n    }\n\n    removeList(): Squire {\n        this.modifyBlocks((frag) => {\n            const lists = frag.querySelectorAll('UL, OL');\n            const items = frag.querySelectorAll('LI');\n            const root = this._root;\n            for (let i = 0, l = lists.length; i < l; i += 1) {\n                const list = lists[i];\n                const listFrag = empty(list);\n                fixContainer(listFrag, root);\n                replaceWith(list, listFrag);\n            }\n\n            for (let i = 0, l = items.length; i < l; i += 1) {\n                const item = items[i];\n                if (isBlock(item)) {\n                    replaceWith(item, this.createDefaultBlock([empty(item)]));\n                } else {\n                    fixContainer(item, root);\n                    replaceWith(item, empty(item));\n                }\n            }\n            return frag;\n        });\n        return this.focus();\n    }\n\n    // ---\n\n    increaseQuoteLevel(range?: Range): Squire {\n        this.modifyBlocks(\n            (frag) =>\n                createElement(\n                    'BLOCKQUOTE',\n                    this._config.tagAttributes.blockquote,\n                    [frag],\n                ),\n            range,\n        );\n        return this.focus();\n    }\n\n    decreaseQuoteLevel(range?: Range): Squire {\n        this.modifyBlocks((frag) => {\n            Array.from(frag.querySelectorAll('blockquote'))\n                .filter((el: Node) => {\n                    return !getNearest(el.parentNode, frag, 'BLOCKQUOTE');\n                })\n                .forEach((el: Node) => {\n                    replaceWith(el, empty(el));\n                });\n            return frag;\n        }, range);\n        return this.focus();\n    }\n\n    removeQuote(range?: Range): Squire {\n        this.modifyBlocks(\n            (/* frag */) =>\n                this.createDefaultBlock([\n                    createElement('INPUT', {\n                        id: this.startSelectionId,\n                        type: 'hidden',\n                    }),\n                    createElement('INPUT', {\n                        id: this.endSelectionId,\n                        type: 'hidden',\n                    }),\n                ]),\n            range,\n        );\n        return this.focus();\n    }\n\n    // ---\n\n    code(): Squire {\n        const range = this.getSelection();\n        if (range.collapsed || isContainer(range.commonAncestorContainer)) {\n            this.modifyBlocks((frag) => {\n                const root = this._root;\n                const output = document.createDocumentFragment();\n                const blockWalker = getBlockWalker(frag, root);\n                let node: Element | Text | null;\n                // 1. Extract inline content; drop all blocks and contains.\n                while ((node = blockWalker.nextNode())) {\n                    // 2. Replace <br> with \\n in content\n                    let nodes = node.querySelectorAll('BR');\n                    const brBreaksLine: boolean[] = [];\n                    let l = nodes.length;\n                    // Must calculate whether the <br> breaks a line first,\n                    // because if we have two <br>s next to each other, after\n                    // the first one is converted to a block split, the second\n                    // will be at the end of a block and therefore seem to not\n                    // be a line break. But in its original context it was, so\n                    // we should also convert it to a block split.\n                    for (let i = 0; i < l; i += 1) {\n                        brBreaksLine[i] = isLineBreak(nodes[i], false);\n                    }\n                    while (l--) {\n                        const br = nodes[l];\n                        if (!brBreaksLine[l]) {\n                            detach(br);\n                        } else {\n                            replaceWith(br, document.createTextNode('\\n'));\n                        }\n                    }\n                    // 3. Remove <code>; its format clashes with <pre>\n                    nodes = node.querySelectorAll('CODE');\n                    l = nodes.length;\n                    while (l--) {\n                        replaceWith(nodes[l], empty(nodes[l]));\n                    }\n                    if (output.childNodes.length) {\n                        output.appendChild(document.createTextNode('\\n'));\n                    }\n                    output.appendChild(empty(node));\n                }\n                // 4. Replace nbsp with regular sp\n                const textWalker = new TreeIterator<Text>(output, SHOW_TEXT);\n                while ((node = textWalker.nextNode())) {\n                    // eslint-disable-next-line no-irregular-whitespace\n                    node.data = node.data.replace(/\u00A0/g, ' '); // nbsp -> sp\n                }\n                output.normalize();\n                return fixCursor(\n                    createElement('PRE', this._config.tagAttributes.pre, [\n                        output,\n                    ]),\n                );\n            }, range);\n            this.focus();\n        } else {\n            this.changeFormat(\n                {\n                    tag: 'CODE',\n                    attributes: this._config.tagAttributes.code,\n                },\n                null,\n                range,\n            );\n        }\n        return this;\n    }\n\n    removeCode(): Squire {\n        const range = this.getSelection();\n        const ancestor = range.commonAncestorContainer;\n        const inPre = getNearest(ancestor, this._root, 'PRE');\n        if (inPre) {\n            this.modifyBlocks((frag) => {\n                const root = this._root;\n                const pres = frag.querySelectorAll('PRE');\n                let l = pres.length;\n                while (l--) {\n                    const pre = pres[l];\n                    const walker = new TreeIterator<Text>(pre, SHOW_TEXT);\n                    let node: Text | null;\n                    while ((node = walker.nextNode())) {\n                        let value = node.data;\n                        value = value.replace(/ (?= )/g, '\u00A0'); // sp -> nbsp\n                        const contents = document.createDocumentFragment();\n                        let index: number;\n                        while ((index = value.indexOf('\\n')) > -1) {\n                            contents.appendChild(\n                                document.createTextNode(value.slice(0, index)),\n                            );\n                            contents.appendChild(createElement('BR'));\n                            value = value.slice(index + 1);\n                        }\n                        node.parentNode!.insertBefore(contents, node);\n                        node.data = value;\n                    }\n                    fixContainer(pre, root);\n                    replaceWith(pre, empty(pre));\n                }\n                return frag;\n            }, range);\n            this.focus();\n        } else {\n            this.changeFormat(null, { tag: 'CODE' }, range);\n        }\n        return this;\n    }\n\n    toggleCode(): Squire {\n        if (this.hasFormat('PRE') || this.hasFormat('CODE')) {\n            this.removeCode();\n        } else {\n            this.code();\n        }\n        return this;\n    }\n\n    // ---\n\n    _removeFormatting(\n        root: DocumentFragment | Element,\n        clean: DocumentFragment | Element,\n    ): DocumentFragment | Element {\n        for (\n            let node = root.firstChild, next: ChildNode | null;\n            node;\n            node = next\n        ) {\n            next = node.nextSibling;\n            if (isInline(node)) {\n                if (\n                    node instanceof Text ||\n                    node.nodeName === 'BR' ||\n                    node.nodeName === 'IMG'\n                ) {\n                    clean.appendChild(node);\n                    continue;\n                }\n            } else if (isBlock(node)) {\n                clean.appendChild(\n                    this.createDefaultBlock([\n                        this._removeFormatting(\n                            node as Element,\n                            document.createDocumentFragment(),\n                        ),\n                    ]),\n                );\n                continue;\n            }\n            this._removeFormatting(node as Element, clean);\n        }\n        return clean;\n    }\n\n    removeAllFormatting(range?: Range): Squire {\n        if (!range) {\n            range = this.getSelection();\n        }\n        if (range.collapsed) {\n            return this.focus();\n        }\n\n        const root = this._root;\n        let stopNode = range.commonAncestorContainer;\n        while (stopNode && !isBlock(stopNode)) {\n            stopNode = stopNode.parentNode!;\n        }\n        if (!stopNode) {\n            expandRangeToBlockBoundaries(range, root);\n            stopNode = root;\n        }\n        if (stopNode instanceof Text) {\n            return this.focus();\n        }\n\n        // Record undo point\n        this.saveUndoState(range);\n\n        // Avoid splitting where we're already at edges.\n        moveRangeBoundariesUpTree(range, stopNode, stopNode, root);\n\n        // Split the selection up to the block, or if whole selection in same\n        // block, expand range boundaries to ends of block and split up to root.\n        const startContainer = range.startContainer;\n        let startOffset = range.startOffset;\n        const endContainer = range.endContainer;\n        let endOffset = range.endOffset;\n\n        // Split end point first to avoid problems when end and start\n        // in same container.\n        const formattedNodes = document.createDocumentFragment();\n        const cleanNodes = document.createDocumentFragment();\n        const nodeAfterSplit = split(endContainer, endOffset, stopNode, root);\n        let nodeInSplit = split(startContainer, startOffset, stopNode, root);\n        let nextNode: ChildNode | null;\n\n        // Then replace contents in split with a cleaned version of the same:\n        // blocks become default blocks, text and leaf nodes survive, everything\n        // else is obliterated.\n        while (nodeInSplit !== nodeAfterSplit) {\n            nextNode = nodeInSplit!.nextSibling;\n            formattedNodes.appendChild(nodeInSplit!);\n            nodeInSplit = nextNode;\n        }\n        this._removeFormatting(formattedNodes, cleanNodes);\n        cleanNodes.normalize();\n        nodeInSplit = cleanNodes.firstChild;\n        nextNode = cleanNodes.lastChild;\n\n        // Restore selection\n        if (nodeInSplit) {\n            stopNode.insertBefore(cleanNodes, nodeAfterSplit);\n            const childNodes = Array.from(stopNode.childNodes) as Node[];\n            startOffset = childNodes.indexOf(nodeInSplit);\n            endOffset = nextNode ? childNodes.indexOf(nextNode) + 1 : 0;\n        } else if (nodeAfterSplit) {\n            const childNodes = Array.from(stopNode.childNodes) as Node[];\n            startOffset = childNodes.indexOf(nodeAfterSplit);\n            endOffset = startOffset;\n        }\n\n        // Merge text nodes at edges, if possible\n        range.setStart(stopNode, startOffset);\n        range.setEnd(stopNode, endOffset);\n        mergeInlines(stopNode, range);\n\n        // And move back down the tree\n        moveRangeBoundariesDownTree(range);\n\n        this.setSelection(range);\n        this._updatePath(range, true);\n\n        return this.focus();\n    }\n}\n\n// ---\n\nexport { Squire };\nexport type { SquireConfig };\n", "import { Squire } from './Editor';\n\nexport default Squire;\n"],
+  "mappings": "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",
   "names": ["always", "TreeIterator", "root", "nodeType", "filter", "node", "current", "ZWS", "ua", "isMac", "isWin", "isIOS", "isAndroid", "isGecko", "isLegacyEdge", "isWebKit", "ctrlKey", "cantFocusEmptyTextNodes", "supportsInputEvents", "notWS", "inlineNodeNames", "leafNodeNames", "UNKNOWN", "INLINE", "BLOCK", "CONTAINER", "cache", "resetNodeCategoryCache", "isLeaf", "node", "getNodeCategory", "nodeCategory", "isInline", "isBlock", "isContainer", "createElement", "tag", "props", "children", "el", "attr", "value", "node", "areAlike", "node2", "isLeaf", "hasTagAttributes", "attributes", "getNearest", "root", "getNodeBeforeOffset", "offset", "getNodeAfterOffset", "returnNode", "getLength", "empty", "frag", "child", "detach", "parent", "replaceWith", "notWSTextNode", "node", "notWS", "isLineBreak", "br", "isLBIfEmptyBlock", "block", "isInline", "walker", "TreeIterator", "removeZWS", "root", "keepNode", "textNode", "index", "ZWS", "parent", "getLength", "START_TO_START", "START_TO_END", "END_TO_END", "END_TO_START", "isNodeContainedInRange", "range", "node", "partial", "nodeRange", "nodeEndBeforeStart", "nodeStartAfterEnd", "nodeStartAfterStart", "nodeEndBeforeEnd", "moveRangeBoundariesDownTree", "startContainer", "startOffset", "endContainer", "endOffset", "child", "isLeaf", "textChild", "prev", "isLineBreak", "getLength", "moveRangeBoundariesUpTree", "startMax", "endMax", "root", "parent", "moveRangeBoundaryOutOf", "tag", "getNearest", "clone", "fixCursor", "node", "fixer", "isInline", "child", "cantFocusEmptyTextNodes", "ZWS", "createElement", "parent", "fixContainer", "container", "root", "wrapper", "isBR", "isContainer", "split", "offset", "stopNode", "nodeAfterSplit", "clone", "next", "getNearest", "_mergeInlines", "fakeRange", "children", "l", "frags", "prev", "areAlike", "getLength", "detach", "empty", "frag", "mergeInlines", "range", "element", "mergeWithBlock", "block", "last", "mergeContainers", "first", "isListItem", "needsFix", "styleToSemantic", "createElement", "notWS", "classNames", "family", "size", "replaceStyles", "node", "_", "config", "style", "newTreeBottom", "newTreeTop", "attr", "converter", "css", "el", "empty", "replaceWith", "replaceWithTag", "tag", "parent", "attributes", "i", "l", "attribute", "fontSizes", "stylesRewriters", "font", "face", "color", "fontSpan", "sizeSpan", "colorSpan", "allowedBlock", "blacklist", "cleanTree", "preserveWS", "children", "nonInlineParent", "isInline", "walker", "TreeIterator", "child", "nodeName", "rewriter", "childLength", "data", "startsWithWS", "endsWithWS", "sibling", "removeEmptyInlines", "isLeaf", "cleanupBRs", "root", "keepForBlankLine", "brs", "brBreaksLine", "isLineBreak", "br", "fixContainer", "detach", "escapeHTML", "text", "getBlockWalker", "node", "root", "walker", "TreeIterator", "isBlock", "getPreviousBlock", "block", "getNextBlock", "isEmptyBlock", "getStartBlockOfRange", "range", "root", "container", "block", "isInline", "getPreviousBlock", "isBlock", "node", "getNodeBeforeOffset", "getNextBlock", "isNodeContainedInRange", "getEndBlockOfRange", "getNodeAfterOffset", "child", "isContent", "notWS", "rangeDoesStartAtBlockBoundary", "startContainer", "startOffset", "nodeAfterCursor", "text", "i", "ZWS", "contentWalker", "TreeIterator", "rangeDoesEndAtBlockBoundary", "endContainer", "endOffset", "currentNode", "length", "expandRangeToBlockBoundaries", "start", "end", "parent", "createRange", "startContainer", "startOffset", "endContainer", "endOffset", "range", "insertNodeInRange", "node", "children", "parent", "afterSplit", "childCount", "extractContentsOfRange", "common", "root", "frag", "split", "next", "detach", "fixCursor", "getAdjacentInlineNode", "iterator", "method", "nextNode", "isLeaf", "isInline", "deleteContentsOfRange", "startBlock", "getStartBlockOfRange", "endBlock", "getEndBlockOfRange", "needsMerge", "moveRangeBoundariesDownTree", "moveRangeBoundariesUpTree", "mergeWithBlock", "child", "TreeIterator", "afterNode", "afterOffset", "beforeNode", "beforeOffset", "offset", "rangeDoesStartAtBlockBoundary", "rangeDoesEndAtBlockBoundary", "insertTreeFragmentIntoRange", "firstInFragIsInline", "fixContainer", "getNextBlock", "stopPoint", "getNearest", "block", "blockContentsAfterSplit", "firstBlockInFrag", "replaceBlock", "isEmptyBlock", "container", "cleanupBRs", "nodeAfterSplit", "getPreviousBlock", "getLength", "nodeBeforeSplit", "isContainer", "mergeContainers", "tempRange", "getTextContentsOfRange", "range", "startContainer", "endContainer", "walker", "TreeIterator", "node", "isNodeContainedInRange", "textContent", "addedTextInBlock", "value", "isInline", "indexOf", "extractRangeToClipboard", "event", "range", "root", "removeRangeFromDocument", "toCleanHTML", "toPlainText", "plainTextOnly", "clipboardData", "isLegacyEdge", "text", "getTextContentsOfRange", "startBlock", "getStartBlockOfRange", "endBlock", "getEndBlockOfRange", "copyRoot", "contents", "deleteContentsOfRange", "moveRangeBoundariesDownTree", "moveRangeBoundariesUpTree", "parent", "newContents", "html", "node", "createElement", "isWin", "_onCut", "error", "_onCopy", "_monitorShiftKey", "_onPaste", "items", "choosePlain", "hasRTF", "hasImage", "plainItem", "htmlItem", "l", "item", "type", "isLink", "notWS", "match", "types", "isGecko", "data", "body", "startContainer", "startOffset", "endContainer", "endOffset", "pasteArea", "next", "first", "detach", "createRange", "_onDrop", "hasPlain", "hasHTML", "Enter", "self", "event", "range", "afterDelete", "self", "range", "node", "parent", "isInline", "ZWS", "isBlock", "getPreviousBlock", "fixCursor", "moveRangeBoundariesDownTree", "detach", "error", "detachUneditableNode", "root", "linkifyText", "textNode", "offset", "getNearest", "data", "searchFrom", "searchText", "match", "selection", "index", "endIndex", "needsSelectionUpdate", "newSelectionOffset", "defaultAttributes", "link", "createElement", "Backspace", "self", "event", "range", "root", "deleteContentsOfRange", "afterDelete", "rangeDoesStartAtBlockBoundary", "startBlock", "getStartBlockOfRange", "current", "fixContainer", "previous", "getPreviousBlock", "detachUneditableNode", "mergeWithBlock", "mergeContainers", "getNearest", "moveRangeBoundariesDownTree", "text", "offset", "a", "Delete", "self", "event", "range", "root", "current", "next", "originalRange", "cursorContainer", "cursorOffset", "nodeAfterCursor", "deleteContentsOfRange", "afterDelete", "rangeDoesEndAtBlockBoundary", "getStartBlockOfRange", "fixContainer", "getNextBlock", "detachUneditableNode", "mergeWithBlock", "mergeContainers", "moveRangeBoundariesUpTree", "detach", "moveRangeBoundariesDownTree", "Tab", "self", "event", "range", "root", "rangeDoesStartAtBlockBoundary", "node", "getStartBlockOfRange", "parent", "ShiftTab", "getNearest", "Space", "self", "event", "range", "node", "root", "deleteContentsOfRange", "rangeDoesEndAtBlockBoundary", "block", "getStartBlockOfRange", "text", "ZWS", "walker", "TreeIterator", "textNode", "detach", "getLength", "linkRange", "moveRangeBoundariesDownTree", "offset", "linkifyText", "_onKey", "event", "key", "modifiers", "code", "isWin", "range", "deleteContentsOfRange", "keyHandlers", "Backspace", "Delete", "Tab", "ShiftTab", "Space", "self", "root", "rangeDoesEndAtBlockBoundary", "moveRangeBoundariesDownTree", "node", "next", "textNode", "supportsInputEvents", "Enter", "isMac", "isIOS", "mapKeyToFormat", "tag", "remove", "ctrlKey", "path", "Squire", "root", "config", "createRange", "_onCut", "_onCopy", "_onPaste", "_onDrop", "_monitorShiftKey", "_onKey", "keyHandlers", "mutation", "_", "type", "userConfig", "html", "frag", "error", "key", "fn", "event", "alignment", "dir", "detail", "handlers", "isFocused", "handler", "target", "l", "removeZWS", "range", "startNode", "createElement", "endNode", "temp", "insertNodeInRange", "start", "end", "startContainer", "endContainer", "startOffset", "endOffset", "mergeInlines", "selection", "isLeaf", "toStart", "moveRangeBoundariesDownTree", "rect", "node", "ZWS", "parent", "force", "anchor", "focus", "newPath", "path", "id", "classList", "classNames", "styleNames", "modificationFn", "resetNodeCategoryCache", "replace", "isInUndoState", "undoIndex", "undoStack", "undoConfig", "undoThreshold", "undoLimit", "undoStackLength", "child", "block", "getNextBlock", "fixCursor", "withBookmark", "cleanTree", "cleanupBRs", "fixContainer", "isPaste", "removeEmptyInlines", "doInsert", "insertTreeFragmentIntoRange", "moveRangeBoundaryOutOf", "el", "isInline", "getStartBlockOfRange", "splitNode", "nodeAfterSplit", "split", "isEmptyBlock", "detach", "blankLine", "src", "attributes", "img", "plainText", "getNearest", "offset", "textNode", "text", "lines", "tag", "closeBlock", "openBlock", "attr", "escapeHTML", "i", "line", "getTextContentsOfRange", "fontInfo", "seenAttributes", "element", "style", "color", "backgroundColor", "fontFamily", "fontSize", "common", "walker", "TreeIterator", "isNodeContainedInRange", "seenNode", "add", "remove", "partial", "focusNode", "focusOffset", "next", "replaceWith", "fixer", "cantFocusEmptyTextNodes", "toWrap", "examineNode", "exemplar", "formatTags", "hasTagAttributes", "empty", "url", "protocolEnd", "searchInNode", "linkRegExp", "defaultAttributes", "data", "match", "index", "endIndex", "name", "className", "size", "last", "isBlock", "children", "lineBreakOnly", "deleteContentsOfRange", "linkifyText", "rangeDoesStartAtBlockBoundary", "rangeDoesEndAtBlockBoundary", "splitTag", "splitProperties", "mutates", "getEndBlockOfRange", "modify", "expandRangeToBlockBoundaries", "moveRangeBoundariesUpTree", "extractContentsOfRange", "mergeContainers", "klass", "direction", "list", "startLi", "endLi", "listSelection", "newParent", "listAttrs", "insertBefore", "makeNotList", "getBlockWalker", "tagAttributes", "listItemAttrs", "newLi", "prev", "lists", "items", "listFrag", "item", "isContainer", "output", "blockWalker", "nodes", "brBreaksLine", "isLineBreak", "br", "textWalker", "ancestor", "pres", "pre", "value", "contents", "clean", "stopNode", "formattedNodes", "cleanNodes", "nodeInSplit", "nextNode", "childNodes", "Squire_default", "Squire"]
 }
diff --git a/dist/types/Clipboard.d.ts b/dist/types/Clipboard.d.ts
index ded6080..bdac966 100644
--- a/dist/types/Clipboard.d.ts
+++ b/dist/types/Clipboard.d.ts
@@ -1,5 +1,5 @@
 import type { Squire } from './Editor';
-declare const extractRangeToClipboard: (event: ClipboardEvent, range: Range, root: HTMLElement, removeRangeFromDocument: boolean, toCleanHTML: ((html: string) => string) | null, toPlainText: ((html: string) => string) | null, plainTextOnly: boolean) => boolean;
+declare const extractRangeToClipboard: (event: ClipboardEvent, range: Range, root: HTMLElement, removeRangeFromDocument: boolean, toCleanHTML: null | ((html: string) => string), toPlainText: null | ((html: string) => string), plainTextOnly: boolean) => boolean;
 declare const _onCut: (this: Squire, event: ClipboardEvent) => void;
 declare const _onCopy: (this: Squire, event: ClipboardEvent) => void;
 declare const _monitorShiftKey: (this: Squire, event: KeyboardEvent) => void;
diff --git a/dist/types/Clipboard.d.ts.map b/dist/types/Clipboard.d.ts.map
index 6e6bf14..f036413 100644
--- a/dist/types/Clipboard.d.ts.map
+++ b/dist/types/Clipboard.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"Clipboard.d.ts","sourceRoot":"","sources":["../../source/Clipboard.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAOvC,QAAA,MAAM,uBAAuB,UAClB,cAAc,SACd,KAAK,QACN,WAAW,2BACQ,OAAO,uBACJ,MAAM,KAAK,MAAM,+BACjB,MAAM,KAAK,MAAM,yBAC9B,OAAO,KACvB,OAyFF,CAAC;AAIF,QAAA,MAAM,MAAM,SAAmB,MAAM,SAAS,cAAc,KAAG,IAkC9D,CAAC;AAEF,QAAA,MAAM,OAAO,SAAmB,MAAM,SAAS,cAAc,KAAG,IAU/D,CAAC;AAIF,QAAA,MAAM,gBAAgB,SAAmB,MAAM,SAAS,aAAa,KAAG,IAEvE,CAAC;AAEF,QAAA,MAAM,QAAQ,SAAmB,MAAM,SAAS,cAAc,KAAG,IAqLhE,CAAC;AAKF,QAAA,MAAM,OAAO,SAAmB,MAAM,SAAS,SAAS,KAAG,IAwB1D,CAAC;AAIF,OAAO,EACH,uBAAuB,EACvB,MAAM,EACN,OAAO,EACP,gBAAgB,EAChB,QAAQ,EACR,OAAO,GACV,CAAC"}
\ No newline at end of file
+{"version":3,"file":"Clipboard.d.ts","sourceRoot":"","sources":["../../source/Clipboard.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAOvC,QAAA,MAAM,uBAAuB,UAClB,cAAc,SACd,KAAK,QACN,WAAW,2BACQ,OAAO,eACnB,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,eACjC,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,iBAC/B,OAAO,KACvB,OAyFF,CAAC;AAIF,QAAA,MAAM,MAAM,SAAmB,MAAM,SAAS,cAAc,KAAG,IAkC9D,CAAC;AAEF,QAAA,MAAM,OAAO,SAAmB,MAAM,SAAS,cAAc,KAAG,IAU/D,CAAC;AAIF,QAAA,MAAM,gBAAgB,SAAmB,MAAM,SAAS,aAAa,KAAG,IAEvE,CAAC;AAEF,QAAA,MAAM,QAAQ,SAAmB,MAAM,SAAS,cAAc,KAAG,IAqLhE,CAAC;AAKF,QAAA,MAAM,OAAO,SAAmB,MAAM,SAAS,SAAS,KAAG,IAwB1D,CAAC;AAIF,OAAO,EACH,uBAAuB,EACvB,MAAM,EACN,OAAO,EACP,gBAAgB,EAChB,QAAQ,EACR,OAAO,GACV,CAAC"}
\ No newline at end of file
diff --git a/dist/types/Editor.d.ts.map b/dist/types/Editor.d.ts.map
index b0bbb76..072f50c 100644
--- a/dist/types/Editor.d.ts.map
+++ b/dist/types/Editor.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"Editor.d.ts","sourceRoot":"","sources":["../../source/Editor.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"Editor.d.ts","sourceRoot":"","sources":["../../source/Editor.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/types/node/Block.d.ts.map b/dist/types/node/Block.d.ts.map
index b1785bf..922b5b0 100644
--- a/dist/types/node/Block.d.ts.map
+++ b/dist/types/node/Block.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"Block.d.ts","sourceRoot":"","sources":["../../../source/node/Block.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAgB,MAAM,gBAAgB,CAAC;AAK5D,QAAA,MAAM,cAAc,SACV,IAAI,QACJ,OAAO,GAAG,gBAAgB,KACjC,aAAa,WAAW,CAI1B,CAAC;AAEF,QAAA,MAAM,gBAAgB,SACZ,IAAI,QACJ,OAAO,GAAG,gBAAgB,KACjC,WAAW,GAAG,IAGhB,CAAC;AAEF,QAAA,MAAM,YAAY,SACR,IAAI,QACJ,OAAO,GAAG,gBAAgB,KACjC,WAAW,GAAG,IAGhB,CAAC;AAEF,QAAA,MAAM,YAAY,UAAW,OAAO,KAAG,OAEtC,CAAC;AAIF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC"}
\ No newline at end of file
+{"version":3,"file":"Block.d.ts","sourceRoot":"","sources":["../../../source/node/Block.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAgB,MAAM,gBAAgB,CAAC;AAK5D,QAAA,MAAM,cAAc,SACV,IAAI,QACJ,OAAO,GAAG,gBAAgB,KACjC,YAAY,CAAC,WAAW,CAI1B,CAAC;AAEF,QAAA,MAAM,gBAAgB,SACZ,IAAI,QACJ,OAAO,GAAG,gBAAgB,KACjC,WAAW,GAAG,IAGhB,CAAC;AAEF,QAAA,MAAM,YAAY,SACR,IAAI,QACJ,OAAO,GAAG,gBAAgB,KACjC,WAAW,GAAG,IAGhB,CAAC;AAEF,QAAA,MAAM,YAAY,UAAW,OAAO,KAAG,OAEtC,CAAC;AAIF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC"}
\ No newline at end of file
diff --git a/dist/types/node/Node.d.ts.map b/dist/types/node/Node.d.ts.map
index 1dff536..a8d27f0 100644
--- a/dist/types/node/Node.d.ts.map
+++ b/dist/types/node/Node.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"Node.d.ts","sourceRoot":"","sources":["../../../source/node/Node.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,aAAa,QACV,MAAM,UACH,OAAO,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,aAC1B,IAAI,EAAE,KAClB,WAkBF,CAAC;AAIF,QAAA,MAAM,QAAQ,SACJ,WAAW,GAAG,IAAI,SACjB,WAAW,GAAG,IAAI,KAC1B,OAeF,CAAC;AAEF,QAAA,MAAM,gBAAgB,SACZ,IAAI,GAAG,OAAO,OACf,MAAM,eACE,OAAO,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAC3C,OAaF,CAAC;AAIF,QAAA,MAAM,UAAU,SACN,IAAI,GAAG,IAAI,QACX,OAAO,GAAG,gBAAgB,OAC3B,MAAM,eACE,OAAO,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAC3C,IAAI,GAAG,IAQT,CAAC;AAEF,QAAA,MAAM,mBAAmB,SAAU,IAAI,UAAU,MAAM,KAAG,IAQzD,CAAC;AAEF,QAAA,MAAM,kBAAkB,SAAU,IAAI,UAAU,MAAM,KAAG,IAAI,GAAG,IAgB/D,CAAC;AAEF,QAAA,MAAM,SAAS,SAAU,IAAI,KAAG,MAM/B,CAAC;AAIF,QAAA,MAAM,KAAK,SAAU,IAAI,KAAG,gBAQ3B,CAAC;AAEF,QAAA,MAAM,MAAM,SAAU,IAAI,KAAG,IAM5B,CAAC;AAEF,QAAA,MAAM,WAAW,SAAU,IAAI,SAAS,IAAI,KAAG,IAK9C,CAAC;AAIF,OAAO,EACH,QAAQ,EACR,aAAa,EACb,MAAM,EACN,KAAK,EACL,SAAS,EACT,UAAU,EACV,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,GACd,CAAC"}
\ No newline at end of file
+{"version":3,"file":"Node.d.ts","sourceRoot":"","sources":["../../../source/node/Node.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,aAAa,QACV,MAAM,UACH,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,aAC1B,IAAI,EAAE,KAClB,WAkBF,CAAC;AAIF,QAAA,MAAM,QAAQ,SACJ,WAAW,GAAG,IAAI,SACjB,WAAW,GAAG,IAAI,KAC1B,OAeF,CAAC;AAEF,QAAA,MAAM,gBAAgB,SACZ,IAAI,GAAG,OAAO,OACf,MAAM,eACE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAC3C,OAaF,CAAC;AAIF,QAAA,MAAM,UAAU,SACN,IAAI,GAAG,IAAI,QACX,OAAO,GAAG,gBAAgB,OAC3B,MAAM,eACE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAC3C,IAAI,GAAG,IAQT,CAAC;AAEF,QAAA,MAAM,mBAAmB,SAAU,IAAI,UAAU,MAAM,KAAG,IAQzD,CAAC;AAEF,QAAA,MAAM,kBAAkB,SAAU,IAAI,UAAU,MAAM,KAAG,IAAI,GAAG,IAgB/D,CAAC;AAEF,QAAA,MAAM,SAAS,SAAU,IAAI,KAAG,MAM/B,CAAC;AAIF,QAAA,MAAM,KAAK,SAAU,IAAI,KAAG,gBAQ3B,CAAC;AAEF,QAAA,MAAM,MAAM,SAAU,IAAI,KAAG,IAM5B,CAAC;AAEF,QAAA,MAAM,WAAW,SAAU,IAAI,SAAS,IAAI,KAAG,IAK9C,CAAC;AAIF,OAAO,EACH,QAAQ,EACR,aAAa,EACb,MAAM,EACN,KAAK,EACL,SAAS,EACT,UAAU,EACV,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,GACd,CAAC"}
\ No newline at end of file
diff --git a/package.json b/package.json
index ea64ae0..016cea1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "squire-rte",
-    "version": "2.2.8",
+    "version": "2.2.9",
     "description": "Squire is an HTML5 rich text editor, which provides powerful cross-browser normalisation, whilst being supremely lightweight and flexible.",
     "main": "dist/squire.mjs",
     "types": "dist/types/Squire.d.ts",