{"version":3,"sources":["jsdelivr-header.js","/npm/writefree@1.2.12/build/js/writefree.es6.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA,ACQA,SAAS,gBAAgB,EAAK,GAC5B,GACE,SAE0B,iBAAb,EACX,OAAO,EACX,IAAI,EAAc,GAKlB,OAJA,OAAO,KAAK,GAAU,SAAS,IAC7B,GAAe,GAAG,MAAS,EAAS,KAAQ,IAE9C,EAAI,aAAa,QAAS,GACnB,CACT,CAWA,SAAS,WAAW,EAAK,GACvB,OAAK,GACD,MAAM,QAAQ,GAChB,EAAQ,SAAS,IACM,iBAAV,GAAsB,EAAM,OAAS,GAAG,EAAI,UAAU,IAAI,EAAM,IAG7E,EAAI,UAAU,IAAI,GAEb,GARc,CASvB,CAeA,SAAS,gBAAgB,EAAU,MAAO,EAAU,GAAI,EAAU,CAAC,GACjE,MAAM,EAAM,SAAS,cAAc,GAcnC,OAbA,WAAW,EAAK,GACZ,GAA8B,iBAAZ,GACpB,OAAO,KAAK,GAAS,SAAS,IACf,UAAT,EACF,gBAAgB,EAAK,EAAQ,IACX,YAAT,EACT,WAAW,EAAK,EAAQ,IAExB,EAAI,aAAa,EAAM,EAAQ,IAE1B,QAGJ,CACT,CAWA,SAAS,eACP,EAAQ,SACR,EAAU,GACV,GAAY,EACZ,EAAU,CAAC,GAEX,MAAM,EAAO,gBAAgB,SAAU,EAAS,GAGhD,OAFI,EAAW,EAAK,UAAY,EAC3B,EAAK,YAAc,EACjB,CACT,CAYA,SAAS,cAAc,GACrB,MAAsB,cAAd,EAAM,KACR,EAAM,SAAyB,cAAd,EAAM,KACvB,EAAM,UAA0B,cAAd,EAAM,KACxB,EAAM,QAAwB,cAAd,EAAM,KACtB,EAAM,SAAyB,cAAd,EAAM,KACT,WAAd,EAAM,KACN,EAAM,SAAyB,WAAd,EAAM,KACvB,EAAM,UAA0B,WAAd,EAAM,KACxB,EAAM,QAAwB,WAAd,EAAM,KACtB,EAAM,SAAyB,WAAd,EAAM,KACvB,EAAM,SAAuC,MAA5B,EAAM,IAAI,eAC3B,EAAM,UAAwC,MAA5B,EAAM,IAAI,eAC5B,EAAM,QAAsC,MAA5B,EAAM,IAAI,eAC1B,EAAM,SAAuC,MAA5B,EAAM,IAAI,aACnC,CAYA,SAAS,YAAY,GACnB,IAAI,EACJ,QAAK,EAAI,SAAS,OAIhB,EAHG,EAAI,WAAW,YAAe,EAAI,WAAW,YAGpC,EAFA,UAAU,IAIjB,EACT,CAYA,SAAS,gBAAgB,GACvB,MAAM,EAAY,CAAC,MAAO,IAAK,KAAM,MACrC,IAAI,EAAY,EAChB,MAAQ,EAAU,SAAS,EAAU,UACnC,EAAY,EAAU,WAExB,OAAO,CACT,CAaA,SAAS,aAAa,EAAM,GAC1B,GAAI,EAAK,WAAa,EACpB,OAAO,EAET,GAAI,EAAK,WAAW,WAAa,EAC/B,OAAO,EAAK,WAEd,IAAI,GAAa,EAWjB,OAVI,EAAK,UACP,MAAM,KAAK,EAAK,UAAU,SAAS,IACjC,MAAM,EAAY,aAAa,EAAO,GACtC,QAAI,IACF,EAAa,EACN,EAEG,IAGT,CACT,CAaA,SAAS,kBAAkB,EAAK,GAC9B,OAAM,aAAe,YAElB,EAAK,SAAS,EAAI,aAAe,EAAK,SAAS,EAAI,YACjD,EAAI,aAAa,GAAM,GAE9B,CAUA,SAAS,yBAAyB,EAAK,EAAO,GAAU,GACtD,EAAM,SAAS,GACf,EAAI,kBACJ,EAAI,SAAS,EACf,CAEA,IAAI,QAAW,WACb,MAAM,EAAM,CACZ,KAAW,eAUX,OATA,EAAI,IAAM,GAAG,EAAI,YACjB,EAAI,YAAc,GAAG,EAAI,eACzB,EAAI,UAAY,GAAG,EAAI,aACvB,EAAI,OAAS,GAAG,EAAI,UACpB,EAAI,MAAQ,GAAG,EAAI,cACnB,EAAI,SAAW,GAAG,EAAI,YACtB,EAAI,OAAS,GAAG,EAAI,eACpB,EAAI,SAAW,GAAG,EAAI,iBACtB,EAAI,KAAO,GAAG,EAAI,YACX,CACT,CAbc,GAmCV,aAAe,CAYjB,IAAA,CAAK,EAAc,GACjB,KAAK,KAAO,gBACV,MACA,CAAC,QAAQ,SAAU,QAAQ,WAE7B,KAAK,OAAS,gBAAgB,QAAS,QAAQ,MAAO,CAAE,KAAM,SAC9D,KAAK,UAAY,eAAe,iBAAkB,QAAQ,KAAK,GAE/D,KAAK,KAAK,YAAY,KAAK,QAC3B,KAAK,KAAK,YAAY,KAAK,WAE3B,KAAK,UAAU,iBAAiB,QAAS,KAAK,KAAK,KAAK,OACxD,KAAK,OAAO,iBAAiB,WAAY,KAAK,oBAAoB,KAAK,OACvE,KAAK,aAAe,EAChB,GAAa,aAAqB,aAAa,KAAK,SAAS,EACnE,EAcA,mBAAA,CAAoB,GACJ,UAAV,EAAE,MAEF,KAAK,aAC0B,mBAArB,KAAK,aAEf,KAAK,YAAY,KAAK,KAAM,KAAK,OAAO,MAAO,KAAK,cAEjD,KAAK,oBACR,KAAK,OAGX,EAUA,cAAA,CAAe,GACT,GAAsC,mBAAhB,IACxB,KAAK,YAAc,EAEvB,EAMA,gBAAA,GACE,KAAK,YAAc,IACrB,EAOA,QAAA,GACE,OAAO,KAAK,OAAO,KACrB,EAWA,OAAA,CAAQ,EAAc,IACpB,KAAK,OAAO,YAAc,EAC1B,KAAK,KAAK,UAAU,OAAO,QAAQ,UAEnC,MAAM,EAAM,OAAO,eACnB,KAAK,aAAe,EAAI,WAAW,GACrB,SAAS,cACjB,WAAW,KAAK,QAItB,WAHA,WACE,KAAK,OAAO,OACd,EACsB,KAAK,MAAO,IACpC,EAUA,KAAA,CAAM,GACJ,KAAK,OAAO,MAAQ,GAChB,IACF,KAAK,OAAO,YAAc,EAE9B,EAMA,IAAA,CAAK,GAAc,GAEjB,GAAI,KAAK,KAAK,UAAU,SAAS,QAAQ,UAAW,OAAO,EAI3D,GAHA,KAAK,KAAK,UAAU,IAAI,QAAQ,UAChC,KAAK,OAAO,MAAQ,GACpB,KAAK,mBACD,KAAK,aAAc,CACrB,MAAM,EAAM,OAAO,eACb,EAAQ,EAAI,WAAW,GAC7B,EAAI,YAAY,GAChB,EAAI,SAAS,KAAK,aACpB,CAQA,OANE,GACG,KAAK,cACwB,mBAAtB,KAAK,cAEf,KAAK,gBAEA,CACT,EAMA,IAAA,GACE,OAAO,KAAK,IACd,EAOA,QAAA,GAEE,OADqB,KAAK,KAAK,wBACX,KACtB,EAOA,SAAA,GAEE,OADqB,KAAK,KAAK,wBACX,MACtB,EASA,QAAA,CAAS,GAEP,OADA,EAAO,YAAY,KAAK,QACjB,CACT,GAIF,MAAM,cAAgB,EAchB,YAAc,CAUlB,WAAA,CAAY,EAAQ,GAalB,OAZA,KAAK,QAAU,EACf,KAAK,OAAS,EACd,KAAK,cA3Ba,EA4BlB,KAAK,KAAO,gBACV,MACA,CAAC,QAAQ,KAAM,QACf,CAAE,iBAAiB,IAErB,KAAK,QAAU,gBAAgB,MAAO,QAAQ,QAC9C,KAAK,KAAK,YAAY,KAAK,SAC3B,KAAK,MAAQ,OAAO,OAAO,cAC3B,KAAK,OAAO,KAAK,YAAY,KAAK,MAC3B,IACT,EAOA,eAAA,GAEE,OAAO,GADY,KAAK,QAAQ,SACD,MACjC,EAOA,iBAAgB,IACP,KAQT,WAAA,GACE,KAAK,KAAK,MAAM,MAAQ,GAAG,KAAK,MAAM,eACtC,KAAK,KAAK,MAAM,OAAS,GAAG,KAAK,MAAM,gBACvC,KAAK,QAAQ,UAAU,IAAI,QAAQ,OACrC,EAMA,cAAA,GACE,KAAK,QAAQ,UAAU,OAAO,QAAQ,QACtC,KAAK,KAAK,MAAM,MAAQ,GAAG,KAAK,sBAChC,KAAK,KAAK,MAAM,OAAS,GAAG,KAAK,sBACnC,EAEA,YAAA,CAAa,GACX,MAAM,EAAM,OAAO,eACnB,KAAK,aAAe,EAAI,WAAW,GACnC,KAAK,MAAM,QAAQ,EACrB,EAEA,SAAA,GAEA,EAOA,eAAA,GACE,MAAM,EAAM,OAAO,eACnB,KAAK,aAAe,EAAI,WAAW,GACnC,IAAI,EAAO,KAET,EADE,KAAK,aAAa,UACb,KAAK,aAAa,wBAAwB,wBAE1C,KAAK,aAAa,wBAE3B,MAAM,EAAc,KAAK,KAAK,wBACZ,EAAK,OAAS,KAAK,cAAgB,EAAY,QAChD,OAAO,YACtB,KAAK,KAAK,MAAM,IAAS,EAAK,KAAO,KAAK,cAAgB,EAAY,QAAhD,KAEtB,KAAK,KAAK,MAAM,IAAM,GAAG,EAAK,OAAS,KAAK,kBAE9C,KAAK,KAAK,MAAM,KAAO,GAAG,EAAK,QACjC,EAWA,WAAA,GAKE,OAJA,KAAK,MAAM,MAAK,GAChB,KAAK,KAAK,UAAU,OAAO,QAC3B,KAAK,kBACL,KAAK,kBACE,CACT,EAOA,IAAA,GAKE,OAJA,KAAK,aAAe,KACpB,KAAK,MAAM,MAAK,GAChB,KAAK,iBACL,KAAK,KAAK,UAAU,IAAI,UACpB,KAAK,KAAK,UAAU,SAAS,OAInC,EAOA,IAAA,GACE,OAAO,KAAK,IACd,EAEA,QAAA,CAAS,GACP,OAAO,KAAK,KAAK,SAAS,EAC5B,GAmBF,IAAI,cAAgB,CAelB,IAAA,CAAK,EAAS,EAAO,EAAS,GAI5B,OAHA,KAAK,MAAQ,eAAe,EAAS,QAAQ,KAAK,EAAM,CAAE,UAC1D,KAAK,eAAe,GACpB,KAAK,SAAS,GACP,IACT,EAUA,cAAA,CAAe,GACb,MAA2B,mBAAhB,EACF,MAEL,KAAK,cACP,KAAK,MAAM,oBAAoB,QAAS,KAAK,cAE/C,KAAK,YAAc,EACnB,KAAK,MAAM,iBAAiB,QAAS,KAAK,aACnC,KAAK,YACd,EASA,QAAA,CAAS,EAAU,IACb,MAAM,QAAQ,GAChB,EAAQ,SAAS,IACf,KAAK,MAAM,UAAU,IAAI,EAAM,IAGjC,KAAK,MAAM,UAAU,IAAI,EAE7B,EASA,WAAA,CAAY,EAAU,IAChB,MAAM,QAAQ,GAChB,EAAQ,SAAS,IACf,KAAK,MAAM,UAAU,OAAO,EAAM,IAGpC,KAAK,MAAM,UAAU,OAAO,EAEhC,EAOA,OAAA,GACE,KAAK,MAAM,UAAW,EACtB,KAAK,SAAS,QAAQ,YACxB,EAOA,MAAA,GACE,KAAK,MAAM,UAAW,EACtB,KAAK,YAAY,QAAQ,YAC3B,EAOA,UAAA,GACE,KAAK,MAAM,UAAU,IAAI,QAAQ,UACnC,EAOA,YAAA,GACE,KAAK,MAAM,UAAU,OAAO,QAAQ,UACtC,EAQA,IAAA,GACE,OAAO,KAAK,KACd,EASA,QAAA,CAAS,GACH,GAAQ,aAAgB,aAC1B,EAAK,YAAY,KAAK,OAE1B,GAIF,MAAM,YAAc,OAAO,OAAO,aAWlC,YAAY,KAAO,SAAc,EAAQ,GASvC,OARA,KAAK,YAAY,EAAQ,GACzB,KAAK,MAAM,KAAK,KAAK,eAAe,KAAK,MAAO,KAAK,MACrD,KAAK,oBACL,KAAK,KAAK,iBAAiB,SAAU,IAC/B,EAAE,SAAW,KAAK,MAAQ,EAAE,SAAW,KAAK,SAC9C,KAAK,MACP,IAEK,IACT,EAMA,YAAY,kBAAoB,WAC9B,KAAK,QAAU,OAAO,OAAO,eAC7B,KAAK,QAAQ,KAAK,WAAY,iBAAkB,KAAK,OAAO,cAAc,KAAK,KAAK,QAAS,KAAK,SAClG,KAAK,UAAY,OAAO,OAAO,eAC/B,KAAK,UAAU,KAAK,WAAY,sBAAuB,KAAK,OAAO,mBAAmB,KAAK,KAAK,QAAS,KAAK,SAC9G,KAAK,WAAa,OAAO,OAAO,eAChC,KAAK,WAAW,KAAK,IAAK,8BAA+B,KAAK,OAAO,YAAY,KAAK,KAAK,QAAS,KAAK,SACzG,KAAK,QAAU,OAAO,OAAO,eAC7B,KAAK,QAAQ,KAAK,KAAM,2BAA4B,KAAK,eAAe,KAAK,MAAO,KAAK,QAC3F,EAUA,YAAY,sBAAwB,WAEhC,aAAa,KAAK,aAAa,wBAAyB,OACrD,aAAa,KAAK,aAAa,wBAAyB,OAE3D,KAAK,QAAQ,UACb,KAAK,QAAQ,UACb,KAAK,UAAU,YAEf,KAAK,QAAQ,SACb,KAAK,QAAQ,SACb,KAAK,UAAU,SAEnB,EAUA,YAAY,iBAAmB,SAA0B,GACvD,MACM,EAAc,aADN,EAAI,WAAW,GACU,wBAAyB,KAC5D,GAAe,EAAI,aAAa,GAAa,IAC/C,KAAK,QAAQ,aACb,KAAK,QAAQ,YAAc,IAE3B,KAAK,QAAQ,YAAc,KAC3B,KAAK,QAAQ,eAEjB,EAOA,YAAY,eAAiB,WACvB,KAAK,QAAQ,aACf,KAAK,OAAO,WAAW,KAAK,QAAQ,aACpC,KAAK,QAAQ,YAAc,KAC3B,KAAK,QAAQ,iBAEb,KAAK,MAAM,eAAe,KAAK,OAAO,UACtC,KAAK,cACL,KAAK,MAAM,QAAQ,kBAEvB,EAUA,YAAY,QAAU,SAAiB,EAAM,MAC3C,OAAM,aAAe,aACjB,kBAAkB,EAAK,KAAK,QAChC,KAAK,aAAe,EAAI,WAAW,GACnC,KAAK,iBAAiB,GACtB,KAAK,wBACE,KAAK,eACd,EAGA,MAAM,cAAgB,OAAO,OAAO,aAWpC,cAAc,KAAO,SAAc,EAAQ,GAMzC,OALA,KAAK,YAAY,EAAQ,GACzB,KAAK,cAAgB,GACrB,KAAK,oBACL,KAAK,MAAM,KAAK,KAAK,eAAe,KAAK,MAAO,KAAK,MACrD,KAAK,MAAM,OAAO,GAAK,QAChB,IACT,EAQA,cAAc,kBAAoB,WAChC,KAAK,OAAS,OAAO,OAAO,eAC5B,KAAK,OAAO,KAAK,MAAO,kBAAmB,KAAK,gBAAgB,KAAK,MAAO,KAAK,SACjF,KAAK,QAAU,OAAO,OAAO,eAC7B,KAAK,QAAQ,KAAK,KAAM,2BAA4B,KAAK,OAAO,WAAW,KAAK,KAAK,QAAS,KAAK,QACrG,EAOA,cAAc,sBAAwB,WACpC,MAAM,EAAM,OAAO,eACf,KAAK,OAAO,QAAQ,EAAI,YAC1B,KAAK,QAAQ,UAEb,KAAK,QAAQ,QAEjB,EAMA,cAAc,gBAAkB,WAC9B,MAAM,EAAM,OAAO,eACnB,KAAK,aAAe,EAAI,WAAW,GACnC,KAAK,MAAM,eAAe,KAAK,YAAY,KAAK,OAChD,KAAK,MAAM,oBAAqB,EAChC,KAAK,cACL,KAAK,MAAM,QAAQ,uBACrB,EAOA,cAAc,eAAiB,WAC7B,KAAK,OAAS,KACd,KAAK,gBACP,EAWA,cAAc,YAAc,WACrB,KAAK,QAQR,KAAK,OAAO,YAAY,KAAK,OAAQ,KAAK,MAAM,WAAY,KAAK,aAAa,gBAC9E,KAAK,MAAM,OACX,KAAK,iBACL,KAAK,SAVL,KAAK,OAAS,YAAY,KAAK,MAAM,YAChC,KAAK,OAGR,KAAK,MAAM,MAAM,qBAFjB,KAAK,MAAM,MAAM,wBAUvB,EAQA,cAAc,QAAU,WAEtB,OADA,KAAK,wBACE,KAAK,aACd,EAoBA,IAAI,WAAa,CAQf,YAAA,CAAa,EAAM,GAyBjB,OAxBA,KAAK,KAAO,EACZ,KAAK,QAAU,EACf,KAAK,kBACL,SAAS,YAAY,6BAA6B,EAAO,KAAK,QAAQ,UACtE,KAAK,UAAY,gBACf,MACA,KAAK,QAAQ,KACb,CAAE,MAAO,KAAK,QAAQ,iBAExB,KAAK,UAAU,aAAa,mBAAmB,GAC/C,KAAK,KAAK,OAAO,KAAK,WACtB,KAAK,yBAEL,KAAK,YAAc,YAAY,KAAK,KAAM,KAAK,SAC/C,KAAK,cAAgB,cAAc,KAAK,KAAM,KAAK,SAEnD,KAAK,KAAK,iBAAiB,QAAS,KAAK,aAAa,KAAK,OAC3D,KAAK,KAAK,iBAAiB,UAAW,KAAK,eAAe,KAAK,OAC/D,KAAK,KAAK,iBAAiB,QAAS,KAAK,aAAa,KAAK,OAC3D,KAAK,KAAK,iBAAiB,QAAS,KAAK,eAAe,KAAK,OAC7D,KAAK,KAAK,iBAAiB,UAAW,KAAK,eAAe,KAAK,OAE/D,SAAS,iBAAiB,kBAAmB,KAAK,iBAAiB,KAAK,OACxE,SAAS,iBAAiB,SAAU,KAAK,cAAc,KAAK,KAAK,KAAK,gBAC/D,IACT,EAEA,eAAA,GASE,OARA,KAAK,QAAU,CAAC,EAChB,KAAK,cAAgB,aACrB,KAAK,QAAQ,KAAO,CAAC,KAAK,eACU,eAAhC,KAAK,QAAQ,gBACf,KAAK,QAAQ,KAAK,KAAK,KAAK,QAAQ,gBAEtC,KAAK,QAAQ,YAAc,mBAC3B,KAAK,QAAQ,iBAAmB,wBACzB,KAAK,OACd,EAiBA,sBAAA,GACO,KAAK,gBACR,KAAK,cAAgB,KAAK,qBAE5B,KAAK,cAAc,YAAc,GACjC,KAAK,UAAU,aAAa,KAAK,cAAe,KAAK,UAAU,YAC/D,MAAM,EAAM,OAAO,eACb,EAAQ,SAAS,cAMvB,OALA,EAAM,SAAS,KAAK,cAAe,GACnC,EAAM,OAAO,KAAK,cAAe,GACjC,EAAI,kBACJ,EAAI,SAAS,GACb,OAAO,MAAQ,KAAK,cACb,KAAK,aACd,EAQA,8BAAA,GACE,MAAM,EAAM,OAAO,eAEjB,EAAI,aACD,KAAK,QAAQ,EAAI,aACjB,EAAI,WAAW,UAAU,SAAS,KAAK,QAAQ,cAChB,KAA/B,EAAI,WAAW,cAElB,EAAI,WAAW,UAAY,GAE/B,EAOA,iBAAA,GACO,MAAM,QAAQ,KAAK,QAAQ,gBAC9B,KAAK,QAAQ,aAAe,MAAM,KAAK,QAAQ,eAE5C,KAAK,QAAQ,aAAa,SAAS,KAAK,QAAQ,cACnD,KAAK,QAAQ,aAAa,KAAK,KAAK,QAAQ,aAE9C,MAAM,EAAa,CACjB,MAAO,KAAK,QAAQ,aACpB,QAAS,KAAK,QAAQ,cAExB,OAAO,gBAAgB,KAAK,QAAQ,SAAU,GAAI,EACpD,EAYA,sBAAA,CAAuB,GACrB,MAAM,EAAQ,KAAK,QAAQ,aACrB,EAAY,gBAAgB,MAAO,KAAK,QAAQ,iBAAkB,CAAE,UAI1E,OAHI,GAAa,aAAqB,SACpC,EAAU,YAAY,GAEjB,CACT,EASA,gBAAA,GACE,MAAM,EAAM,OAAO,eACnB,IACE,MAAM,EAAQ,EAAI,WAAW,GACvB,EAAc,gBAAgB,EAAI,YAClC,EAAY,gBAAgB,EAAI,WAClC,GAAa,EAAY,YACzB,GAAW,EAAU,YACrB,EAAM,yBAAyB,EAAM,wBAAwB,WACnE,CAAE,MAAO,GACP,OAAO,IACT,CACA,OAAO,IACT,EAeA,sBAAA,CAAuB,GACrB,MACM,EAAU,gBADJ,OAAO,eACiB,YACpC,IAAI,EAAc,KAalB,MAZc,cAAV,EAAE,IACJ,EAAc,EAAQ,gBACH,WAAV,EAAE,MACX,EAAc,EAAQ,aAGtB,GACG,EAAY,UAAU,SAAS,KAAK,QAAQ,oBAE/C,EAAE,iBACF,EAAY,WAAW,YAAY,KAE9B,CACT,EAYA,sBAAA,CAAuB,GACrB,MAAM,EAAM,OAAO,eACb,EAAU,gBAAgB,EAAI,YACpC,GAAI,EAAQ,UAAU,SAAS,KAAK,QAAQ,kBAAmB,CAC7D,IAAI,EAAa,EAAQ,YACX,YAAV,EAAE,KAA+B,cAAV,EAAE,IAC3B,EAAa,EAAQ,gBACF,cAAV,EAAE,KAAiC,eAAV,EAAE,IACpC,EAAa,EAAQ,cAEpB,GACE,EAAW,UAAU,SAAS,KAAK,QAAQ,mBAE5C,EAAW,UAAU,SAAS,KAAK,QAAQ,cACxC,EAAW,YAAY,OAAS,KAGrC,EAAa,KAAK,oBAClB,KAAK,UAAU,aAAa,EAAY,EAAQ,cAElD,EAAE,iBACF,EAAI,SAAS,EAAY,EAC3B,CACF,EAgBA,aAAA,GACc,OAAO,yBACA,WACjB,SAAS,YAAY,QAAQ,EAEjC,EAMA,kBAAA,GACc,OAAO,yBACA,WACjB,SAAS,YAAY,UAAU,EAEnC,EAUA,WAAA,GACE,MAAM,EAAM,OAAO,eACnB,IAEI,EACA,EACA,EAJA,EAAa,gBAAgB,EAAI,YAKrC,GAJA,EAAW,UAAY,EAAW,UAAU,QAAQ,WAAY,IAI5D,aAAe,UAAW,CAED,OAAvB,EAAW,SACb,EAAU,KAAK,QAAQ,SACvB,EAAQ,KAAK,QAAQ,aACrB,EAAQ,KAAK,QAAQ,cACW,QAAvB,EAAW,SAA4C,MAAvB,EAAW,SACpD,EAAU,KACV,EAAQ,KAAK,QAAQ,kBACrB,EAAQ,KAAK,QAAQ,oBAErB,EAAU,KACV,EAAQ,KAAK,QAAQ,kBACrB,EAAQ,KAAK,QAAQ,mBAEvB,MAAM,EAAa,SAAS,YAAY,eAAe,EAAO,GAC9D,GAAI,EAAY,CACd,EAAa,gBAAgB,EAAI,YACjC,gBAAgB,EAAY,GAC5B,WAAW,EAAY,GACvB,MAAM,EAAQ,EAAI,WAAW,GAC7B,EAAM,WAAW,EAAI,WACrB,EAAM,UACR,CACA,OAAO,CACT,CACA,OAAO,CACT,EAgBA,QAAA,CAAS,EAAQ,GACf,MAAM,EAAM,YAAY,GACxB,IAAK,EAAK,OAAO,EACjB,MAAM,EAAO,gBAAgB,KAI7B,OAHA,EAAK,KAAO,EACZ,EAAa,iBAAiB,GAC9B,yBAAyB,OAAO,eAAgB,GACzC,CACT,EASA,UAAA,CAAW,GACT,MAAM,EAAM,OAAO,eACnB,KAAK,aAAe,EAAI,WAAW,GACnC,MAAM,EAAQ,SAAS,cACvB,EAAM,WAAW,GACjB,MAAM,EAAY,SAAS,eAAe,EAAM,aAChD,EAAM,iBACN,EAAM,WAAW,GACjB,yBAAyB,EAAK,EAChC,EAaA,WAAA,CAAY,EAAK,EAAK,GACiB,IAAjC,EAAY,UAAU,QACxB,EAAY,OAAO,SAAS,cAAc,OAE5C,MAAM,EAAM,OAAO,eACb,EAAM,gBAAgB,MAAO,KAAK,QAAQ,SAAU,CAAE,MAAK,MAAK,MAAO,KAAK,QAAQ,WACpF,EAAU,KAAK,yBACrB,EAAI,QAAU,EACd,EAAI,QAAU,WACZ,EAAI,QAAQ,WAAW,YAAY,EAAI,SACvC,MAAM,wBACR,EACI,IAAgB,KAAK,gBACvB,KAAK,cAAgB,GAEvB,EAAQ,YAAY,GACpB,EAAY,WAAW,aAAa,EAAS,GAC7C,MAAM,EAAQ,SAAS,cACvB,EAAM,mBAAmB,GAEzB,yBAAyB,EAAK,GAAO,EACvC,EAOA,UAAA,GACE,MAAM,EAAM,OAAO,eACnB,IAAI,EAAQ,EAAI,WAAW,GAC3B,MAAM,EAAc,gBAAgB,EAAM,gBAC1C,GAAI,IAAgB,KAAK,cAAe,OAAO,EAC/C,MAAM,EAAO,SAAS,cAAc,MAC9B,EAAU,KAAK,yBAOrB,OANA,EAAQ,YAAY,GACpB,EAAY,WAAW,aAAa,EAAS,GAC7C,EAAQ,SAAS,cACjB,EAAM,mBAAmB,GACzB,yBAAyB,EAAK,GAAO,GACrC,KAAK,cAAc,QACZ,CACT,EAyBA,gBAAA,CAAiB,GACf,GAAe,oBAAX,EAAE,KAA4B,OAAO,EACzC,MAAM,EAAM,OAAO,eASnB,OAPG,kBAAkB,EAAK,KAAK,QAAU,EAAI,aACxC,kBAAkB,EAAK,KAAK,YAAY,QAE3C,KAAK,YAAY,QAAQ,GAEzB,KAAK,YAAY,QAEZ,CACT,EAcA,YAAA,CAAa,GAEX,GADA,EAAE,iBACc,WAAX,EAAE,KAAkB,OAAO,EAChC,MAAM,EAAO,EAAE,cAAc,QAAQ,cAErC,OADA,SAAS,YAAY,cAAc,EAAO,IACnC,CACT,EAYA,cAAA,CAAe,GACb,EAAE,iBACF,MAAM,EAAM,OAAO,eACb,EAAc,gBAAgB,EAAI,WAClC,EAAS,KAAK,oBACpB,EAAY,WAAW,aAAa,EAAQ,EAAY,aACxD,MAAM,EAAe,EAAI,WAAW,GACpC,GAAI,EAAa,UACf,IACE,EAAa,aAAa,EAAY,aACC,IAAnC,EAAa,WAAW,QAC1B,EAAO,OAAO,EAAa,gBAE/B,CAAE,MAAO,GACP,EAAO,YAAc,EACvB,CAEgC,IAA9B,EAAO,YAAY,SACrB,EAAO,OAAO,SAAS,cAAc,OACrC,EAAO,OAAO,SAAS,eAAe,MAExC,EAAa,iBAEwB,IAAnC,EAAY,YAAY,QACrB,EAAa,0BAA4B,EAAa,gBAEzD,EAAY,OAAO,SAAS,cAAc,OAE5C,MAAM,EAAQ,SAAS,cACvB,EAAM,mBAAmB,GACzB,yBAAyB,EAAK,GAAO,GACrC,EAAO,YACP,MAAM,EAAO,EAAO,wBAOpB,OANI,EAAK,KAAO,OAAO,aACrB,OAAO,OAAO,CACZ,IAAK,EAAK,IAAM,OAAO,QACvB,SAAU,YAGP,CACT,EAYA,cAAA,CAAe,GACb,MAAM,EAAM,OAAO,eACf,cAAc,KAEJ,cAAV,EAAE,KACsB,IAArB,EAAI,cACJ,EAAI,aAAe,EAAI,WAIhB,WAAV,EAAE,KACC,EAAI,eAAiB,EAAI,WAAW,YAAY,QAChD,EAAI,aAAe,EAAI,YAJ1B,KAAK,uBAAuB,GASlB,UAAV,EAAE,KAAmB,KAAK,UAAU,SAAS,EAAE,SACjD,KAAK,eAAe,GAEtB,KAAK,uBAAuB,GAC5B,MAAM,EAAQ,EAAI,WAAW,GACvB,EAAc,gBAAgB,EAAM,gBACrC,KAAK,YAAY,SAAS,IAAiB,KAAK,cAAc,SAAS,KAC1E,KAAK,YAAc,EACnB,KAAK,uBAAyB,KAAK,YAAY,gBAC/C,KAAK,WAAa,EAAM,YAE5B,EAWA,YAAA,CAAa,GACX,GAAI,cAAc,GAAI,CACpB,MAAM,EAAM,OAAO,eAEY,KAA7B,KAAK,UAAU,WACiB,SAA7B,KAAK,UAAU,WACf,EAAI,aAAe,KAAK,WAE3B,KAAK,yBAEP,KAAK,gCACP,CACA,IACE,KAAK,mBACL,KAAK,eAAe,GACpB,KAAK,gBACP,CAAE,MAAO,GACP,OAAO,IACT,CACA,OAAO,CACT,EAUA,cAAA,CAAe,GACb,GAAI,GAAK,KAAK,cAAc,KAAK,SAAS,EAAE,QAAS,OAAO,EAC5D,MAAM,EAAM,OAAO,eAEnB,OADA,KAAK,cAAc,UACf,EAAI,cACF,EAAI,YAA6C,KAA/B,EAAI,WAAW,aACjC,kBAAkB,EAAK,KAAK,cAAc,SAE9C,KAAK,cAAc,WACZ,EAGX,EAWA,cAAA,GACE,MAAM,EAAM,OAAO,eACnB,IAAI,EAAU,KACd,IACE,EAAU,gBAAgB,EAAI,WAChC,CAAE,MAAO,GACP,OAAO,CACT,CACA,GAAI,KAAK,YAAY,SAAS,IAAY,KAAK,cAAc,SAAS,GACpE,OAAO,EAET,MAAM,EAAQ,EAAI,WAAW,GAC7B,IACG,EAAQ,UAAU,SAAS,KAAK,QAAQ,cAClB,OAApB,EAAQ,SACY,OAApB,EAAQ,QAEX,GACE,KAAK,aACF,KAAK,UAAU,SAAS,KAAK,cAC7B,KAAK,YAAY,UAAU,SAAS,KAAK,QAAQ,aAEpD,EAAI,SAAS,KAAK,YAAa,KAAK,gBAC/B,CAEL,GADA,EAAU,KAAK,wBACV,EAAS,OAAO,EACrB,MAAM,EAAa,KAAK,oBACpB,EAAQ,YACV,KAAK,UAAU,aAAa,EAAY,EAAQ,aAEhD,KAAK,UAAU,YAAY,GAE7B,EAAM,mBAAmB,GACzB,EAAM,UAAS,EACjB,CAEF,OAAO,CACT,EAyBA,IAAA,CAAK,GACH,MAAM,EAAS,IAAI,UACnB,IAAI,EAAO,EACe,iBAAf,IACT,EAAO,EAAO,gBAAgB,EAAY,cAE5C,IAAI,EAAc,KAClB,IACE,EAAc,EAAK,KAAK,UAC1B,CAAE,MAAO,GACP,OAAO,CACT,CAMA,OALI,GAAe,EAAY,UAAU,SAAS,KAAK,iBACrD,KAAK,KAAK,YAAY,KAAK,WAC3B,KAAK,KAAK,YAAY,GACtB,KAAK,UAAY,GAEZ,KAAK,KAAK,SAAS,EAC5B,EAUA,IAAA,CAAK,GAAW,GACd,MAAM,EAAW,KAAK,UAAU,WAAU,GAE1C,OADA,EAAS,aAAa,kBAAmB,GAClC,EAAS,SAClB,EAOA,UAAA,GAAe,OAAO,KAAK,WAAa,EAWxC,OAAA,CAAQ,GACN,OAAI,KAAK,UAAU,SAAS,KAAO,CAIrC,GAGE,aAAe,o/GAInB,MAAM,sBAAwB,CAC5B,aAAc,MACd,YAAa,OACb,OAAQ,WACR,QAAS,aACT,YAAa,UACb,cAAe,wBACf,QAAS,OACT,MAAO,QAGH,oBAAsB,CAC1B,YAAa,UACb,YAAa,QACb,cAAe,OACf,eAAgB,QAGZ,yBAA2B,OAAO,OAAO,CAAC,EAAG,qBACnD,yBAAyB,aAAe,OAExC,MAAM,yBAA2B,OAAO,OAAO,CAAC,EAAG,qBACnD,yBAAyB,aAAe,SAExC,MAAM,gBAAkB,OAAO,OAAO,CAAC,EAAG,qBAC1C,gBAAgB,aAAe,OAG/B,MAAM,eAAiB,CACrB,SAAU,IACV,aAAc,GACd,aAAc,oBACd,eAAgB,GAChB,eAAgB,sBAChB,kBAAmB,GACnB,kBAAmB,yBACnB,kBAAmB,GACnB,kBAAmB,yBACnB,SAAU,GACV,SAAU,gBACV,iBAAkB,uBAYpB,SAAS,UAAU,EAAM,EAAc,CAAC,GACtC,MAAM,EAAW,WACf,MAAM,EAAgB,OAAO,OAAO,gBAMpC,OALI,GAAsC,iBAAhB,GACxB,OAAO,KAAK,GAAa,SAAS,IAChC,EAAc,GAAU,EAAY,EAAO,IAGxC,CACT,CARgB,GASV,EAAgB,SAAS,cAAc,SACvC,EAAkB,aAAa,QAAQ,mBAAoB,EAAQ,kBACzE,EAAc,YAAY,SAAS,eAAe,IAClD,SAAS,qBAAqB,QAAQ,GAAG,YAAY,GAIrD,MAAM,EAAS,OAAO,OAAO,YAG7B,OAFA,EAAO,aAAa,EAAM,GAC1B,EAAK,UAAU,IAAI,WACZ,CACL,KAAM,EAAO,KAAK,KAAK,GACvB,KAAM,EAAO,KAAK,KAAK,GAE3B,gBAEe","file":"/npm/writefree@1.2.12/build/js/writefree.es6.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/writefree@1.2.12/build/js/writefree.es6.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","/**\n * addStyleFromObj - Adds inline-style to a given HTML Element from the given\n *  style Object.\n *\n * @param {Element} $el   The HTML Element to which the styles will be added.\n * @param {object} styleObj The object which contains the styles. Must be\n *  formatted in the format { 'property' : 'value' } where 'property' is the\n *  CSS property and 'value' is the value to which it should be set.\n *  E.g. { color : 'purple' } will set $el's color to purple.\n *\n * @returns {Element} If the given styleObj is not an object or is null or\n *  undefined, will return false. If styles are successfully added, returns the\n *  HTML Element.\n */\nfunction addStyleFromObj($el, styleObj) {\n  if (\n    styleObj === null\n    || styleObj === undefined\n    || (!(typeof styleObj === 'object'))\n  ) { return false; }\n  let styleString = '';\n  Object.keys(styleObj).forEach((prop) => {\n    styleString += `${prop}: ${styleObj[prop]};`;\n  });\n  $el.setAttribute('style', styleString);\n  return $el;\n}\n\n/**\n * addClasses - Add classes to an HTML Element.\n *\n * @param {Element} $el  The HTML Element to which the classes will be added.\n * @param {string || Array} klasses A single string or an array of strings\n *  representing the classes to be added to $el.\n *\n * @returns {Element} The original $el with classes attached.\n */\nfunction addClasses($el, klasses) {\n  if (!klasses) return $el;\n  if (Array.isArray(klasses)) {\n    klasses.forEach((klass) => {\n      if (typeof klass === 'string' && klass.length > 0) $el.classList.add(klass);\n    });\n  } else {\n    $el.classList.add(klasses);\n  }\n  return $el;\n}\n\n/**\n * generateElement - Quickly generates an HTML element with given tagName,\n *  classes, and id.\n *\n * @param {string} [tagName=div] The tag name to use for the element.\n * @param {string|string[]}  [klasses=[]]  A single string or an array of\n *  strings representing the classes to be added to the element.\n * @param {object} [options={}] An optional object containing attributes to be\n *  added to the element. Each key must be a valid HTML attribute and the value\n *  must be a string.\n *\n * @returns {Element} The newly-created HTML element.\n */\nfunction generateElement(tagName = 'div', klasses = [], options = {}) {\n  const $el = document.createElement(tagName);\n  addClasses($el, klasses);\n  if (options && typeof options === 'object') {\n    Object.keys(options).forEach((attr) => {\n      if (attr === 'style') {\n        addStyleFromObj($el, options[attr]);\n      } else if (attr === 'klasses') {\n        addClasses($el, options[attr]);\n      } else {\n        $el.setAttribute(attr, options[attr]);\n      }\n      return null;\n    });\n  }\n  return $el;\n}\n\n/**\n * generateButton - Quickly generate a button element\n *\n * @param {string} [value=Button] Description\n * @param {array}  [klasses=[]]   Description\n * @param {string} [id=]          Description\n *\n * @returns {type} Description\n */\nfunction generateButton(\n  value = 'Button',\n  klasses = [],\n  innerHTML = false,\n  options = {},\n) {\n  const $btn = generateElement('button', klasses, options);\n  if (innerHTML) $btn.innerHTML = value;\n  else $btn.textContent = value;\n  return $btn;\n}\n\n/**\n * isDeletionKey - Determines whether the key in the given KeyboardEvent will\n *  delete any characters or words. Because of a peculiarity with FireFox, we\n *  must specifically test for metakeys (ctrl, shift, alt, and\n *  metaKey (Windows/Cmd)).\n *\n * @param {KeyboardEvent} event The KeyboardEvent to test.\n *\n * @returns {boolean} Returns true if the key is a deletion key, else false.\n */\nfunction isDeletionKey(event) {\n  return (event.key === 'Backspace')\n    || (event.ctrlKey && event.key === 'Backspace')\n    || (event.shiftKey && event.key === 'Backspace')\n    || (event.altKey && event.key === 'Backspace')\n    || (event.metaKey && event.key === 'Backspace')\n    || (event.key === 'Delete')\n    || (event.ctrlKey && event.key === 'Delete')\n    || (event.shiftKey && event.key === 'Delete')\n    || (event.altKey && event.key === 'Delete')\n    || (event.metaKey && event.key === 'Delete')\n    || (event.ctrlKey && event.key.toLowerCase() === 'x')\n    || (event.shiftKey && event.key.toLowerCase() === 'x')\n    || (event.altKey && event.key.toLowerCase() === 'x')\n    || (event.metaKey && event.key.toLowerCase() === 'x');\n}\n\n/**\n * validateURL - A very simple url validator that checks for at least one dot\n *  and for http or https. If it has a dot but no http(s), http:// will be\n *  prepended before the url is returned.\n *\n * @param {string} url The url to validate\n *\n * @returns {string || boolean} Returns the url (with http:// prepended if\n*   applicable) if url is valid. Else returns false.\n */\nfunction validateURL(url) {\n  let returnVal;\n  if (!url.includes('.')) return false;\n  if (!url.startsWith('http://') && !url.startsWith('https://')) {\n    returnVal = `http://${url}`;\n  } else {\n    returnVal = url;\n  }\n  return returnVal;\n}\n\n/**\n * findParentBlock - Finds the nearest ancestor of the given element which is\n *  of the types listed in parentTags.\n *\n * @param {Element} $el The Element of which to find an acceptable parent element.\n *\n * @returns {Element || boolean} If no Element of an acceptable type is found,\n *  or if the given $el isn't an HTML Element, will return false. Else it\n *  returns the found HTML Element.\n */\nfunction findParentBlock($el) {\n  const blockTags = ['DIV', 'P', 'H1', 'H2'];\n  let $returnEl = $el;\n  while (!blockTags.includes($returnEl.tagName)) {\n    $returnEl = $returnEl.parentNode;\n  }\n  return $returnEl;\n}\n\n/**\n * findNodeType - Finds a node of the given targetType. This function will\n *  first check the given node's parent, then itself, then its children. If no\n *  Element is found, the function will return false. Otherwise it will return\n *  the found Element.\n *\n * @param {Element} node    The Element to search.\n * @param {string} targetType The tagName of the target Element type.\n *\n * @returns {Element || boolean} The found HTML Element or false.\n */\nfunction findNodeType(node, targetType) {\n  if (node.nodeName === targetType) {\n    return node;\n  }\n  if (node.parentNode.nodeName === targetType) {\n    return node.parentNode;\n  }\n  let returnNode = false;\n  if (node.children) {\n    Array.from(node.children).forEach((child) => {\n      const foundNode = findNodeType(child, targetType);\n      if (foundNode) {\n        returnNode = foundNode;\n        return returnNode;\n      }\n      return false;\n    });\n  }\n  return returnNode;\n}\n\n/**\n * containsSelection - Check if a given selection contains a given node. Will\n *  return true even if given node is only partially contained within the\n *  selection.\n *\n * @param {Selection} sel  The selection to test.\n * @param {Element} node   The node to check for.\n *\n * @returns {boolean} Returns true if the given node is contained, at least in\n *  part, within the given selection. Otherwise, returns false.\n */\nfunction containsSelection(sel, node) {\n  if (!(sel instanceof Selection)) return false;\n  return (\n    (node.contains(sel.anchorNode) && node.contains(sel.focusNode))\n    || sel.containsNode(node, true)\n  );\n}\n\n/**\n * collapseSelectionToRange - Collapses the given Selection (sel) to the end of\n *  the given Range (range).\n *\n * @param {Selection} sel  The Selection to collapse.\n * @param {Range} range    The Range to use to collapse the selection.\n *\n */\nfunction collapseSelectionToRange(sel, range, toStart = false) {\n  range.collapse(toStart);\n  sel.removeAllRanges();\n  sel.addRange(range);\n}\n\nvar tbClass = (function tbClass() {\n  const obj = {};\n  obj.main = 'wf__toolbar';\n  obj.btn = `${obj.main}__btn`;\n  obj.btnDisabled = `${obj.btn}-disabled`;\n  obj.btnActive = `${obj.btn}-active`;\n  obj.btnCtn = `${obj.btn}-ctn`;\n  obj.input = `${obj.main}__input`;\n  obj.inputCtn = `${obj.input}-ctn`;\n  obj.hideUp = `${obj.main}-hide-up`;\n  obj.hideDown = `${obj.main}-hide-down`;\n  obj.wide = `${obj.main}-wide`;\n  return obj;\n}());\n\n/*\n######## ########  #### ##    ## ########  ##     ## ########\n   ##    ##     ##  ##  ###   ## ##     ## ##     ##    ##\n   ##    ##     ##  ##  ####  ## ##     ## ##     ##    ##\n   ##    ########   ##  ## ## ## ########  ##     ##    ##\n   ##    ##     ##  ##  ##  #### ##        ##     ##    ##\n   ##    ##     ##  ##  ##   ### ##        ##     ##    ##\n   ##    ########  #### ##    ## ##         #######     ##\n*/\n\n/**\n * ToolbarInput - The text input area of the Toolbar. This base object provides\n *  text input to users, allowing them to insert things like hyperlinks and\n *  image URIs.\n *\n * @param {HTML Element} $ctn - The containing div for $input and $closeBtn.\n * @param {HTML Element} $input - The text input.\n * @param {HTML Element} $closeBtn - The button which closes the input.\n *\n */\nvar ToolbarInput = {\n\n  /**\n   * init - Initializes the input by generating the requisite HTML and attaching\n   *  the necessary event listeners.\n   *\n   * @param {HTML Element} [$ctn] - An optional container HTML element to which\n   *  the input (or, more accurately, the container) will be attached.\n   * @param {Function} hideCallback The function to be called when the input is\n   *  hidden.\n   *\n   */\n  init(hideCallback, $outerCtn) {\n    this.$ctn = generateElement(\n      'div',\n      [tbClass.inputCtn, tbClass.hideDown],\n    );\n    this.$input = generateElement('input', tbClass.input, { type: 'text' });\n    this.$closeBtn = generateButton('<b>&times;</b>', tbClass.btn, true);\n\n    this.$ctn.appendChild(this.$input);\n    this.$ctn.appendChild(this.$closeBtn);\n\n    this.$closeBtn.addEventListener('click', this.hide.bind(this));\n    this.$input.addEventListener('keypress', this.defaultEnterHandler.bind(this));\n    this.hideCallback = hideCallback;\n    if ($outerCtn && $outerCtn instanceof HTMLElement) this.appendTo($outerCtn);\n  },\n\n  /**\n   * defaultEnterHandler - The function to be called when the user presses enter\n   *  while typing in the input. When the user presses 'Enter' in the input\n   *  field, this function will call the current saveHandler, always passing in\n   *  the value of the input and the value assigned to this.currentRange. It is\n   *  assumed that the value will always be required by the saveHandler and\n   *  that, since it is hidden while the input is focused, so will the current\n   *  range.\n   *\n   * @param {Event} e The Event to listen for.\n   *\n   */\n  defaultEnterHandler(e) {\n    if (e.key === 'Enter') {\n      if (\n        this.saveHandler\n        && typeof this.saveHandler === 'function'\n      ) {\n        this.saveHandler.call(this, this.$input.value, this.currentRange);\n      }\n      if (!this.preventHideOnEnter) {\n        this.hide();\n      }\n    }\n  },\n\n  /**\n   * setSaveHandler - Sets the saveHandler for the input to the given\n   *  saveHandler function.\n   *\n   * @param {function} saveHandler The function to be called when the user saves\n   *  (presses Enter) on the input.\n   *\n   */\n  setSaveHandler(saveHandler) {\n    if (saveHandler && typeof saveHandler === 'function') {\n      this.saveHandler = saveHandler;\n    }\n  },\n\n  /**\n   * clearSaveHandler - Sets the saveHandler to null.\n   *\n   */\n  clearSaveHandler() {\n    this.saveHandler = null;\n  },\n\n  /**\n   * getValue - Get the value of the input.\n   *\n   * @returns {string} The value of the $input.\n   */\n  getValue() {\n    return this.$input.value;\n  },\n\n  /**\n   * display - Displays the input by removing hiding class from the input\n   *  container and placing the focus in the input. Will also accept an optional\n   *  placeholder which will be placed on the input.\n   *\n   * @param {string} [placeholder=''] An optional string which will be set as\n   *  the placeholder on the input.\n   *\n   */\n  display(placeholder = '') {\n    this.$input.placeholder = placeholder;\n    this.$ctn.classList.remove(tbClass.hideDown);\n    // put focus in the input. Must timeout due to animations.\n    const sel = window.getSelection();\n    this.currentRange = sel.getRangeAt(0);\n    const range = document.createRange();\n    range.selectNode(this.$input);\n    function focusInput() {\n      this.$input.focus();\n    }\n    setTimeout(focusInput.bind(this), 200);\n  },\n\n  /**\n   * clear - Clears the input.If a new placeholder is provided, will set the\n   *  input's placeholder to that.\n   *\n   * @param {string} [newPlaceholder] The new placeholder to be put on the\n   *  input.\n   *\n   */\n  clear(newPlaceholder) {\n    this.$input.value = '';\n    if (newPlaceholder) {\n      this.$input.placeholder = newPlaceholder;\n    }\n  },\n\n  /**\n   * hide - Hides the input container and calls the saved hideCallback function.\n   *\n   */\n  hide(useCallback = true) {\n    // debugger;\n    if (this.$ctn.classList.contains(tbClass.hideDown)) return false;\n    this.$ctn.classList.add(tbClass.hideDown);\n    this.$input.value = '';\n    this.clearSaveHandler();\n    if (this.currentRange) {\n      const sel = window.getSelection();\n      const range = sel.getRangeAt(0);\n      sel.removeRange(range);\n      sel.addRange(this.currentRange);\n    }\n    if (\n      useCallback\n      && this.hideCallback\n      && typeof this.hideCallback === 'function'\n    ) {\n      this.hideCallback();\n    }\n    return true;\n  },\n\n  /**\n   * html - Return the HTML for the input container.\n   *\n   */\n  html() {\n    return this.$ctn;\n  },\n\n  /**\n   * getWidth - Returns the width of the input container.\n   *\n   * @returns {number} The width of the input container.\n   */\n  getWidth() {\n    const boundingRect = this.$ctn.getBoundingClientRect();\n    return boundingRect.width;\n  },\n\n  /**\n   * getWidth - Returns the height of the input container.\n   *\n   * @returns {number} The height of the input container.\n   */\n  getHeight() {\n    const boundingRect = this.$ctn.getBoundingClientRect();\n    return boundingRect.height;\n  },\n  /**\n   * appendTo - Appends the input to the given HTML Element.\n   *\n   * @param {Element} $where The HTML Element to which the input will be\n   *  appended\n   *\n   * @returns {Element} The $where HTML Element.\n   */\n  appendTo($where) {\n    $where.appendChild(this.html());\n    return $where;\n  },\n};\n\n// The number of pixels by which to offset the top of the toolbar.\nconst toolbarOffset = 5;\n\n/**\n * Toolbar - The toolbar used for editing text in the WFEditor.\n *\n * @property {Element} $ctn - The div containing the toolbar. This div is\n *  created in the initToolbar method.\n * @property {Element} $boldBtn - When clicked, bolds current selection.\n * @property {Element} $italicBtn - When clicked, italicizes current selection.\n * @property {Element} $headingBtn - When clicked, changes the section of the\n *  current selection to a heading (h3).\n * @property {Element} linkBtn - When clicked, presents a box for inserting a\n *  URL, then uses that URL to turn current selection into a hyperlink.\n */\nconst BaseToolbar = {\n\n  /**\n   * initToolbar - Initializes the Toolbar.\n   *\n   * @param {Editor} editor The Editor which owns this Toolbar.\n   * @param {Object} options The initialization options provided by the user.\n   *\n   * @returns {Toolbar} Returns this.\n   */\n  initToolbar(editor, options) {\n    this.options = options;\n    this.editor = editor;\n    this.toolbarOffset = toolbarOffset;\n    this.$ctn = generateElement(\n      'div',\n      [tbClass.main, 'hide'],\n      { contenteditable: false },\n    );\n    this.$btnCtn = generateElement('div', tbClass.btnCtn);\n    this.$ctn.appendChild(this.$btnCtn);\n    this.input = Object.create(ToolbarInput);\n    this.editor.$ctn.appendChild(this.$ctn);\n    return this;\n  },\n\n  /**\n   * getButtonsWidth - Returns the width of the button container.\n   *\n   * @returns {number} The width of the button container.\n   */\n  getButtonsWidth() {\n    const childNodes = this.$btnCtn.children;\n    return (16 * 2.5) * childNodes.length;\n  },\n\n  /**\n   * getButtonsWidth - Returns the height of the button container.\n   *\n   * @returns {number} The height of the button container.\n   */\n  getButtonsHeight() {\n    return (16 * 2.3);\n  },\n\n  /**\n   * hideButtons - Hides the buttons contained within the Toolbar so the input\n   *  can be displayed.\n   *\n   */\n  hideButtons() {\n    this.$ctn.style.width = `${this.input.getWidth()}px`;\n    this.$ctn.style.height = `${this.input.getHeight()}px`;\n    this.$btnCtn.classList.add(tbClass.hideUp);\n  },\n\n  /**\n   * displayButtons - Displays the buttons in the Toolbar.\n   *\n   */\n  displayButtons() {\n    this.$btnCtn.classList.remove(tbClass.hideUp);\n    this.$ctn.style.width = `${this.getButtonsWidth()}px`;\n    this.$ctn.style.height = `${this.getButtonsHeight()}px`;\n  },\n\n  displayInput(placeholder) {\n    const sel = window.getSelection();\n    this.currentRange = sel.getRangeAt(0);\n    this.input.display(placeholder);\n  },\n\n  hideInput() {\n\n  },\n\n  /**\n  * positionToolbar - Positions the Toolbar at the appropriate place based on\n  *  the current range.\n  *\n  */\n  positionToolbar() {\n    const sel = window.getSelection();\n    this.currentRange = sel.getRangeAt(0);\n    let rect = null;\n    if (this.currentRange.collapsed) {\n      rect = this.currentRange.commonAncestorContainer.getBoundingClientRect();\n    } else {\n      rect = this.currentRange.getBoundingClientRect();\n    }\n    const toolbarRect = this.$ctn.getBoundingClientRect();\n    const bottomPos = rect.bottom + this.toolbarOffset + toolbarRect.height;\n    if (bottomPos >= window.innerHeight) {\n      this.$ctn.style.top = `${rect.top - (this.toolbarOffset + toolbarRect.height)}px`;\n    } else {\n      this.$ctn.style.top = `${rect.bottom + this.toolbarOffset}px`;\n    }\n    this.$ctn.style.left = `${rect.left}px`;\n  },\n\n  /**\n   * display - Optionally display the Toolbar next to the given selection.\n   *  The Toolbar is always returned as an HTML element.\n   *\n   * @param {Selection} [sel=null] The Selection next to which the Toolbar should be\n   *  displayed.\n   *\n   * @returns {boolean} Returns true if the Toolbar is displayed. Else false.\n   */\n  baseDisplay() {\n    this.input.hide(false);\n    this.$ctn.classList.remove('hide');\n    this.positionToolbar();\n    this.displayButtons();\n    return true;\n  },\n\n  /**\n   * hide - Hides the Toolbar.\n   *\n   * @returns {boolean} Returns true if successful else false.\n   */\n  hide() {\n    this.currentRange = null;\n    this.input.hide(false);\n    this.displayButtons();\n    this.$ctn.classList.add('hide');\n    if (this.$ctn.classList.contains('hide')) {\n      return true;\n    }\n    return false;\n  },\n\n  /**\n   * html - Render the Toolbar as HTML\n   *\n   * @returns {Element} The Toolbar as HTML.\n   */\n  html() {\n    return this.$ctn;\n  },\n\n  contains(node) {\n    return this.$ctn.contains(node);\n  },\n};\n\n/*\n######## ########  ########  ##     ## ######## ########  #######  ##    ##\n   ##    ##     ## ##     ## ##     ##    ##       ##    ##     ## ###   ##\n   ##    ##     ## ##     ## ##     ##    ##       ##    ##     ## ####  ##\n   ##    ########  ########  ##     ##    ##       ##    ##     ## ## ## ##\n   ##    ##     ## ##     ## ##     ##    ##       ##    ##     ## ##  ####\n   ##    ##     ## ##     ## ##     ##    ##       ##    ##     ## ##   ###\n   ##    ########  ########   #######     ##       ##     #######  ##    ##\n*/\n\n/**\n* ToolbarButton - A button to be used in the Toolbar. This base object provides\n*  a convenient set of methods for creating, attaching event listeners to, and\n*  changing the appearance of buttons on the Toolbar.\n*\n*/\nvar ToolbarButton = {\n  /**\n   * init - Initializes the button by creating the requisite HTML (using the\n   *  given content), attaching the given clickHandler, and, if ctn is provided,\n   *  attaching the html to the given container Element.\n   *\n   * @param {HTML string || string} content The content which will be placed in\n   *  the button. This can be either a simple string or a string containing\n   *  HTML.\n   * @param {Function} clickHandler The function to be attached to the button's\n   *  HTML to be called when the button is clicked.\n   * @param {HTML Element (opt)} $ctn The optional containing HTML Element.\n   *\n   * @returns {ToolbarButton} Returns this ToolbarButton.\n   */\n  init(content, title, handler, $ctn) {\n    this.$html = generateButton(content, tbClass.btn, true, { title });\n    this.setSaveHandler(handler);\n    this.appendTo($ctn);\n    return this;\n  },\n\n  /**\n   * setSaveHandler - Sets the clickHandler to the given function.\n   *\n   * @param {Function} saveHandler The function to call on each click.\n   *\n   * @returns {Function || null} Returns null if the given saveHandler is not a\n   *  Function. Otherwise it returns the Function.\n   */\n  setSaveHandler(saveHandler) {\n    if (typeof saveHandler !== 'function') {\n      return null;\n    }\n    if (this.clickHandler) {\n      this.$html.removeEventListener('click', this.clickHandler);\n    }\n    this.saveHandler = saveHandler;\n    this.$html.addEventListener('click', this.saveHandler);\n    return this.saveHandler;\n  },\n\n  /**\n   * addClass - Adds a class or multiple classes to the button's html.\n   *\n   * @param {array || string} [klasses] The class(es) to be added to the button.\n   *  Can either be an array of strings or a single string.\n   *\n   */\n  addClass(klasses = []) {\n    if (Array.isArray(klasses)) {\n      klasses.forEach((klass) => {\n        this.$html.classList.add(klass);\n      });\n    } else {\n      this.$html.classList.add(klasses);\n    }\n  },\n\n  /**\n   * removeClass - Removes a class or multiple classes from the button's html.\n   *\n   * @param {array || string} [klasses] The class(es) to be removed from the\n   *  button. Can either be an array of strings or a single string.\n   *\n   */\n  removeClass(klasses = []) {\n    if (Array.isArray(klasses)) {\n      klasses.forEach((klass) => {\n        this.$html.classList.remove(klass);\n      });\n    } else {\n      this.$html.classList.remove(klasses);\n    }\n  },\n\n  /**\n   * disable - Disables the button. Sets the disabled attribute to true and adds\n   *  the disabled class to the button.\n   *\n   */\n  disable() {\n    this.$html.disabled = true;\n    this.addClass(tbClass.btnDisabled);\n  },\n\n  /**\n   * enable - Enables the button. Sets the disabled attribute to false and\n   *  removes the disabled class from the button.\n   *\n   */\n  enable() {\n    this.$html.disabled = false;\n    this.removeClass(tbClass.btnDisabled);\n  },\n\n  /**\n   * markActive - Marks the button as currently active. Simply adds the active\n   *  class to the button.\n   *\n   */\n  markActive() {\n    this.$html.classList.add(tbClass.btnActive);\n  },\n\n  /**\n   * markInactive - Marks the button as currently inactive. Simply removes the\n   *  active class from the button.\n   *\n   */\n  markInactive() {\n    this.$html.classList.remove(tbClass.btnActive);\n  },\n\n\n  /**\n   * html - Returns the button in HTML form.\n   *\n   * @returns {Element} The button in HTML form.\n   */\n  html() {\n    return this.$html;\n  },\n\n  /**\n   * appendTo - Appends the button HTML Element to the given container.\n   *\n   * @param {Element} $ctn The HTML Element to which the button should be\n   *  appended.\n   *\n   */\n  appendTo($ctn) {\n    if ($ctn && $ctn instanceof HTMLElement) {\n      $ctn.appendChild(this.html());\n    }\n  },\n};\n\n// Create the EditToolbar from the BaseToolbar.\nconst editToolbar = Object.create(BaseToolbar);\n\n/**\n * init - Initialize the EditToolbar. Callse the BaseToolbar's initialization\n *  method then sets up the requisite buttons and input.\n *\n * @param {Editor} editor The Editor which owns this Toolbar.\n * @param {Object} options The initialization options provided by the user.\n *\n * @returns {EditToolbar} Returns this.\n */\neditToolbar.init = function init(editor, options) {\n  this.initToolbar(editor, options);\n  this.input.init(this.displayButtons.bind(this), this.$ctn);\n  this.createToolbarBtns();\n  this.$ctn.addEventListener('click', (e) => {\n    if (e.target === this.$ctn || e.target === this.$btnCtn) {\n      this.hide();\n    }\n  });\n  return this;\n};\n\n/**\n * createToolbarBtns - Create the buttons to be included on the toolbar, add\n *  appropriate event listeners, and attaches them to the $ctn.\n */\neditToolbar.createToolbarBtns = function createToolbarBtns() {\n  this.boldBtn = Object.create(ToolbarButton);\n  this.boldBtn.init('<b>B</b>', 'Bold Selection', this.editor.boldSelection.bind(this.editor), this.$btnCtn);\n  this.italicBtn = Object.create(ToolbarButton);\n  this.italicBtn.init('<i>i</i>', 'Italicize Selection', this.editor.italicizeSelection.bind(this.editor), this.$btnCtn);\n  this.headingBtn = Object.create(ToolbarButton);\n  this.headingBtn.init('H', 'Wrap Selection with Heading', this.editor.wrapHeading.bind(this.editor), this.$btnCtn);\n  this.linkBtn = Object.create(ToolbarButton);\n  this.linkBtn.init('🔗', 'Wrap Selection with Link', this.linkBtnHandler.bind(this), this.$btnCtn);\n};\n\n/**\n * toggleDisabledButtons - Disables buttons as necessary. As of now, if a the\n *  current selection contains a heading, all buttons other than the heading\n *  button are disabled.\n *\n * @param {Range} range The current range.\n *\n */\neditToolbar.toggleDisabledButtons = function toggleDisabledButtons() {\n  if (\n    findNodeType(this.currentRange.commonAncestorContainer, 'H1')\n    || findNodeType(this.currentRange.commonAncestorContainer, 'H2')\n  ) {\n    this.linkBtn.disable();\n    this.boldBtn.disable();\n    this.italicBtn.disable();\n  } else {\n    this.linkBtn.enable();\n    this.boldBtn.enable();\n    this.italicBtn.enable();\n  }\n};\n\n/**\n * toggleActiveLink - If ads the active class to the link button if the\n *  current selection contains a link. It also attaches a currentLink\n *  attribute to the link button so the link can be removed.\n *\n * @param {Selection} sel The current selection.\n *\n */\neditToolbar.toggleActiveLink = function toggleActiveLink(sel) {\n  const range = sel.getRangeAt(0);\n  const currentLink = findNodeType(range.commonAncestorContainer, 'A');\n  if (currentLink && sel.containsNode(currentLink, true)) {\n    this.linkBtn.markActive();\n    this.linkBtn.currentLink = currentLink;\n  } else {\n    this.linkBtn.currentLink = null;\n    this.linkBtn.markInactive();\n  }\n};\n\n/**\n * linkBtnHandler - Handler for when $linkBtn is clicked.\n *\n * @returns {boolean} Returns true if successful else false.\n */\neditToolbar.linkBtnHandler = function linkBtnHandler() {\n  if (this.linkBtn.currentLink) {\n    this.editor.removeLink(this.linkBtn.currentLink);\n    this.linkBtn.currentLink = null;\n    this.linkBtn.markInactive();\n  } else {\n    this.input.setSaveHandler(this.editor.wrapLink);\n    this.hideButtons();\n    this.input.display('Type a link...');\n  }\n};\n\n/**\n * display - Displays the EditToolbar.\n *\n * @param {Selection} [sel=null] The selection next to which the toolbar should\n *  be displayed.\n *\n * @returns {boolean} Returns true if the EditToolbar is displayed. Else false.\n */\neditToolbar.display = function display(sel = null) {\n  if (!(sel instanceof Selection)) return false;\n  if (containsSelection(sel, this.$ctn)) return false;\n  this.currentRange = sel.getRangeAt(0);\n  this.toggleActiveLink(sel);\n  this.toggleDisabledButtons();\n  return this.baseDisplay();\n};\n\n// Create the InsertToolbar from the BaseToolbar.\nconst insertToolbar = Object.create(BaseToolbar);\n\n/**\n * init - Initialize the insertToolbar. Callse the BaseToolbar's initialization\n *  method then sets up the requisite buttons and input.\n *\n * @param {Editor} editor The Editor which owns this Toolbar.\n * @param {Object} options The initialization options provided by the user.\n *\n * @returns {InsertToolbar} Returns this.\n */\ninsertToolbar.init = function init(editor, options) {\n  this.initToolbar(editor, options);\n  this.toolbarOffset = 15;\n  this.createToolbarBtns();\n  this.input.init(this.hideImageInput.bind(this), this.$ctn);\n  this.input.$input.id = 'input';\n  return this;\n};\n\n/**\n * createToolbarBtns - Creates the requisite buttons for this toolbar. The\n *  insertToolbar allows users to insert images and horizontal rules so this\n *  method creates buttons to allow the user to do these things.\n *\n */\ninsertToolbar.createToolbarBtns = function createToolbarBtns() {\n  this.imgBtn = Object.create(ToolbarButton);\n  this.imgBtn.init('🖼️', 'Insert an Image', this.displayImgInput.bind(this), this.$btnCtn);\n  this.lineBtn = Object.create(ToolbarButton);\n  this.lineBtn.init('--', 'Insert a Horizontal Rule', this.editor.insertLine.bind(this.editor), this.$btnCtn);\n};\n\n/**\n * toggleDisabledButtons - Toggles the disabled buttons. If the user is\n *  currently in the first section, they cannot add a horizontal rule.\n *\n */\ninsertToolbar.toggleDisabledButtons = function toggleDisabledButtons() {\n  const sel = window.getSelection();\n  if (this.editor.isFirst(sel.anchorNode)) {\n    this.lineBtn.disable();\n  } else {\n    this.lineBtn.enable();\n  }\n};\n\n/**\n * displayImgInput - Displays the input for adding an image.\n *\n */\ninsertToolbar.displayImgInput = function displayImgInput() {\n  const sel = window.getSelection();\n  this.currentRange = sel.getRangeAt(0);\n  this.input.setSaveHandler(this.insertImage.bind(this));\n  this.input.preventHideOnEnter = true;\n  this.hideButtons();\n  this.input.display('Type an image URL...');\n};\n\n\n/**\n * hideImageInput - Hides the input for adding an image.\n *\n */\ninsertToolbar.hideImageInput = function hideImageInput() {\n  this.imgURL = null;\n  this.displayButtons();\n};\n\n/**\n * insertImage - This function acts as the saveHandler for the image input. The\n *  input first prompts the user to provide a url, then alt text. If the user\n *  has not yet provided the image url, it will take the value from the input\n *  and store it as the image url, prompting the user for the alt text. Once\n *  this has been provided, the function passes control to the editor and closes\n *  itself.\n *\n */\ninsertToolbar.insertImage = function insertImage() {\n  if (!this.imgURL) {\n    this.imgURL = validateURL(this.input.getValue());\n    if (!this.imgURL) {\n      this.input.clear('Type an image URL...');\n    } else {\n      this.input.clear('Enter alt text...');\n    }\n  } else {\n    this.editor.insertImage(this.imgURL, this.input.getValue(), this.currentRange.startContainer);\n    this.input.hide();\n    this.displayButtons();\n    this.hide();\n  }\n};\n\n/**\n * display - Displays the InsertToolbar.\n *\n * @returns {boolean} Returns true if the InsertToolbar was successfully\n *  displayed. Else returns false.\n */\ninsertToolbar.display = function display() {\n  this.toggleDisabledButtons();\n  return this.baseDisplay();\n};\n\n/*\n########  ######## ########    ###    ##     ## ##       ########\n##     ## ##       ##         ## ##   ##     ## ##          ##\n##     ## ##       ##        ##   ##  ##     ## ##          ##\n##     ## ######   ######   ##     ## ##     ## ##          ##\n##     ## ##       ##       ######### ##     ## ##          ##\n##     ## ##       ##       ##     ## ##     ## ##          ##\n########  ######## ##       ##     ##  #######  ########    ##\n*/\n\n/**\n* Editor - The main object representing the WriteFree editor.\n*\n* @property {Element} $ctn - The outermost container of the WriteFree editor.\n*  $ctn is passed in to the WriteFree instantiation function.\n* @property {Element} $innerCtn - The actual contetneditable-div in which the\n*  user can write\n*/\nvar editorBase = {\n  /**\n   * initWFEditor - Initializes the Editor. Creates an inner container div and\n   *  fills it with a line break (done to ensure the first block of text is\n   *  wrapped in a div); initializes the Toolbar; and adds the pasteHandler.\n   *\n   * @returns {Editor} Returns this.\n   */\n  initWFEditor($ctn, options) {\n    this.$ctn = $ctn;\n    this.options = options;\n    this.generateClasses();\n    document.execCommand('defaultParagraphSeparator', false, this.options.divOrPar);\n    this.$innerCtn = generateElement(\n      'div',\n      this.classes.main,\n      { style: this.options.containerStyle },\n    );\n    this.$innerCtn.setAttribute('contenteditable', true);\n    this.$ctn.append(this.$innerCtn);\n    this.createFirstTextSection();\n\n    this.editToolbar = editToolbar.init(this, this.options);\n    this.insertToolbar = insertToolbar.init(this, this.options);\n\n    this.$ctn.addEventListener('paste', this.pasteHandler.bind(this));\n    this.$ctn.addEventListener('keydown', this.keydownHandler.bind(this));\n    this.$ctn.addEventListener('keyup', this.keyupHandler.bind(this));\n    this.$ctn.addEventListener('click', this.checkForInsert.bind(this));\n    this.$ctn.addEventListener('mouseup', this.positionCursor.bind(this));\n    // must be added to document because of browsers.\n    document.addEventListener('selectionchange', this.selectionHandler.bind(this));\n    document.addEventListener('scroll', this.insertToolbar.hide.bind(this.insertToolbar));\n    return this;\n  },\n\n  generateClasses() {\n    this.classes = {};\n    this.innerCtnClass = 'wf__editor';\n    this.classes.main = [this.innerCtnClass];\n    if (this.options.containerClass !== 'wf__edtior') {\n      this.classes.main.push(this.options.containerClass);\n    }\n    this.classes.textSection = 'wf__text-section';\n    this.classes.containerSection = 'wf__container-section';\n    return this.classes;\n  },\n\n  /*\n   ######  ########  ######  ######## ####  #######  ##    ##  ######\n  ##    ## ##       ##    ##    ##     ##  ##     ## ###   ## ##    ##\n  ##       ##       ##          ##     ##  ##     ## ####  ## ##\n   ######  ######   ##          ##     ##  ##     ## ## ## ##  ######\n        ## ##       ##          ##     ##  ##     ## ##  ####       ##\n  ##    ## ##       ##    ##    ##     ##  ##     ## ##   ### ##    ##\n   ######  ########  ######     ##    ####  #######  ##    ##  ######\n  */\n\n  /**\n   * createFirstTextSection - The editor must have the a first div in order to ensure\n   *  proper formatting. This method creates the first div and appends it to\n   *  the inner container.\n   */\n  createFirstTextSection() {\n    if (!this.$firstSection) {\n      this.$firstSection = this.createTextSection();\n    }\n    this.$firstSection.textContent = '';\n    this.$innerCtn.insertBefore(this.$firstSection, this.$innerCtn.firstChild);\n    const sel = window.getSelection();\n    const range = document.createRange();\n    range.setStart(this.$firstSection, 0);\n    range.setEnd(this.$firstSection, 0);\n    sel.removeAllRanges();\n    sel.addRange(range);\n    window.first = this.$firstSection;\n    return this.$firstSection;\n  },\n\n  /**\n   * displayFirstSectionPlaceholder - Checks if current section is the first textSection\n   *  and, if so, ensures the section is completely empty. This ensures that the\n   *  placeholder is properly displayed.\n   *\n   */\n  displayFirstSectionPlaceholder() {\n    const sel = window.getSelection();\n    if (\n      sel.isCollapsed\n      && this.isFirst(sel.anchorNode)\n      && sel.anchorNode.classList.contains(this.classes.textSection)\n      && sel.anchorNode.textContent === ''\n    ) {\n      sel.anchorNode.innerHTML = '';\n    }\n  },\n\n  /**\n   * createTextSection - Creates a standard text section.\n   *\n   * @returns {Element} The newly-created text section.\n   */\n  createTextSection() {\n    if (!Array.isArray(this.options.sectionClass)) {\n      this.options.sectionClass = Array(this.options.sectionClass);\n    }\n    if (!this.options.sectionClass.includes(this.classes.textSection)) {\n      this.options.sectionClass.push(this.classes.textSection);\n    }\n    const parOptions = {\n      style: this.options.sectionStyle,\n      klasses: this.options.sectionClass,\n    };\n    return generateElement(this.options.divOrPar, [], parOptions);\n  },\n\n  /**\n   * createContainerSection - Creates a container div to house any inline objects the\n   *  user inserts (img, hr). These are used in order to separate sections in which the\n   *  user can type and those in which they cannot.\n   *\n   * @param {Element} [childNode] If provided, childNode will be attached to the newly-\n   *  created container.\n   *\n   * @returns {Element} Returns the newly-created container.\n   */\n  createContainerSection(childNode) {\n    const style = this.options.sectionStyle;\n    const container = generateElement('div', this.classes.containerSection, { style });\n    if (childNode && childNode instanceof Element) {\n      container.appendChild(childNode);\n    }\n    return container;\n  },\n\n  /**\n   * normalizeSection - Normalizes the current section (div or p) in order to\n   *  join all separate text nodes. Text nodes end up split when starting\n   *  newlines and trying to rejoin sections and so must be normalized.\n   *\n   * @returns {null} Returns null.\n   */\n  normalizeSection() {\n    const sel = window.getSelection();\n    try {\n      const range = sel.getRangeAt(0);\n      const startParent = findParentBlock(sel.anchorNode);\n      const endParent = findParentBlock(sel.focusNode);\n      if (startParent) startParent.normalize();\n      if (endParent) endParent.normalize();\n      if (range.commonAncestorContainer) range.commonAncestorContainer.normalize();\n    } catch (exception) {\n      return null;\n    }\n    return null;\n  },\n\n  /**\n   * deleteContainerSection - Deletes an entire container section. Used when the\n   *  user is at the beginning or end of a text section and press backspace or\n   *  the delete key (passed in as 'key'). Prevents user from entering the\n   *  container section unwittingly.\n   *\n   * @param {KeybaordEvent} e The Event to use to determine which key was\n   *  pressed. e.key must be 'Backspace' or 'Delete'. If 'Backspace', this\n   *  method will delete the previous section. If 'Delete', this method will\n   *  delete the next container section.\n   *\n   * @returns {boolean} Returns true if the\n   */\n  deleteContainerSection(e) {\n    const sel = window.getSelection();\n    const section = findParentBlock(sel.anchorNode);\n    let nextSection = null;\n    if (e.key === 'Backspace') {\n      nextSection = section.previousSibling;\n    } else if (e.key === 'Delete') {\n      nextSection = section.nextSibling;\n    }\n    if (\n      nextSection\n      && nextSection.classList.contains(this.classes.containerSection)\n    ) {\n      e.preventDefault();\n      nextSection.parentNode.removeChild(nextSection);\n    }\n    return false;\n  },\n\n\n  /**\n   * preventTextInContainer - Prevents the user from typing in container\n   *  sections. If a user tries to type in a container section, the next text\n   *  section is automatically selected or, if none is present, a new one is\n   *  created immediately after the container section. Up and left arrow keys\n   *  move the cursor to the previous section and right and down arrow keys move\n   *  the cursor to the next section.\n   *\n   */\n  preventTextInContainer(e) {\n    const sel = window.getSelection();\n    const section = findParentBlock(sel.anchorNode);\n    if (section.classList.contains(this.classes.containerSection)) {\n      let newSection = section.nextSibling;\n      if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {\n        newSection = section.previousSibling;\n      } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {\n        newSection = section.nextSibling;\n      } else if (\n        !newSection\n        || newSection.classList.contains(this.classes.containerSection)\n        || (\n          newSection.classList.contains(this.classes.textSection)\n          && newSection.textContent.length > 0\n        )\n      ) {\n        newSection = this.createTextSection();\n        this.$innerCtn.insertBefore(newSection, section.nextSibling);\n      }\n      e.preventDefault();\n      sel.collapse(newSection, 0);\n    }\n  },\n\n  /*\n  ######## ########  #### ######## #### ##    ##  ######\n  ##       ##     ##  ##     ##     ##  ###   ## ##    ##\n  ##       ##     ##  ##     ##     ##  ####  ## ##\n  ######   ##     ##  ##     ##     ##  ## ## ## ##   ####\n  ##       ##     ##  ##     ##     ##  ##  #### ##    ##\n  ##       ##     ##  ##     ##     ##  ##   ### ##    ##\n  ######## ########  ####    ##    #### ##    ##  ######\n  */\n\n  /**\n   * boldSelection - Bolds the current selection.\n   *\n   */\n  boldSelection() {\n    const sel = window.getSelection();\n    if (sel instanceof Selection) {\n      document.execCommand('bold', false);\n    }\n  },\n\n  /**\n   * linkBtnHandler - Italicizes the current selection.\n   *\n   */\n  italicizeSelection() {\n    const sel = window.getSelection();\n    if (sel instanceof Selection) {\n      document.execCommand('italic', false);\n    }\n  },\n\n  /**\n   * wrapHeading - Wrap the current selection in a heading element or removes\n   *  current heading element. Wraps non-headings in H1 if the current\n   *  selection is the first element in the Editor otherwise uses H2. Removes\n   *  all children HTML elements, leavining only text.\n   *\n   * @returns {boolean} Returns true if successful else false.\n   */\n  wrapHeading() {\n    const sel = window.getSelection();\n    let parentnode = findParentBlock(sel.anchorNode);\n    parentnode.innerHTML = parentnode.innerHTML.replace(/<[^>]+>/g, '');\n    let tagName;\n    let klass;\n    let style;\n    if (sel instanceof Selection) {\n      // debugger;\n      if (parentnode.tagName === 'H2') {\n        tagName = this.options.divOrPar;\n        klass = this.options.sectionClass;\n        style = this.options.sectionStyle;\n      } else if (parentnode.tagName === 'DIV' || parentnode.tagName === 'P') {\n        tagName = 'h1';\n        klass = this.options.largeHeadingClass;\n        style = this.options.largeHeadingStyle;\n      } else {\n        tagName = 'h2';\n        klass = this.options.smallHeadingClass;\n        style = this.options.smallHeadingStyle;\n      }\n      const successful = document.execCommand('formatBlock', false, tagName);\n      if (successful) {\n        parentnode = findParentBlock(sel.anchorNode);\n        addStyleFromObj(parentnode, style);\n        addClasses(parentnode, klass);\n        const range = sel.getRangeAt(0);\n        range.selectNode(sel.focusNode);\n        range.collapse();\n      }\n      return successful;\n    }\n    return false;\n  },\n\n  /**\n   * wrapLink - wraps the given range (currentRange) with a link node pointing\n   *  to the given URL (rawURL). This method first validates the URL, throwing\n   *  it out if it isn't properly formatting. It will insert the http protocol\n   *  if at the beginning of the string if it doesn't contain it.\n   *\n   * @param {string} rawURL      A string containing the URL to which the link\n   *  will point.\n   * @param {Range} currentRange The Range around which the link will be\n   *  wrapped.\n   *\n   * @returns {Element || boolean} Will return the new link if successful, else\n   *  returns false.\n   */\n  wrapLink(rawURL, currentRange) {\n    const url = validateURL(rawURL);\n    if (!url) return false;\n    const link = generateElement('a');\n    link.href = url;\n    currentRange.surroundContents(link);\n    collapseSelectionToRange(window.getSelection(), currentRange);\n    return link;\n  },\n\n  /**\n   * removeLink - Replaces the given link node with a text node containing the\n   *  link's text content.\n   *\n   * @param {Element} $link The linnk to remove.\n   *\n   */\n  removeLink($link) {\n    const sel = window.getSelection();\n    this.currentRange = sel.getRangeAt(0);\n    const range = document.createRange();\n    range.selectNode($link);\n    const plainText = document.createTextNode($link.textContent);\n    range.deleteContents();\n    range.insertNode(plainText);\n    collapseSelectionToRange(sel, range);\n  },\n\n  /**\n   * insertImage - Inserts an image into the editor. Must be provided src, alt\n   *  and nextSibling in order to properly render.\n   *\n   * @param {string} src          The string to use for the img's src attribute.\n   * @param {string} alt          The string to use for the img's alt attribute.\n   * @param {Element} nextSibling The HTML Element before which the image will\n   *  be inserted.\n   *\n   * TODO: Get rid of alert. Build out a simple messaging system for users.\n   */\n  insertImage(src, alt, nextSibling) {\n    if (nextSibling.innerHTML.length === 0) {\n      nextSibling.append(document.createElement('br'));\n    }\n    const sel = window.getSelection();\n    const img = generateElement('img', this.options.imgClass, { src, alt, style: this.options.imgStyle });\n    const section = this.createContainerSection();\n    img.section = section;\n    img.onerror = function onImageError() {\n      img.section.parentNode.removeChild(img.section);\n      alert('Image failed to load.');\n    };\n    if (nextSibling === this.$firstSection) {\n      this.$firstSection = section;\n    }\n    section.appendChild(img);\n    nextSibling.parentNode.insertBefore(section, nextSibling);\n    const range = document.createRange();\n    range.selectNodeContents(nextSibling);\n    // debugger;\n    collapseSelectionToRange(sel, range, true);\n  },\n\n  /**\n   * insertLine - Inserts a line in the editor directly before the current\n   *  position of the selection cursor.\n   *\n   */\n  insertLine() {\n    const sel = window.getSelection();\n    let range = sel.getRangeAt(0);\n    const nextSibling = findParentBlock(range.startContainer);\n    if (nextSibling === this.$firstSection) return false;\n    const line = document.createElement('hr');\n    const section = this.createContainerSection();\n    section.appendChild(line);\n    nextSibling.parentNode.insertBefore(section, nextSibling);\n    range = document.createRange();\n    range.selectNodeContents(nextSibling);\n    collapseSelectionToRange(sel, range, true);\n    this.insertToolbar.hide();\n    return true;\n  },\n\n  /*\n  ##     ##    ###    ##    ## ########  ##       ######## ########   ######\n  ##     ##   ## ##   ###   ## ##     ## ##       ##       ##     ## ##    ##\n  ##     ##  ##   ##  ####  ## ##     ## ##       ##       ##     ## ##\n  ######### ##     ## ## ## ## ##     ## ##       ######   ########   ######\n  ##     ## ######### ##  #### ##     ## ##       ##       ##   ##         ##\n  ##     ## ##     ## ##   ### ##     ## ##       ##       ##    ##  ##    ##\n  ##     ## ##     ## ##    ## ########  ######## ######## ##     ##  ######\n  */\n\n  /**\n   * selectionHandler - Handles the given Selection (sel) event. If the given\n   *  object is not a Selection this method will immediately return false.\n   *  Otherwise it will display the Toolbar if the Selection is contained\n   *  fully within this Editor and the Selection contains text. Otherwise it\n   *  will hide the Toolbar.\n   *\n   *\n   * @param {Event} e The selectionchange event.\n   *\n   * @returns {boolean} Returns true if the given selection was successfully\n   *  handled, else false.\n   */\n  selectionHandler(e) {\n    if (e.type !== 'selectionchange') return false;\n    const sel = window.getSelection();\n    if (\n      (containsSelection(sel, this.$ctn) && !sel.isCollapsed)\n      || containsSelection(sel, this.editToolbar.html())\n    ) {\n      this.editToolbar.display(sel);\n    } else {\n      this.editToolbar.hide();\n    }\n    return true;\n  },\n\n  /**\n   * pasteHandler - Handles the paste event in the editor. We intercept the\n   *  normal paste event and strip all HTML from the copied text and then\n   *  insert it as HTML. This is done to ensure that each paste is essentially\n   *  a 'paste as plain text.' We use 'insertHTML' because most browsers don't\n   *  allow access to the paste action in execCommand.\n   *\n   * @param {Event} e The paste event.\n   *\n   * @returns {boolean} Returns true if hijacked paste was successful else\n   *  false.\n   */\n  pasteHandler(e) {\n    e.preventDefault();\n    if (!e.type === 'paste') return false;\n    const text = e.clipboardData.getData('text/plain');\n    document.execCommand('insertHTML', false, text);\n    return true;\n  },\n\n  /**\n   * newLineHandler - Handle's the creation of a new section when the user\n   *  creates a newline (aka presses 'Enter'). We hijack this event because\n   *  the implementation of creating new sections in contenteditable elements\n   *  can be pretty different between browsers (especially Firefox).\n   *\n   *  TODO: Clean this up.\n   *\n   * @returns {Element} Returns the newly created paragraph.\n   */\n  newLineHandler(e) {\n    e.preventDefault();\n    const sel = window.getSelection();\n    const parentBlock = findParentBlock(sel.focusNode);\n    const newPar = this.createTextSection();\n    parentBlock.parentNode.insertBefore(newPar, parentBlock.nextSibling);\n    const currentRange = sel.getRangeAt(0);\n    if (currentRange.collapsed) {\n      try {\n        currentRange.setEndBefore(parentBlock.nextSibling);\n        if (currentRange.toString().length !== 0) {\n          newPar.append(currentRange.cloneContents());\n        }\n      } catch (exception) {\n        newPar.textContent = '';\n      }\n    }\n    if (newPar.textContent.length === 0) {\n      newPar.append(document.createElement('br'));\n      newPar.append(document.createTextNode(''));\n    }\n    currentRange.deleteContents();\n    if (\n      parentBlock.textContent.length === 0\n      && currentRange.commonAncestorContainer === currentRange.startContainer\n    ) {\n      parentBlock.append(document.createElement('br'));\n    }\n    const range = document.createRange();\n    range.selectNodeContents(newPar);\n    collapseSelectionToRange(sel, range, true);\n    newPar.normalize();\n    const rect = newPar.getBoundingClientRect();\n    if (rect.top >= window.innerHeight) {\n      window.scroll({\n        top: rect.top + window.scrollY,\n        behavior: 'instant',\n      });\n    }\n    return newPar;\n  },\n\n  /**\n   * keydownHandler - Watches for deletion keys on keydown events and stops\n   *  them from deleting the divs inside the main container. However, this is\n   *  fairly limited in scope: though it catches Backspace, Delete, and ctrl-X\n   *  it's really only meant to stop the Backspace from deleting the first\n   *  paragraph. Events like ctrl-A + Backspace are handled in the\n   *  keyupHandler.\n   *\n   * @param {KeyboardEvent} e The KeyboardEvent to test.\n   */\n  keydownHandler(e) {\n    const sel = window.getSelection();\n    if (isDeletionKey(e)) {\n      if (\n        e.key === 'Backspace'\n        && sel.anchorOffset === 0\n        && sel.anchorNode === sel.focusNode\n      ) {\n        this.deleteContainerSection(e);\n      } else if (\n        e.key === 'Delete'\n        && sel.anchorOffset === sel.anchorNode.textContent.length\n        && sel.anchorNode === sel.focusNode\n      ) {\n        this.deleteContainerSection(e);\n      }\n    }\n    if (e.key === 'Enter' && this.$innerCtn.contains(e.target)) {\n      this.newLineHandler(e);\n    }\n    this.preventTextInContainer(e);\n    const range = sel.getRangeAt(0);\n    const prevSection = findParentBlock(range.startContainer);\n    if (!this.editToolbar.contains(prevSection) && !this.insertToolbar.contains(prevSection)) {\n      this.prevSection = prevSection;\n      this.prevSectionPrevSibling = this.prevSection.previousSibling;\n      this.prevOffset = range.startOffset;\n    }\n  },\n\n  /**\n   * keyupHandler - Watches for deletion keys and resets the editor container\n   *  if they remove the first inner paragraph. Also normalizes the current\n   *  section so that all text nodes are merged together (this helps with\n   *  newline generation). Also checks to see if the insertToolbar should be\n   *  displayed.\n   *\n   * @param {KeyboardEvent} e The KeyboardEvent to test.\n   */\n  keyupHandler(e) {\n    if (isDeletionKey(e)) {\n      const sel = window.getSelection();\n      if (\n        this.$innerCtn.innerHTML === ''\n        || this.$innerCtn.innerHTML === '<br>'\n        || sel.anchorNode === this.$innerCtn\n      ) {\n        this.createFirstTextSection();\n      }\n      this.displayFirstSectionPlaceholder();\n    }\n    try {\n      this.normalizeSection();\n      this.checkForInsert(e);\n      this.positionCursor();\n    } catch (exception) {\n      return null;\n    }\n    return true;\n  },\n\n  /**\n   * checkForInsert - Determines if the insertToolbar should be displayed. If\n   *  the user is in an empty section, the insertToolbar should be displayed.\n   *\n   *\n   * @returns {boolean} Returns true if the insertToolbar is displayed else\n   *  false.\n   */\n  checkForInsert(e) {\n    if (e && this.insertToolbar.$ctn.contains(e.target)) return false;\n    const sel = window.getSelection();\n    this.insertToolbar.hide();\n    if (sel.isCollapsed\n      && (sel.anchorNode && sel.anchorNode.textContent === '')\n      && !containsSelection(sel, this.insertToolbar.$ctn)\n    ) {\n      this.insertToolbar.display();\n      return true;\n    }\n    return false;\n  },\n\n  /**\n   * positionCursor - Positions the cursor in a textSection if it isn't in one\n   *  already. This method first looks at the previous section (set in the\n   *  keydownHandler) and tries to position the cursor there. If that fails, it\n   *  will position the cursor in the next adjacent text container, creating one\n   *  if necessary. This method will return false if the cursor is currently in\n   *  one of the toolbars.\n   *\n   */\n  positionCursor() {\n    const sel = window.getSelection();\n    let section = null;\n    try {\n      section = findParentBlock(sel.anchorNode);\n    } catch (exception) {\n      return false;\n    }\n    if (this.editToolbar.contains(section) || this.insertToolbar.contains(section)) {\n      return false;\n    }\n    const range = sel.getRangeAt(0);\n    if (\n      !section.classList.contains(this.classes.textSection)\n      && section.tagName !== 'H2'\n      && section.tagName !== 'H1'\n    ) {\n      if (\n        this.prevSection\n        && this.$innerCtn.contains(this.prevSection)\n        && this.prevSection.classList.contains(this.classes.textSection)\n      ) {\n        sel.collapse(this.prevSection, this.prevOffset);\n      } else {\n        section = this.prevSectionPrevSibling;\n        if (!section) return false;\n        const newSection = this.createTextSection();\n        if (section.nextSibling) {\n          this.$innerCtn.insertBefore(newSection, section.nextSibling);\n        } else {\n          this.$innerCtn.appendChild(newSection);\n        }\n        range.selectNodeContents(newSection);\n        range.collapse(true);\n      }\n    }\n    return true;\n  },\n\n  /*\n  ##     ## ######## #### ##        ######\n  ##     ##    ##     ##  ##       ##    ##\n  ##     ##    ##     ##  ##       ##\n  ##     ##    ##     ##  ##        ######\n  ##     ##    ##     ##  ##             ##\n  ##     ##    ##     ##  ##       ##    ##\n   #######     ##    #### ########  ######\n  */\n\n  /**\n   * load - Load a previous version of the editor. The given htmlSTring MUST be\n   *  that returned by this.html(true). If the given htmlString doesn not\n   *  contain the appropriate $innerCtn class, it will be rejected. If passed\n   *  correctly, the given htmlString will replace the current editor's\n   *  $innerCtn.\n   *\n   * @param {string} htmlString A string containing a previous state of a\n   *  writefree editor.\n   *\n   * @returns {boolean} Returns true if the given htmlString was formatted\n   *  properly and was inserted into the editor. Else returns false.\n   */\n  load(htmlString) {\n    const parser = new DOMParser();\n    let html = htmlString;\n    if (typeof htmlString === 'string') {\n      html = parser.parseFromString(htmlString, 'text/html');\n    }\n    let newInnerCtn = null;\n    try {\n      newInnerCtn = html.body.firstChild;\n    } catch (exc) {\n      return false;\n    }\n    if (newInnerCtn && newInnerCtn.classList.contains(this.innerCtnClass)) {\n      this.$ctn.removeChild(this.$innerCtn);\n      this.$ctn.appendChild(newInnerCtn);\n      this.$innerCtn = newInnerCtn;\n    }\n    return this.$ctn.contains(newInnerCtn);\n  },\n\n  /**\n   * html - Returns the Editor in HTML form.\n   *\n   * @param {boolean} [editable=false] Determines whether the returned HTML will\n   *  have contenteditable set to true or false, according to given value.\n   *\n   * @returns {Element} The Editor in HTML form.\n   */\n  html(editable = false) {\n    const returnEl = this.$innerCtn.cloneNode(true);\n    returnEl.setAttribute('contenteditable', editable);\n    return returnEl.outerHTML;\n  },\n\n  /**\n   * getToolbar - Returns the Toolbar associated with this Editor.\n   *\n   * @returns {Toolbar} The Toolbar associated with this Editor.\n   */\n  getToolbar() { return this.editToolbar; },\n\n  /**\n   * isFirst - Determines if given HTML Element is the first element of the\n   *  Editor.\n   *\n   * @param {Element} $node The HTML Element to test.\n   *\n   * @returns {boolean} True if the given Element is the first in the Editor,\n   *  else returns false.\n   */\n  isFirst($node) {\n    if (this.$innerCtn.children[0] === $node) {\n      return true;\n    }\n    return false;\n  },\n};\n\nvar toolbarStyle = '@import url(\"https://fonts.googleapis.com/css?family=Crimson+Text:400,700|Roboto\");@keyframes fade-in {  from {    opacity: 0;  }  to {    opacity: 1;  }}@keyframes expand-width {  from {    width: 0;  }  to {    width: 15rem;  }}.wf__ctn * {  margin: 0;  padding: 0;  font-size: 16px;  color: inherit;}.wf__ctn button:hover,.wf__ctn a:hover,.wf__ctn input:hover {  box-shadow: none;  transform: none;}.wf__toolbar {  position: fixed;  display: inline-block;  font-family: \"Roboto\", sans-serif;  background: linear-gradient(#555, #222);  padding: 0.25rem 0.25rem;  border-radius: 0.25rem;  box-shadow: 0.1rem 0.1rem 1rem 0.1rem rgba(0, 0, 0, 0.55);  animation: fade-in 0.15s ease-out;  transition: width 0.2s;  overflow: hidden;  min-width: 1rem;}.wf__toolbar__btn-ctn {  transition: transform 0.2s;}.wf__toolbar__btn {  display: inline-block;  box-sizing: border-box;  margin: 0 0.1rem;  background: none;  color: #fff;  border: 1px solid rgba(0, 0, 0, 0);  border-radius: 0.25rem;  transition: all 0.2s;  font-size: 16px;  width: 32px;  height: 28px;  line-height: 20px;  text-align: center;  outline: none;  z-index: 0;  vertical-align: baseline;  box-shadow: none;  transform: none;}.wf__toolbar__btn:hover {  display: inline-block;  box-sizing: border-box;  margin: 0 0.1rem;  color: #fff;  border: 1px solid #fff;  border-radius: 0.25rem;  transition: all 0.2s;  font-size: 16px;  width: 32px;  height: 28px;  line-height: 20px;  text-align: center;  outline: none;  z-index: 0;  vertical-align: baseline;  background: rgba(255, 255, 255, 0.075);}.wf__toolbar__btn:active {  display: inline-block;  box-sizing: border-box;  margin: 0 0.1rem;  color: #fff;  border: 1px solid #bbb;  border-radius: 0.25rem;  transition: all 0.2s;  font-size: 16px;  width: 32px;  height: 28px;  line-height: 20px;  text-align: center;  outline: none;  z-index: 0;  vertical-align: baseline;  background: rgba(0, 0, 0, 0.2);}.wf__toolbar__btn-active {  color: #A9D943;  border-color: #A9D943;}.wf__toolbar__btn-disabled {  color: #666;}.wf__toolbar__btn-disabled:hover {  display: inline-block;  box-sizing: border-box;  margin: 0 0.1rem;  color: #fff;  border: 1px solid rgba(0, 0, 0, 0);  border-radius: 0.25rem;  transition: all 0.2s;  font-size: 16px;  width: 32px;  height: 28px;  line-height: 20px;  text-align: center;  outline: none;  z-index: 0;  vertical-align: baseline;  transform: none;  color: #666;  box-shadow: none;}.wf__toolbar__input-ctn {  box-sizing: border-box;  position: absolute;  width: 15rem;  height: 100%;  top: 0;  left: 0;  z-index: 1;  padding: 0.25rem 0.25rem;  padding-left: 5.3333333333px;  animation: fade-in 0.15s ease-out;  transition: all 0.2s;}.wf__toolbar__input-ctn button {  display: inline-block;  position: absolute;  right: 5.3333333333px;  margin-right: 0;}.wf__toolbar__input-ctn button:hover {  display: inline-block;  position: absolute;  right: 5.3333333333px;  margin-right: 0;}.wf__toolbar__input {  display: inline-block;  max-width: 100%;  height: 100%;  margin: 0;  padding: 0;  border: none;  outline: none;  background: none;  color: white;  padding-left: 0.1rem;  font-size: 16px;}.wf__toolbar-hide-up {  transform: translateY(-150%);  visibility: hidden;}.wf__toolbar-hide-down {  transform: translateY(150%);  visibility: hidden;}.wf__toolbar-wide {  width: 15rem;}.wf__toolbar.hide {  display: none !important;}.wf__editor p:first-child:empty:not(:focus)::before,.wf__editor div:first-child:empty:not(:focus)::before {  content: \"placeholder_text\";  color: grey;  font-style: italic;}/*# sourceMappingURL=site.css.map */';\n\n// writeFree.js\n\nconst defaultContainerStyle = {\n  'min-height': '2em',\n  'max-width': '100%',\n  margin: '1em auto',\n  padding: '0em 1.5rem',\n  'font-size': '1.25rem',\n  'font-family': \"'Crimson Text', serif\",\n  outline: 'none',\n  color: '#333',\n};\n\nconst defaultSectionStyle = {\n  'font-size': '1.25rem',\n  'max-width': '35rem',\n  'margin-left': 'auto',\n  'margin-right': 'auto',\n};\n\nconst defaultLargeHeadingStyle = Object.assign({}, defaultSectionStyle);\ndefaultLargeHeadingStyle['font-size'] = '2rem';\n\nconst defaultSmallHeadingStyle = Object.assign({}, defaultSectionStyle);\ndefaultSmallHeadingStyle['font-size'] = '1.5rem';\n\nconst defaultImgStyle = Object.assign({}, defaultSectionStyle);\ndefaultImgStyle['max-width'] = '100%';\n\n\nconst defaultOptions = {\n  divOrPar: 'p',\n  sectionClass: '',\n  sectionStyle: defaultSectionStyle,\n  containerClass: '',\n  containerStyle: defaultContainerStyle,\n  largeHeadingClass: '',\n  largeHeadingStyle: defaultLargeHeadingStyle,\n  smallHeadingClass: '',\n  smallHeadingStyle: defaultSmallHeadingStyle,\n  imgClass: '',\n  imgStyle: defaultImgStyle,\n  emptyPlaceholder: 'Try writing here...',\n};\n\n/**\n * WriteFree - The initialization function used to create instances of the\n *  WriteFree editor.\n *\n * @param {Element} $ctn - The empty HTML Element, usually a div to be used as the\n *  container for the WriteFree editor.\n *\n * @returns {Editor} The WriteFree editor.\n */\nfunction WriteFree($ctn, userOptions = {}) {\n  const options = (function setOptions() {\n    const globalOptions = Object.create(defaultOptions);\n    if (userOptions && typeof userOptions === 'object') {\n      Object.keys(userOptions).forEach((option) => {\n        globalOptions[option] = userOptions[option];\n      });\n    }\n    return globalOptions;\n  }());\n  const $toolbarStyle = document.createElement('style');\n  const newToolbarStyle = toolbarStyle.replace('placeholder_text', options.emptyPlaceholder);\n  $toolbarStyle.appendChild(document.createTextNode(newToolbarStyle));\n  document.getElementsByTagName('head')[0].appendChild($toolbarStyle);\n\n\n  // Create and initialize the editor.\n  const Editor = Object.create(editorBase);\n  Editor.initWFEditor($ctn, options);\n  $ctn.classList.add('wf__ctn');\n  return {\n    html: Editor.html.bind(Editor),\n    load: Editor.load.bind(Editor),\n  };\n}\n\nexport default WriteFree;\n"]}