{"version":3,"sources":["jsdelivr-header.js","/npm/dash-textangular@1.5.20/dist/textAngular.umd.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA,CCNC,SAAU,EAAM,GACO,mBAAX,QAAyB,OAAO,IAEzC,OAAO,cAAe,CAAC,QAAQ,yCAAyC,SAAU,EAAG,GACnF,OAAQ,EAAK,oBAAsB,EAAQ,EAC7C,IAC4B,iBAAZ,QAIhB,OAAO,QAAU,EAAQ,QAAQ,SAAS,QAAQ,yCAElD,EAAkB,YAAI,EAAQ,MAElC,CAdA,CAcE,MAAM,SAAU,GAclB,IAAI,EAAU,CAAC,EAqDf,QAAQ,OAAO,mBAAoB,IAClC,SAAS,kBAXV,SAAiC,EAAM,GACnC,IAAI,GAAiB,KAAT,GAAe,EAAQ,eAAe,GAAO,KAAK,qEAC9D,GACK,EAAe,UAAuC,KAA3B,EAAe,UAxDnD,SAA4B,GACxB,IACI,OAA0C,IAAnC,QAAQ,QAAQ,GAAQ,MACnC,CAAC,MAAM,GACH,OAAO,CACX,CACJ,CAkDsE,CAAmB,EAAe,YAC9F,EAAe,UAAY,EAAe,aAAe,EAAe,UAE1E,KAAM,2CAA6C,EAAO,6DAC9D,EAAQ,GAAQ,CACpB,IAIC,MAAM,UAAW,GAEjB,MAAM,YAAc,CAKjB,0BAA0B,EA0B1B,YAAc,GACd,QAAS,CACL,CAAC,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,IAAK,MAAO,SACjD,CAAC,OAAQ,UAAW,YAAa,gBAAiB,KAAM,KAAM,OAAQ,OAAQ,SAC9E,CAAC,cAAc,gBAAgB,eAAe,cAAc,SAAS,WACrE,CAAC,OAAQ,cAAe,aAAc,cAAe,YAAa,cAEtE,QAAS,CACL,SAAU,WACV,QAAS,cACT,aAAc,YACd,cAAe,kBACf,oBAAqB,SACrB,SAAU,WACV,WAAY,eACZ,WAAY,gBAEhB,qBAAuB,CACnB,EAAG,CAAC,OAAO,KAEf,MAAO,CAEH,gBAAiB,SAAS,GAAyC,EAEnE,gBAAiB,SAAS,GAAyC,GAEvE,uBAEI,SAAS,EAAM,GACX,IAAI,EAAS,IAAI,WACjB,MAAiC,UAA9B,EAAK,KAAK,UAAU,EAAG,KACtB,EAAO,OAAS,WACS,KAAlB,EAAO,QAAe,EAAa,cAAe,EAAO,QAAQ,EACxE,EAEA,EAAO,cAAc,IAEd,EAGf,IAMP,MAAM,uBAAwB,CAAC,IAAI,QAOnC,MAAM,oBAAqB,CACxB,CAGI,SAAU,MACV,gBAAiB,kBACjB,YAAa,SAAS,GAClB,IAAI,EAAS,QAAQ,QAAQ,qBACzB,EAAa,EAAQ,KAAK,cAE9B,QAAQ,QAAQ,GAAY,SAAS,GACjC,EAAO,KAAK,EAAK,KAAM,EAAK,MAChC,IACA,EAAO,KAAK,MAAO,EAAO,KAAK,oBAC/B,EAAQ,YAAY,EACxB,KAIP,MAAM,iBAAkB,CAMrB,KAAM,CACF,QAAS,2BAGb,QAAS,CACL,QAAS,YAEb,EAAG,CACC,QAAS,aAEb,IAAK,CACD,QAAS,qBAEb,GAAI,CACA,QAAS,kBAEb,GAAI,CACA,QAAS,gBAEb,MAAO,CACH,QAAS,wCAEb,KAAM,CACF,QAAS,QAEb,KAAM,CACF,QAAS,QAEb,KAAM,CACF,QAAS,QAEb,OAAQ,CACJ,QAAS,UAEb,UAAW,CACP,QAAS,aAEb,cAAc,CACV,QAAS,iBAEb,YAAa,CACT,QAAS,mBAEb,aAAc,CACV,QAAS,oBAEb,YAAa,CACT,QAAS,gBAEb,cAAe,CACX,QAAS,UAEb,OAAQ,CACJ,QAAS,mBAEb,QAAS,CACL,QAAS,mBAEb,MAAO,CACH,QAAS,oBAEb,YAAa,CACT,aAAc,sCACd,QAAS,eACT,OAAQ,+EAEZ,YAAa,CACT,QAAS,eACT,aAAc,uCAElB,WAAY,CACR,QAAS,qBACT,aAAc,gCAElB,SAAU,CACN,aAAc,CACV,QAAS,UAEb,aAAc,CACV,QAAS,UAEb,aAAc,CACV,WAAY,uBAGpB,UAAW,CACP,QAAS,uBAET,UAAW,CACX,QAAS,8BAGhB,QAAQ,kBAAmB,CAAC,UAAU,iBAAkB,SAAS,EAAS,GACvE,MAAO,CACH,kBAAmB,SAAS,EAAO,EAAU,GAGzC,IAAI,EAAa,WACb,EAAY,4BACZ,EAAY,aAChB,EACA,EAAM,iBACN,EAAY,gBAAgB,QAAQ,IAAI,QAAS,SACjD,IAAI,EAAY,EAAY,gBAAgB,iBAC5C,EAAU,QACV,IAAI,EAAc,QAAQ,QAAQ,uDAC9B,EAAa,QAAQ,QAAQ,iHACjC,EAAW,GAAG,SAAS,SAAS,GAC5B,EAAM,iBACN,EAAS,IAAI,CACT,MAAS,OACT,OAAU,KAEd,GACJ,IACA,IAAI,EAAa,QAAQ,QAAQ,gHACjC,EAAW,GAAG,SAAS,SAAS,GAC5B,EAAM,iBACN,EAAS,IAAI,CACT,MAAS,MACT,OAAU,KAEd,GACJ,IACA,IAAI,EAAc,QAAQ,QAAQ,gHAClC,EAAY,GAAG,SAAS,SAAS,GAC7B,EAAM,iBACN,EAAS,IAAI,CACT,MAAS,MACT,OAAU,KAEd,GACJ,IACA,IAAI,EAAc,QAAQ,QAAQ,iHAClC,EAAY,GAAG,SAAS,SAAS,GAC7B,EAAM,iBACN,EAAS,IAAI,CACT,MAAO,GACP,OAAQ,KAEZ,GACJ,IACA,EAAY,OAAO,GACnB,EAAY,OAAO,GACnB,EAAY,OAAO,GACnB,EAAY,OAAO,GACnB,EAAU,OAAO,GAEjB,EAAc,QAAQ,QAAQ,uDAC9B,IAAI,EAAY,QAAQ,QAAQ,4IAChC,EAAU,GAAG,SAAS,SAAS,GAC3B,EAAM,iBAEN,EAAS,IAAI,QAAS,QAEtB,EAAS,IAAI,WAAY,QAEzB,EAAS,IAAI,aAAc,QAC3B,GACJ,IACA,IAAI,EAAa,QAAQ,QAAQ,6IACjC,EAAW,GAAG,SAAS,SAAS,GAC5B,EAAM,iBAEN,EAAS,IAAI,QAAS,SAEtB,EAAS,IAAI,WAAY,SAEzB,EAAS,IAAI,aAAc,SAC3B,GACJ,IACA,IAAI,EAAY,QAAQ,QAAQ,+IAChC,EAAU,GAAG,SAAS,SAAS,GAC3B,EAAM,iBAEN,EAAS,IAAI,QAAS,IAEtB,EAAS,IAAI,WAAY,IAEzB,EAAS,IAAI,aAAc,IAC3B,GACJ,IACA,EAAY,OAAO,GACnB,EAAY,OAAO,GACnB,EAAY,OAAO,GACnB,EAAU,OAAO,GAEjB,EAAc,QAAQ,QAAQ,2BAC9B,IAAI,EAAS,QAAQ,QAAQ,yIAC7B,EAAO,GAAG,SAAS,SAAS,GACxB,EAAM,iBACN,EAAS,SACT,GACJ,IACA,EAAY,OAAO,GACnB,EAAU,OAAO,GAEjB,EAAY,YAAY,GACxB,EAAY,kBAAkB,EAClC,EACA,gBAAiB,SAAS,EAAO,EAAU,GAGvC,EAAM,iBACN,EAAY,gBAAgB,QAAQ,IAAI,QAAS,SACjD,IAAI,EAAY,EAAY,gBAAgB,iBAC5C,EAAU,QACV,EAAU,IAAI,cAAe,QAC7B,IAAI,EAAO,QAAQ,QAAQ,YAAc,EAAS,KAAK,QAAU,qBAAuB,EAAS,KAAK,QAAU,QAChH,EAAK,IAAI,CACL,QAAW,eACX,YAAa,QACb,SAAY,SACZ,gBAAiB,WACjB,cAAe,SACf,iBAAkB,WAEtB,EAAU,OAAO,GACjB,IAAI,EAAc,QAAQ,QAAQ,sCAC9B,EAAe,QAAQ,QAAQ,yGAA2G,EAAe,SAAS,aAAa,QAAU,mDAC7L,EAAa,GAAG,SAAS,SAAS,GAC9B,EAAM,iBACN,IAAI,EAAU,EAAQ,OAAO,EAAe,WAAW,aAAc,EAAS,KAAK,SAChF,GAAuB,KAAZ,GAA8B,YAAZ,IAC5B,EAAS,KAAK,OAAQ,GACtB,EAAY,6BAEhB,EAAY,aAChB,IACA,EAAY,OAAO,GACnB,IAAI,EAAe,QAAQ,QAAQ,yGAA2G,EAAe,SAAS,aAAa,QAAU,uDAE7L,EAAa,GAAG,SAAS,SAAS,GAC9B,EAAM,iBACN,EAAS,YAAY,EAAS,YAC9B,EAAY,4BACZ,EAAY,aAChB,IACA,EAAY,OAAO,GACnB,IAAI,EAAe,QAAQ,QAAQ,kGAAoG,EAAe,SAAS,aAAa,WAAa,aAC1J,WAA5B,EAAS,KAAK,WACb,EAAa,SAAS,UAE1B,EAAa,GAAG,SAAS,SAAS,GAC9B,EAAM,iBACN,EAAS,KAAK,SAAuC,WAA5B,EAAS,KAAK,UAA0B,GAAK,UACtE,EAAa,YAAY,UACzB,EAAY,2BAChB,IACA,EAAY,OAAO,GACnB,EAAU,OAAO,GACjB,EAAY,YAAY,EAC5B,EACA,sBAAuB,SAAS,GAC5B,IACI,EAAQ,EAAI,MADP,uHAET,OAAQ,GAAS,EAAM,IAAO,IAClC,EAER,IACC,IAAI,CAAC,iBAAkB,UAAW,iBAAkB,cAAe,kBAAmB,YAAa,YAAa,OAC7G,SAAS,EAAgB,EAAS,EAAgB,EAAa,EAAiB,EAAW,EAAW,GAGtG,IAAI,EAAK,CAAC,EAEV,GAFa,EAAU,GAAI,IAEe,IAArC,EAAU,0BAAoD,eAAf,EAAG,QACnD,MAAM,QAAQ,SAAS,cAAjB,CAAgC,mBAAoB,oHAE9D,EAAe,OAAQ,CACnB,UAAW,aACX,YAAa,EAAe,KAAK,QACjC,OAAQ,WACJ,KAAK,UAAU,YACnB,EACA,YAAa,WACT,OAAO,KAAK,UAAU,QAC1B,IAIJ,IAGI,EAAe,WACf,OAAO,KAAK,UAAU,cAAc,cAAe,IAAM,KAAK,KAAK,cAAe,IACtF,EACA,QAAQ,QAAQ,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,OAAO,SAAS,GAN5B,IAAS,EAOnC,EAAe,EAAE,cAAe,CAC5B,WAAY,EAAE,cACd,YAAa,EAAe,QAAQ,QAAU,EAAE,OAAO,GACvD,OAAQ,EACR,aAX+B,EAWM,EAAE,cAVpC,WAAY,OAAO,KAAK,UAAU,sBAAsB,EAAI,IAYvE,IACA,EAAe,IAAK,CAChB,WAAY,IACZ,YAAa,EAAe,EAAE,QAC9B,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,cAAe,MACvD,EACA,YAAa,WAAY,OAAO,KAAK,UAAU,sBAAsB,IAAM,IAG/E,EAAe,MAAO,CAClB,WAAY,MACZ,YAAa,EAAe,IAAI,QAChC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,cAAe,QACvD,EACA,YAAa,WAAY,OAAO,KAAK,UAAU,sBAAsB,MAAQ,IAEjF,EAAe,KAAM,CACjB,UAAW,gBACX,YAAa,EAAe,GAAG,QAC/B,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,sBAAuB,KAC/D,EACA,YAAa,WAAY,OAAO,KAAK,UAAU,kBAAkB,sBAAwB,IAE7F,EAAe,KAAM,CACjB,UAAW,gBACX,YAAa,EAAe,GAAG,QAC/B,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,oBAAqB,KAC7D,EACA,YAAa,WAAY,OAAO,KAAK,UAAU,kBAAkB,oBAAsB,IAE3F,EAAe,QAAS,CACpB,UAAW,oBACX,YAAa,EAAe,MAAM,QAClC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,cAAe,eACvD,EACA,YAAa,WAAY,OAAO,KAAK,UAAU,sBAAsB,aAAe,IAExF,EAAe,OAAQ,CACnB,UAAW,aACX,YAAa,EAAe,KAAK,QACjC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,OAAQ,KAChD,IAEJ,EAAe,OAAQ,CACnB,UAAW,eACX,YAAa,EAAe,KAAK,QACjC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,OAAQ,KAChD,IAEJ,EAAe,OAAQ,CACnB,UAAW,aACX,YAAa,EAAe,KAAK,QACjC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,OAAQ,KAChD,EACA,YAAa,WACT,OAAO,KAAK,UAAU,kBAAkB,OAC5C,EACA,eAAgB,KAEpB,EAAe,cAAe,CAC1B,UAAW,mBACX,YAAa,EAAe,YAAY,QACxC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,cAAe,KACvD,EACA,YAAa,SAAS,GAElB,GAAI,GAA4C,cAA3B,EAAc,SAA0B,OAAO,EACpE,IAAI,GAAS,EACb,GAAI,EAGA,IACI,EACwC,SAApC,EAAc,IAAI,eACc,SAAhC,EAAc,KAAK,UAEqB,UAApC,EAAc,IAAI,eACkB,WAApC,EAAc,IAAI,eACkB,YAApC,EAAc,IAAI,gBAAgC,KAAK,UAAU,kBAAkB,kBAAoB,KAAK,UAAU,kBAAkB,mBACtI,KAAK,UAAU,kBAAkB,cAC/C,CAAE,MAAM,GAGJ,GAAS,CACb,CAGJ,OADA,EAAS,GAAU,KAAK,UAAU,kBAAkB,cAExD,IAEJ,EAAe,eAAgB,CAC3B,UAAW,oBACX,YAAa,EAAe,aAAa,QACzC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,eAAgB,KACxD,EACA,YAAa,SAAS,GAElB,GAAI,GAA4C,cAA3B,EAAc,SAA0B,OAAO,EACpE,IAAI,GAAS,EACb,GAAG,EAGC,IACI,EAA6C,UAApC,EAAc,IAAI,aAC/B,CAAE,MAAM,GAGJ,GAAS,CACb,CAGJ,OADA,EAAS,GAAU,KAAK,UAAU,kBAAkB,eAExD,IAEJ,EAAe,cAAe,CAC1B,UAAW,sBACX,YAAa,EAAe,YAAY,QACxC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,cAAe,KACvD,EACA,YAAa,SAAS,GAClB,IAAI,GAAS,EACb,GAAG,EAGC,IACI,EAA6C,YAApC,EAAc,IAAI,aAC/B,CAAE,MAAM,GAGJ,GAAS,CACb,CAGJ,OADA,EAAS,GAAU,KAAK,UAAU,kBAAkB,cAExD,IAEJ,EAAe,gBAAiB,CAC5B,UAAW,qBACX,YAAa,EAAe,cAAc,QAC1C,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,gBAAiB,KACzD,EACA,YAAa,SAAS,GAElB,GAAI,GAA4C,cAA3B,EAAc,SAA0B,OAAO,EACpE,IAAI,GAAS,EACb,GAAG,EAGC,IACI,EAA6C,WAApC,EAAc,IAAI,aAC/B,CAAE,MAAM,GAGJ,GAAS,CACb,CAIJ,OADA,EAAS,GAAU,KAAK,UAAU,kBAAkB,gBAExD,IAEJ,EAAe,SAAU,CACrB,UAAW,eACX,YAAa,EAAe,OAAO,QACnC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,SAAU,KAClD,EACA,YAAa,WACT,OAAO,KAAK,UAAU,sBAAsB,aAChD,EACA,eAAgB,WAEpB,EAAe,UAAW,CACtB,UAAW,gBACX,YAAa,EAAe,QAAQ,QACpC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,UAAW,KACnD,EACA,YAAa,WACT,OAAO,CACX,EACA,eAAgB,gBAEpB,EAAe,UAAW,CACtB,UAAW,eACX,YAAa,EAAe,OAAO,QACnC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,SAAU,KAClD,EACA,YAAa,WACT,OAAO,KAAK,UAAU,kBAAkB,SAC5C,EACA,eAAgB,MAEpB,EAAe,YAAa,CACxB,UAAW,kBACX,YAAa,EAAe,UAAU,QACtC,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,YAAa,KACrD,EACA,YAAa,WACT,OAAO,KAAK,UAAU,kBAAkB,YAC5C,EACA,eAAgB,MAEpB,EAAe,gBAAiB,CAC5B,UAAW,sBACX,YAAa,EAAe,cAAc,QAC1C,OAAQ,WACJ,OAAO,KAAK,UAAU,cAAc,gBAAiB,KACzD,EACA,YAAa,WACT,OAAO,SAAS,kBAAkB,gBACtC,IAEJ,EAAe,QAAS,CACpB,UAAW,YACX,YAAa,EAAe,MAAM,QAClC,OAAQ,SAAS,EAAU,GACvB,IAAO,EAEP,KAAK,UAAU,cAAc,eAAgB,MAC7C,IAAI,EAAgB,QAAQ,QAAQ,EAAY,uBAChD,EAAmB,EAAY,yBAG/B,IAAI,EAAqB,SAAS,EAAM,GACpC,EAAO,QAAQ,QAAQ,GACvB,IAAI,EAAc,EAgBlB,OAfK,IACD,EAAc,GAElB,QAAQ,QAAQ,EAAK,YAAY,SAAS,GACtC,GAAqC,OAAjC,EAAO,QAAQ,eACkB,OAAjC,EAAO,QAAQ,cACf,EAAc,EAAmB,EAAQ,OACtC,CACH,IAAI,EAAU,QAAQ,QAAQ,WAC9B,EAAQ,KAAK,QAAQ,QAAQ,GAAQ,QACrC,EAAY,MAAM,GAClB,EAAc,CAClB,CACJ,IACA,EAAK,SACE,CACX,EAEA,QAAQ,QAAQ,GAAkB,SAAS,GACA,OAAnC,EAAQ,SAAS,eACkB,OAAnC,EAAQ,SAAS,eAEjB,EAAmB,EAE3B,IAEA,QAAQ,QAAQ,EAAc,KAAK,MAAO,GAC1C,QAAQ,QAAQ,EAAc,KAAK,MAAO,GAG1C,IAAI,EAAU,KAAK,UACf,EAAuB,SAAS,IAChC,EAAO,QAAQ,QAAQ,IAEf,KAAO,EAAQ,gBAAgB,KAAK,IACxC,EAAK,WAAW,SAEpB,QAAQ,QAAQ,EAAK,WAAY,EACrC,EACA,QAAQ,QAAQ,EAAe,GAE5B,EAAc,IAAiD,OAA3C,EAAc,GAAG,QAAQ,eACD,OAA3C,EAAc,GAAG,QAAQ,eACkB,OAA3C,EAAc,GAAG,QAAQ,eAC4B,SAArD,EAAc,GAAG,aAAa,oBAC9B,KAAK,UAAU,cAAc,cAAe,WAEhD,GACJ,IA8FJ,IAAI,EAAkB,SAAU,GAC5B,OAAgD,IAA5C,EAAK,cAAc,QAAQ,aAInC,EAEA,EAAe,cAAe,CAC1B,UAAW,kBACX,YAAa,EAAe,YAAY,QACxC,OAAQ,WACJ,IAAI,EAEJ,IADA,EAAY,EAAQ,OAAO,EAAe,YAAY,aAAc,aACtC,KAAd,GAAkC,YAAd,IAG3B,EAAgB,GAAY,CACzB,EAAY,sBAAsB,SAAuE,MAA5D,EAAY,sBAAsB,QAAQ,eAIvF,EAAY,yBAAyB,EAAY,uBAUrD,IAAI,EAAQ,aAAe,EAAY,KACvC,OAAO,KAAK,UAAU,cAAc,aAAc,GAAO,EAC7D,CAER,EACA,gBAAiB,CACb,QAAS,MACT,OAAQ,EAAgB,qBAGhC,EAAe,cAAe,CAC1B,UAAW,qBACX,YAAa,EAAe,YAAY,QACxC,OAAQ,WACJ,IAAI,EAIJ,GAHA,EAAY,EAAQ,OAAO,EAAe,YAAY,aAAc,aAG/D,EAAgB,IAEb,GAA2B,KAAd,GAAkC,aAAd,IAEjC,QAAU,EAAgB,sBAAsB,GAG5C,SAAS,CAET,IAAI,EAAU,iCAAmC,QAI7C,EAAQ,gEAAkE,QAAU,oCAAsC,EAAU,sEASxI,OAPI,EAAY,sBAAsB,SAAuE,MAA5D,EAAY,sBAAsB,QAAQ,eAIvF,EAAY,yBAAyB,EAAY,uBAG9C,KAAK,UAAU,cAAc,aAAc,GAAO,EAC7D,CAGZ,EACA,gBAAiB,CACb,QAAS,MACT,cAAe,CAAC,mBAChB,OAAQ,EAAgB,qBAGhC,EAAe,aAAc,CACzB,YAAa,EAAe,WAAW,QACvC,UAAW,aACX,OAAQ,WACJ,IAAI,EAQJ,IAJI,EADA,EAAY,sBAAsB,SAAuE,MAA5D,EAAY,sBAAsB,QAAQ,cAC7E,EAAQ,OAAO,EAAe,WAAW,aAAc,EAAY,sBAAsB,MAEzF,EAAQ,OAAO,EAAe,WAAW,aAAc,aAE3C,KAAZ,GAA8B,YAAZ,IAGvB,EAAgB,GACjB,OAAO,KAAK,UAAU,cAAc,aAAc,GAAS,EAGvE,EACA,YAAa,SAAS,GAClB,QAAG,GAAmD,MAA7B,EAAc,GAAG,OAE9C,EACA,gBAAiB,CACb,QAAS,IACT,OAAQ,EAAgB,mBAGhC,EAAe,YAAa,CACxB,QAAS,6GACT,UAAU,EACV,UAAW,EACX,YAAa,WACT,IAEI,EAFc,KAAK,UAAU,gBAAgB,KAEnB,GAAG,WAAa,GAC1C,EAAY,EAkBhB,MAfmD,KAA/C,EAAY,QAAQ,kBAAmB,KACZ,KAAvB,EAAY,SACZ,EAAY,EAAY,QAAQ,kFAAmF,IAC9G,QAAQ,0BAA2B,KACnC,QAAQ,eAAgB,IACxB,QAAQ,QAAS,KACjB,MAAM,QAAQ,QAK3B,KAAK,UAAY,EAEjB,KAAK,UAAU,UAAY,GAEpB,CACX,IAEJ,EAAe,YAAa,CACxB,QAAS,kHACT,UAAU,EACV,UAAW,EACX,YAAa,WACT,IAAI,EAAc,KAAK,UAAU,gBAAgB,KAI7C,GAHa,EAAY,GAAG,WAAa,EAAY,GAAG,aAGjC,QAAQ,iBAAiB,IAAI,QAAQ,QAAQ,KAAK,QAAQ,QAAS,KAAK,OAKnG,OAHA,KAAK,UAAY,EAEjB,KAAK,UAAU,UAAY,GACpB,CACX,GAER,IAiBA,IAkBI,EACE,WAMJ,IALA,IACC,EAAI,EACJ,EAAM,SAAS,cAAc,OAC7B,EAAM,EAAI,qBAAqB,KAG/B,EAAI,UAAY,uBAAsB,EAAK,2BAC3C,EAAI,KAGL,OAAO,EAAI,EAAI,EAVX,SAWL,CAZI,GADD,EAcK,yBAAyB,KAAK,UAAU,WAM7C,GALQ,UAAU,UAAU,cAAc,QAAQ,WAKpC,GAAe,CAAC,GAElC,EAAY,IACJ,EAAY,KAClB,EAAY,QACZ,EAAY,OACZ,EAAY,MACZ,EAAY,WACZ,WAAa,OAAO,IAAI,MAAO,SAAW,EA8B5C,IAkBI,EAAO,EAAY,EAAe,EAAa,EAAgB,EAoB5D,EAtCH,EAAgB,sMAChB,EAAe,gBAEf,EAAgB,oNAiBpB,GAZK,OAAO,UAAU,OACrB,OAAO,UAAU,KAAO,WACvB,OAAO,KAAK,QAAQ,aAAc,GACnC,GASE,EAAoB,QAA2B,IAAtB,EAAgC,CAG3D,IAFA,IAAI,EAAU,SAAS,YAEf,EAAI,EAAG,EAAI,EAAQ,OAAQ,IAClC,IAA+B,IAA5B,EAAQ,GAAG,MAAM,QAAgB,EAAQ,GAAG,MAAM,UAAU,MAAM,oBACjE,EAAQ,GAAG,MACV,EAAQ,GAAG,KAAK,MAAM,8BAA8B,CACtD,EAAQ,EAAQ,GAChB,KACD,CAKC,IAIE,EAAQ,SAAS,cAAc,SAEhC,GAAuB,EAAM,YAAY,SAAS,eAAe,KAGpE,SAAS,qBAAqB,QAAQ,GAAG,YAAY,GAPtD,EASQ,EAAM,OAKf,EAAa,SAAS,EAAU,GAC/B,OAAO,EAAY,EAAO,EAAU,EACrC,EACA,EAAc,SAAS,EAAQ,EAAU,GACxC,IAAI,EACA,EAiBJ,OAdG,EAAO,SAAU,EAAc,KAAK,IAAI,EAAO,SAAS,OAAS,EAAG,GAC/D,EAAO,QAAO,EAAc,KAAK,IAAI,EAAO,MAAM,OAAS,EAAG,IAGnE,EAAO,WACT,EAAO,WAAW,EAAW,IAAM,EAAQ,IAAK,GAGhD,EAAO,QAAQ,EAAU,EAAO,GAG9B,EAAM,MAAO,EAAe,EAAM,MAAM,GACnC,EAAM,WAAU,EAAe,EAAM,SAAS,IAE/C,CACR,EAEA,EAAgB,SAAS,EAAM,GAC9B,IAAI,EAAG,EACP,IAAK,EAAE,EAAG,EAAI,EAAM,OAAQ,IAE3B,GAAI,EAAM,GAAG,UAAY,EAAK,QAAS,CACtC,EAAY,EACZ,KACD,CAED,OAAO,CACR,EAEA,EAAgB,SAAS,GACxB,EAAe,EAAO,EACvB,EAEA,EAAiB,SAAS,EAAO,GAChC,IAAI,EAAQ,EAAM,UAAY,EAAM,MACpC,GAAI,GAA0B,IAAjB,EAAM,OAAnB,CACA,IAAI,EAAY,EAAc,EAAM,GACjC,EAAM,WACR,EAAM,WAAW,GAEjB,EAAM,WAAW,EALqB,CAOxC,CACD,CAEA,QAAQ,OAAO,wBAAyB,IACvC,QAAQ,eAAgB,CAAC,WACtB,OAAO,SAAS,GAEZ,OAAI,EACY,KAAR,OAA0C,IAAtB,EAAkC,MAAS,GAAqB,EAAI,IAAM,IACzF,GAAqB,EAAI,EAAI,cAAgB,EAFzC,GAAqB,EAAI,IAAM,GAGpD,CACJ,IAAI,QAAQ,yBAA0B,CAAC,oBAAqB,QAAS,SAAS,EAAmB,GAC7F,OAAO,SAAS,GACZ,IAAI,EAAU,QAAQ,QAAQ,eAoB9B,OAnBA,EAAQ,GAAG,UAAY,EAEvB,QAAQ,QAAQ,GAAmB,SAAS,GACxC,IAAI,EAAW,GAEZ,EAAS,UAAkC,KAAtB,EAAS,SAC7B,EAAW,EAAQ,KAAK,EAAS,UAE7B,EAAS,iBAAgD,KAA7B,EAAS,kBACzC,EAAW,EAAM,eAAe,EAAS,EAAS,kBAEtD,QAAQ,QAAQ,GAAU,SAAS,GAC/B,EAAW,QAAQ,QAAQ,GACxB,EAAS,UAAkC,KAAtB,EAAS,UAAmB,EAAS,iBAAgD,KAA7B,EAAS,qBACtC,IAA5C,EAAS,KAAK,EAAS,kBAAgC,EAAS,YAAY,GAC5E,EAAS,YAAY,EAChC,GACJ,IAEO,EAAQ,GAAG,SACtB,CACJ,IAAI,QAAQ,eAAe,WAwEvB,OArEkB,SAAS,EAAM,GAC7B,IAAI,IAAS,QAAQ,SAAS,IAAS,EAAK,QAAU,EAAG,OAAO,EAchE,IATA,IAKI,EAAO,EAAU,EALjB,EAAkB,2CAIlB,EAA2B,wDACa,EAAY,GAAI,EAAY,EAIlE,EAAQ,EAAyB,KAAK,IAExC,GADA,EAAgB,EAAM,IACQ,QAAQ,WAAY,KAClD,GAAa,EAAK,UAAU,EAAW,EAAM,OAAS,EACtD,EAAY,EAAM,MAAQ,EAAM,GAAG,OAuBvC,GApBI,IAEA,GAAa,EAAK,UAAU,GAC5B,EAAK,EACL,EAAU,GACV,EAAU,IAeT,EAAY,CACb,KAAO,EAAQ,EAAgB,KAAK,IAChC,GAAa,EAAK,UAAU,EAAW,EAAM,MAAM,GACnD,EAAW,EAAM,IAEjB,EAAQ,qLAAqL,KAAK,KAE9L,EAAW,EAAS,QAAQ,uLAAwL,KAEvM,OAAS,IAClB,GAAa,IAAM,GAGvB,GAAa,IAAM,EAEvB,EAAY,EAAgB,UAEhC,GAAa,EAAK,UAAU,EAChC,CAGA,OAAG,EAAY,EAEF,EAAU,QAAQ,uCAAwC,MAEzD,CAClB,CAEJ,IAAG,QAAQ,aAAc,CAAC,YAAa,SAA2B,GAgB9D,IAdA,IAAI,EAAgB,CAChB,CACI,SAAU,cACV,OAAQ,CAAE,QACV,IAAK,KAET,CACI,SAAU,aACV,OAAQ,CAAE,UACV,IAAK,MAIT,EAAa,GACT,EAAI,EAAG,EAAI,EAAc,OAAQ,IAAI,CAEzC,IADA,IAAI,EAAgB,IAAM,EAAc,GAAG,SAAW,SAC9C,EAAI,EAAG,EAAI,EAAc,GAAG,OAAO,OAAQ,IAE5C,EAAI,IAAG,GAAiB,KAC3B,GAAiB,EAAc,GAAG,OAAO,GAE7C,GAAiB,MACjB,EAAW,KAAK,EACpB,CACA,IAAI,EAAmB,IAAM,EAAW,KAAK,KAAO,IAEpD,SAAS,EAAW,EAAM,GAKtB,IAJA,IAEI,EAFA,EAAQ,EACR,EAAY,EAEZ,EAAW,YACT,EAAQ,EAAS,KAAK,IAExB,GADA,EAAY,EAAM,MACW,MAA1B,EAAM,GAAG,OAAO,EAAG,GAAW,CAC7B,GAAa,IAAV,EAAa,MACX,GACT,MAAM,IAEV,OAAO,EACH,EAAK,UAAU,EAAG,GAElB,QAAQ,QAAQ,GAAS,GAAG,UAAU,UAAU,EAAQ,QACxD,EAAK,UAAU,EACvB,CAEA,SAAS,EAAsB,GAC3B,IAAI,IAAS,QAAQ,SAAS,IAAS,EAAK,QAAU,EAAG,OAAO,EAIhE,IAHA,IAAI,EAEA,EAAO,EAAU,EAAU,EAAyB,EADpD,EAAoB,mDACe,EAAa,GAAa,EAAY,GAAI,EAAY,EACvF,EAAQ,EAAkB,KAAK,IAAM,CAGvC,EAAW,EAAM,IAAM,EAAM,GAC7B,IAAI,EAAa,IAAI,OAAO,EAAkB,KAE9C,GAAG,QAAQ,SAAS,IAAa,EAAW,KAAK,GAAU,CAEvD,EAAS,GAIT,IAFA,IAAI,EAAiB,IAAI,OAAO,EAAkB,MAE5C,EAAW,EAAe,KAAK,IACjC,IAAI,EAAI,EAAG,EAAI,EAAc,OAAQ,IAC5B,EAAY,EAAF,EAAO,KAClB,GAAU,IAAM,EAAc,GAAG,IAAM,KAKnD,EAAU,EAAsB,EAAK,UAAU,EAAW,EAAM,QAE7D,EAAW,OAAS,EACnB,GAAa,EAAW,EAAS,GAC/B,GAAa,EAEnB,EAAW,EAAS,QAAQ,IAAI,OAAO,EAAkB,MAAO,IAEhE,GAAa,IAAM,EAAM,GAAG,OACzB,EAAS,OAAS,IAAG,GAAa,WAAa,EAAW,KAC7D,GAAa,EAAM,GAAK,IAExB,EAAY,EAAM,MAAQ,EAAM,GAAG,OACnC,EAAa,CACjB,CACJ,CAKA,OAJG,EAAW,OAAS,EACnB,GAAa,EAAW,EAAK,UAAU,GAAY,GAElD,GAAa,EAAK,UAAU,GAC1B,CACX,CAgCA,IAAI,EAAO,IAAI,OAAO,yFAClB,EAAO,IAAI,OAAO,yFAClB,EAAO,IAAI,OAAO,yFAEtB,OAAO,SAAoB,EAAQ,EAAS,GAExC,IAAM,EACF,IACI,EAAS,EAAsB,EACnC,CAAE,MAAO,GACT,CAWJ,GALA,EA9CJ,SAAmC,GAC/B,IAAI,IAAS,QAAQ,SAAS,IAAS,EAAK,QAAU,EAAG,OAAO,EAKhE,IAHA,IACI,EADA,EAAmB,mDACZ,EAAY,GAAI,EAAY,EAEjC,EAAQ,EAAiB,KAAK,IAAM,CAEtC,GAAa,EAAK,UAAU,EAAW,EAAM,OAE7C,EAAY,EAAM,MAAQ,EAAM,GAAG,OAEnC,IAAI,EAAS,IAAM,EAAM,GAAK,EAAM,GAEjC,gCAAgC,KAAK,GAEpC,EAAS,EAAO,QAAQ,+BAAgC,2BAA6B,EAAM,IAAM,EAAM,IAAM,MAG7G,GAAU,uBAAyB,EAAM,IAAM,EAAM,IAAM,KAI/D,GAFA,GAAU,GAGd,CAEA,OAAO,EAAY,EAAK,UAAU,EACtC,CAmBa,CAA0B,GAK/B,EACA,IAII,GADA,GADA,GADA,EAAS,EAAO,QAAQ,EAAM,KACd,QAAQ,EAAM,KACd,QAAQ,EAAM,KACd,QAAQ,EAAM,GAClC,CAAE,MAAO,GACT,CAGJ,IAAI,EACJ,IACI,EAAO,EAAU,GAEd,IAAQ,EAAO,EACtB,CAAE,MAAO,GACL,EAAO,GAAW,EACtB,CAIA,IAKI,EALA,EAAW,EAAK,MAAM,iCACtB,EAAgB,EAAK,QAAQ,iBAAkB,IAC/C,EAAK,8BACL,EAAQ,EACR,EAAY,EAGhB,IADA,EAAO,GACsC,QAAtC,EAAU,EAAG,KAAK,KAA4B,EAAQ,EAAS,QAClE,GAAQ,EAAc,UAAU,EAAW,EAAQ,OAAS,EAAS,GACrE,EAAY,EAAQ,MAAQ,EAAQ,GAAG,OACvC,IAEJ,OAAO,EAAO,EAAc,UAAU,EAC1C,CACJ,IAAI,QAAQ,sBAAuB,CAAC,KAAM,OAAQ,SAAS,EAAI,GAE3D,OAAO,SAAS,QACE,IAAX,IAAsB,KAAK,QAAU,WAAY,OAAO,CAAQ,GACnE,IAII,EAJA,EAAW,EAAG,QACd,EAAU,EAAS,QACnB,EAAU,KAAK,UAGnB,IACI,EAAS,KAAK,OAAO,EAAU,EAAQ,eAEvC,EAAiB,SAAE,WACf,EAAQ,UAAU,KAAK,EAC3B,GACJ,CAAC,MAAM,GACH,EAAK,MAAM,EACf,EACG,QAAqB,IAAX,IAET,EAAS,SAEjB,CACJ,IAEA,QAAQ,OAAO,kBAAmB,CAAC,0BAClC,QAAQ,gBAAiB,CAAC,cAAe,eAAgB,YAAa,SAAS,EAAa,EAAc,GACvG,IAAI,EAAgB,SAAS,EAAa,GACtC,IAAI,EAAS,EAGT,EAAW,EAAY,KAAK,MAChC,IAAI,EAAI,EAAS,OAAS,EAAG,GAAK,EAAG,IACjC,EAAU,QAAQ,QAAQ,IAAM,EAAc,IAAM,EAAS,GAAG,UAAY,KAAO,EAAc,KACjG,EAAY,MAAM,GAEtB,EAAY,SACZ,EAAY,yBAAyB,EAAQ,GACjD,EACI,EAAuB,SAAS,EAAM,EAAa,EAAS,EAAU,GACtE,IAAI,EAAS,EAGT,EACA,EAEA,EADA,EAAW,EAAK,KAAK,MAEzB,IAAK,EAAI,EAAG,EAAE,EAAS,OAAQ,IAC3B,GAAI,EAAS,GAAG,YAAc,EAAY,GAAG,UAAW,CAEpD,EAAa,EACT,EAAE,IACF,EAAe,EAAS,EAAE,IAE1B,EAAE,EAAE,EAAS,SACb,EAAc,EAAS,EAAE,IAE7B,KACJ,CAIJ,IAAI,EAAO,GAUX,GATI,EACA,GAAQ,IAAM,EAAc,IAAM,EAAY,GAAG,UAAY,KAAO,EAAc,KAElF,GAAQ,IAAM,EAAa,GAAW,IACtC,GAAQ,OAAS,EAAY,GAAG,UAAY,QAC5C,GAAQ,KAAO,EAAa,GAAW,KAE3C,EAAU,QAAQ,QAAQ,IAErB,EAOD,OALA,EAAY,SACZ,EAAK,MAAM,QAAQ,QAAQ,EAAK,GAAG,YACnC,EAAK,MAAM,GACX,EAAK,cACL,EAAY,yBAAyB,EAAQ,IAE1C,GAAK,EAKL,CACK,EAAK,SAAb,IAEI,EAAQ,GACR,EAAU,EAAK,GAAG,SAAS,cAE/B,IADA,GAAS,IAAM,EAAU,IACrB,EAAI,EAAG,EAAI,EAAY,IACvB,GAAS,OAAS,EAAS,GAAG,UAAY,QAE9C,GAAS,KAAO,EAAU,IAC1B,IAAI,EAAQ,GAEZ,IADA,GAAS,IAAM,EAAU,IACrB,EAAI,EAAW,EAAG,EAAI,EAAS,OAAQ,IACvC,GAAS,OAAS,EAAS,GAAG,UAAY,QAE9C,GAAS,KAAO,EAAU,IAE1B,EAAK,MAAM,QAAQ,QAAQ,IAC3B,EAAK,MAAM,GACX,EAAK,MAAM,QAAQ,QAAQ,IAC3B,EAAK,SAEL,EAAY,yBAAyB,EAAQ,GACjD,MA1BI,EAAY,SACZ,EAAK,MAAM,GACX,EAAY,yBAAyB,EAAQ,GAyBrD,EA2EI,EAAW,SAAS,GACjB,gBAAgB,KAAK,EAAU,UAAU,QAAS,EAAY,0BAA0B,QAAQ,QAAQ,GAAW,KAAK,MAAM,IAC5H,EAAY,yBAAyB,EAC9C,EACI,EAAa,SAAS,EAAa,GACnC,IAAI,EAAU,QAAQ,QAAQ,IAAM,EAAa,IAAM,EAAY,GAAG,UAAY,KAAO,EAAa,KACtG,EAAY,MAAM,GAClB,EAAY,SACZ,EAAS,EAAQ,KAAK,MAAM,GAChC,EACI,EAAsB,SAAS,EAAU,EAAa,GAEtD,IADA,IAAI,EAAO,GACH,EAAI,EAAG,EAAI,EAAS,OAAQ,IAChC,GAAQ,IAAM,EAAa,MAAQ,IAAM,EAAS,GAAG,UAAY,KAAO,EAAa,MAAQ,IAEjG,IAAI,EAAU,QAAQ,QAAQ,IAAM,EAAa,IAAM,EAAO,KAAO,EAAa,KAClF,EAAY,MAAM,GAClB,EAAY,SACZ,EAAS,EAAQ,KAAK,MAAM,GAChC,EACI,EAAsB,SAAS,EAAS,GACxC,IAAI,IAAI,EAAI,EAAG,EAAE,EAAQ,WAAW,OAAQ,IAAK,CAC7C,IAAI,EAAK,EAAQ,WAAW,GAExB,EAAG,SAAW,EAAG,QAAQ,MAAM,IAC/B,EAAoB,EAAI,EAEhC,CAEA,GAA2B,OAAvB,EAAQ,WAER,OAAO,EAGX,GAAgB,SAAZ,EACA,OAAO,EAGP,IAAI,EAAU,QAAQ,QAAQ,GAI9B,OAHA,EAAQ,GAAG,UAAY,EAAQ,UAC/B,EAAQ,WAAW,aAAa,EAAQ,GAAI,GAC5C,EAAQ,WAAW,YAAY,GACxB,CAEf,EACA,OAAO,SAAS,EAAe,GAI3B,OADA,EAAgB,EAAa,GACtB,SAAS,EAAS,EAAQ,EAAS,GACtC,IAAI,EAAG,EAAS,EAAM,EAAQ,EAAM,EAAgB,EAAiB,EACjE,EAAiB,QAAQ,QAAQ,IAAM,EAAgB,KAC3D,IAMI,IAAI,EAAK,EALL,EAAY,eACZ,EAAe,EAAY,qBAMC,KAJhC,EAAkB,EAAY,uBAIV,UAC8B,QAA1C,EAAgB,QAAQ,eACxB,kBAAkB,KAAK,EAAgB,KACvC,GAAgB,EAAa,OACC,IAA9B,EAAa,MAAM,QACS,IAA5B,EAAa,IAAI,QA6BgC,SAA1C,EAAgB,QAAQ,eAC/B,GAAgB,EAAa,OACC,IAA9B,EAAa,MAAM,QACS,IAA5B,EAAa,IAAI,QA7BjB,EAAM,EAAgB,UAClB,QAAQ,KAAK,KAEb,EAAM,EAAI,QAAQ,QAAS,YAE3B,UAAU,KAAK,KAEf,EAAM,EAAI,QAAQ,UAAW,YAG7B,mBAAmB,KAAK,KACxB,EAAM,GAAG,QAAQ,mBAAoB,WAGrC,uBAAuB,KAAK,KAC5B,EAAM,GAAG,QAAQ,uBAAwB,YAEzC,kBAAkB,KAAK,KAEvB,EAAM,EAAI,QAAQ,kBAAmB,KAGzC,EAAa,QAAU,EAAM,SAC7B,EAAgB,UAAY,EAC5B,EAAY,yBAAyB,EAAgB,WAAW,IAChE,EAAkB,EAAY,uBAoCmB,MAA1C,EAAgB,QAAQ,eAC/B,GAAgB,EAAa,OACC,IAA9B,EAAa,MAAM,QACS,IAA5B,EAAa,IAAI,QAGjB,EAAM,EAAgB,UAClB,QAAQ,KAAK,KAEb,EAAM,EAAI,QAAQ,QAAS,WAC3B,EAAgB,UAAY,IAEiB,OAA1C,EAAgB,QAAQ,eAC/B,GAAgB,EAAa,OAC7B,EAAa,MAAM,SAAW,EAAa,IAAI,SAE/C,EAAM,EAAgB,UAClB,QAAQ,KAAK,KAEb,EAAM,EAAI,QAAQ,QAAS,IAC3B,EAAgB,UAAY,IAI5C,CAAC,MAAM,GAAG,CAGV,GAAK,EAAL,CACA,IAAI,EAAY,QAAQ,QAAQ,GAC5B,EAAW,GAAmB,EAAgB,SAAW,EAAgB,QAAQ,eACrD,GAChC,GAA6B,sBAA1B,EAAQ,eAAmE,wBAA1B,EAAQ,cAAwC,CAChG,IAAI,EAAU,EAAwC,sBAA1B,EAAQ,cAAwC,KAAO,MAC/E,EAAmB,EAAY,0BAEnC,GAAI,EAAiB,OAAO,IAAkB,OAAZ,GAAiC,OAAZ,GACnD,OA/OY,SAAS,EAAM,EAAc,EAAS,EAAU,GACxE,IAAI,EAAS,EAAG,EAEZ,EACA,EAEA,EAAW,EAAK,KAAK,MACrB,EAAe,GACnB,IAAK,EAAI,EAAG,EAAE,EAAS,OAAQ,IAC3B,IAAK,EAAI,EAAG,EAAE,EAAa,OAAQ,IAC3B,EAAS,GAAG,YAAY,EAAa,MAErC,EAAa,GAAK,GAI1B,EAAa,GAAK,IAClB,EAAe,EAAS,EAAa,GAAK,IAE1C,EAAa,EAAa,OAAO,GAAK,EAAI,EAAS,SACnD,EAAe,EAAS,EAAa,EAAa,OAAO,GAAK,IAIlE,IAAI,EAAO,GACX,GAAI,EACA,IAAK,EAAI,EAAG,EAAI,EAAa,OAAQ,IACjC,GAAQ,IAAM,EAAc,IAAM,EAAa,GAAG,UAAY,KAAO,EAAc,IACnF,EAAa,GAAG,aAEjB,CAEH,IADA,GAAQ,IAAM,EAAa,GAAW,IACjC,EAAI,EAAG,EAAI,EAAa,OAAQ,IACjC,GAAQ,EAAa,GAAG,UACxB,EAAa,GAAG,SAEpB,GAAQ,KAAO,EAAa,GAAW,GAC3C,CAEA,GADA,EAAU,QAAQ,QAAQ,IACrB,EAMD,OAJA,EAAK,MAAM,QAAQ,QAAQ,EAAK,GAAG,YACnC,EAAK,MAAM,GACX,EAAK,cACL,EAAY,yBAAyB,EAAQ,IAE1C,IAAK,EAIR,OAFA,EAAK,MAAM,QACX,EAAY,yBAAyB,EAAQ,IAI7C,IAAI,EAAQ,GACR,EAAU,EAAK,GAAG,SAAS,cAE/B,IADA,GAAS,IAAM,EAAU,IACrB,EAAI,EAAG,EAAI,EAAa,GAAI,IAC5B,GAAS,OAAS,EAAS,GAAG,UAAY,QAE9C,GAAS,KAAO,EAAU,IAC1B,IAAI,EAAQ,GAEZ,IADA,GAAS,IAAM,EAAU,IACrB,EAAI,EAAa,EAAa,OAAO,GAAG,EAAG,EAAI,EAAS,OAAQ,IAChE,GAAS,OAAS,EAAS,GAAG,UAAY,QAE9C,GAAS,KAAO,EAAU,IAC1B,EAAK,MAAM,QAAQ,QAAQ,IAC3B,EAAK,MAAM,GACX,EAAK,MAAM,QAAQ,QAAQ,IAC3B,EAAK,SAEL,EAAY,yBAAyB,EAAQ,GAErD,CAsKuB,CAAsB,EAAW,EAAkB,EAAS,IAAU,EAAS,GAE1F,GAAG,IAAY,EAIX,OAAI,EAAU,GAAG,WAAW,SAAW,EAAiB,QAAoC,IAA1B,EAAiB,QAC/E,EAAY,QAAQ,QAAQ,EAAiB,IACtC,EAAqB,EAAU,SAAU,EAAW,GAAS,EAAM,IAEnE,EAAc,EAAW,GAElC,GAAe,OAAZ,GACL,EAAU,SAAS,GAAG,QAAQ,gBAAkB,GACP,IAAzC,EAAU,SAAS,WAAW,OAE9B,OAAO,EAAc,EAAU,SAAU,GACvC,GAAe,OAAZ,GACL,EAAU,SAAS,GAAG,QAAQ,gBAAkB,GACP,IAAzC,EAAU,SAAS,WAAW,OAE9B,OAAO,EAAW,EAAU,SAAU,GACpC,GAAG,EAAQ,MAAM,KAAmB,EAAU,SAAS,WAAW,CAGpE,GAAI,EAAiB,QACb,EAAU,GAAG,WAAW,SAAW,EAAiB,QAAoC,IAA1B,EAAiB,OAG/E,OADA,EAAY,QAAQ,QAAQ,EAAiB,IACtC,EAAqB,EAAU,SAAU,EAAW,EAAS,IAAU,EAAS,GAG/F,GAAe,OAAZ,GAAgC,OAAZ,EAEnB,OAAO,EAAW,EAAW,GAE7B,IAAI,GAAqB,EAMzB,OALA,QAAQ,QAAQ,EAAU,YAAY,SAAS,GACxC,EAAK,QAAQ,MAAM,KAClB,GAAqB,EAE7B,IAEW,EADR,EAC4B,EAAU,WAEV,CAAC,QAAQ,QAAQ,QAAU,EAAgB,UAAY,UAAU,IAF3C,EAAW,EAKxE,CAAM,GAAG,EAAQ,MAAM,GAAe,CAIlC,GAAqB,KAFrB,EAAS,EAAY,2BAEX,OAEN,EAAU,QAAQ,QAAQ,IAAM,EAAU,QAAU,EAAgB,UAAY,UAAY,EAAU,KACtG,EAAU,KAAK,IACf,EAAU,OAAO,OACf,IAAqB,IAAlB,EAAO,SAAqD,OAApC,EAAO,GAAG,QAAQ,eAA8D,OAApC,EAAO,GAAG,QAAQ,eAC3F,OAAG,EAAO,GAAG,QAAQ,gBAAkB,EAE5B,EAAc,QAAQ,QAAQ,EAAO,IAAK,GAE1C,EAAW,QAAQ,QAAQ,EAAO,IAAK,GAGlD,EAAO,GACP,IAAI,EAAS,GACb,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IAE1B,GAA0B,IAAvB,EAAO,GAAG,SAAe,CACxB,IAAI,EAAK,QAAQ,QAAQ,EAAO,IAEhC,GAAuC,OAApC,EAAO,GAAG,QAAQ,cAAwB,SACD,OAApC,EAAO,GAAG,QAAQ,eAA8D,OAApC,EAAO,GAAG,QAAQ,cAClE,GAAQ,EAAG,GAAG,UAC2B,SAApC,EAAO,GAAG,QAAQ,eAA+E,OAAlD,EAAO,GAAG,WAAW,GAAG,QAAQ,eAA4E,OAAlD,EAAO,GAAG,WAAW,GAAG,QAAQ,cAG9I,GAAQ,IAAM,EAAa,MAAQ,IAAM,EAAG,GAAG,UAAY,KAAO,EAAa,MAAQ,IAFvF,GAAQ,EAAG,GAAG,WAAW,GAAG,UAIhC,EAAO,QAAQ,EACnB,CAGJ,EAAU,QAAQ,QAAQ,IAAM,EAAU,IAAM,EAAO,KAAO,EAAU,KACxE,EAAO,MAAM,YAAY,GACzB,QAAQ,QAAQ,GAAQ,SAAS,GAAQ,EAAM,QAAU,GAC7D,CAEA,YADA,EAAY,yBAAyB,EAAQ,GAEjD,CACJ,KAAM,IAA6B,gBAA1B,EAAQ,cAAgC,CAa7C,IAX6B,aAD7B,EAAiB,EAAQ,cAAc,QAAQ,SAAU,KACvC,SACd,EAAiB,EACjB,EAAU,IAAM,EAAgB,KAGhC,EADW,OAAZ,EACW,EAAU,SAGV,GAGP,EAAQ,GAAG,UAAY,EAAQ,GAAG,QAAQ,MAAM,KAAmB,EAAQ,SAAS,KAAK,oBAG5F,IAFA,EAAU,EAAQ,UAEC,GAAG,SAAW,IAAI,cAEzC,GAAG,IAAY,EAAe,CAE1B,EAAS,EAAQ,WACjB,IAAI,GAAW,EACf,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IAC1B,EAAW,GAAY,EAAO,GAAG,QAAQ,MAAM,GAEhD,GACC,EAAQ,MAAM,GACd,EAAO,EAAQ,OACf,EAAQ,SACR,EAAU,IAEV,EAAe,OAAO,EAAQ,GAAG,YACjC,EAAQ,MAAM,GACd,EAAQ,SACR,EAAU,EAElB,MAAM,GAAG,EAAQ,SAAS,GAAG,QAAQ,gBAAkB,GAClD,EAAQ,SAAS,SAAS,WAczB,GAAG,EAAQ,MAAM,GAEnB,EAAQ,KAAK,OACZ,CAQD,IALqB,KADrB,EAAS,EAAY,2BACX,SAEN,EAAS,CAAC,EAAQ,KAGlB,EAAI,EAAG,EAAI,EAAO,OAAQ,IAC1B,GAA0B,IAAvB,EAAO,GAAG,WAAmB,EAAO,GAAG,QAAQ,MAAM,GACpD,KAA6B,IAAvB,EAAO,GAAG,WAAmB,EAAO,GAAG,UAAY,EAAO,GAAG,QAAQ,MAAM,IAC7E,EAAO,GAAK,EAAO,GAAG,WAclC,IATA,EAAS,EAAO,QAAO,SAAS,EAAO,EAAO,GAC1C,OAAO,EAAK,QAAQ,KAAW,CACnC,KAEW,OAAO,IACd,EAAS,EAAO,QAAO,SAAU,EAAO,EAAO,GAC3C,QAA0C,QAAjC,EAAM,SAAS,eAA2B,iBAAiB,KAAK,EAAM,IACnF,KAED,QAAQ,QAAQ,EAAO,IAAI,SAAS,YACnC,EAAU,QAAQ,QAAQ,IAClB,GAAG,UAAY,EAAO,GAAG,UACjC,EAAO,GAAG,UAAY,EAAQ,GAAG,eAC/B,GAAsB,eAAnB,EAAgC,CAGrC,IADA,EAAO,GACH,EAAI,EAAG,EAAI,EAAO,OAAQ,IAC1B,GAAQ,EAAO,GAAG,UAKtB,KAHA,EAAU,QAAQ,QAAQ,IAClB,GAAG,UAAY,EACvB,EAAO,GAAG,WAAW,aAAa,EAAQ,GAAG,EAAO,IAChD,EAAI,EAAO,OAAS,EAAG,GAAK,EAAG,IAE3B,EAAO,GAAG,YAAY,EAAO,GAAG,WAAW,YAAY,EAAO,GAE1E,MACA,GAAuB,QAAnB,GAA4B,EAAY,mBAAoB,CAI5D,IADA,EAAO,GACF,EAAI,EAAG,EAAI,EAAO,OAAQ,IAC3B,GAAQ,EAAO,GAAG,UAKtB,KAHA,EAAU,QAAQ,QAAQ,IAClB,GAAG,UAAY,EACvB,EAAO,GAAG,WAAW,aAAa,EAAQ,GAAI,EAAO,IAChD,EAAI,EAAO,OAAS,EAAG,GAAK,EAAG,IAE5B,EAAO,GAAG,YAAY,EAAO,GAAG,WAAW,YAAY,EAAO,GAE1E,MAII,IAAK,EAAI,EAAG,EAAI,EAAO,OAAQ,IAAK,CAChC,IAAI,EAAW,EAAoB,EAAO,GAAI,GAC1C,EAAO,KAAO,EAAQ,KACtB,EAAU,QAAQ,QAAQ,GAElC,CAER,KArF0C,CAEtC,IAAI,EAAe,EAAQ,SACvB,EAAW,EAAa,WAC5B,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,IAEzB,EAAa,SAAS,SAAS,YAAuC,IAAzB,EAAS,GAAG,YACxD,EAAiB,QAAQ,QAAQ,IAAM,EAAgB,MACxC,GAAG,UAAY,EAAS,GAAG,UAC1C,EAAS,GAAK,EAAe,IAEjC,EAAa,SAAS,GAAG,aAAa,EAAS,GAAI,EAAa,IAEpE,EAAa,QACjB,CA4EA,OAJA,EAAY,yBAAyB,EAAQ,SAG7C,EAAQ,GAAG,OAEf,CAAM,GAA6B,eAA1B,EAAQ,cAA+B,CAE5C,GAAgB,MAAZ,EAGA,YADA,EAAY,sBAAsB,KAAO,GAG7C,IAAI,EAAW,YAAc,EAAU,cAC9B,EAAqB,EAAE,OAAS,EAAqB,EAAE,OAAS,IACjE,KACJ,EAAS,OAEb,GADiB,EAAY,eACf,UAGV,EAAY,WAAW,EAAW,EAAU,EAAQ,QAClD,GAAG,EAAM,eAAe,WAAW,GAAG,sBAAsB,CAC9D,IAAI,EAAO,QAAQ,QAAQ,EAAW,GAAQ,GAC9C,EAAM,eAAe,WAAW,GAAG,iBAAiB,EACxD,CACA,MACJ,CAAM,GAA6B,eAA1B,EAAQ,cAGb,YADA,EAAY,WAAW,EAAS,EAEpC,CACA,IACI,EAAU,GAAG,YAAY,EAAS,EAAQ,EAC9C,CAAC,MAAM,GAAG,CAhQmB,CAiQjC,CACJ,CACJ,IAAI,QAAQ,cAAe,CAAC,YAAa,QAAS,OAElD,SAAS,EAAW,EAAO,GAEvB,IACI,EADA,EAAY,EAAU,GAEtB,EAAc,SAAU,EAAS,GAMjC,OAAI,EAAQ,SAAW,EAAQ,QAAQ,MAAM,UAAuB,IAAX,IAAiB,EAAQ,gBACvE,CACH,QAAS,EAAQ,WACjB,OAAQ,GAGL,CACH,QAAS,EACT,OAAQ,EAGpB,EACI,EAAM,CACN,aAAc,WACV,IAAI,EACJ,IAEI,EAAQ,EAAM,eAAe,WAAW,EAC5C,CAAE,MAAM,GAEJ,MACJ,CACA,IAAI,EAAY,EAAM,wBAClB,EAAY,CACZ,MAAO,EAAY,EAAM,eAAgB,EAAM,aAC/C,IAAK,EAAY,EAAM,aAAc,EAAM,WAC3C,UAAW,EAAM,WA+CrB,OAzB2B,IAAvB,EAAU,WAC0C,QAAhD,EAAU,WAAW,SAAS,eAC9B,iBAAiB,KAAK,EAAU,WAAW,MAI3C,EAAY,EAAU,aAGW,QAArC,EAAU,SAAS,eACnB,iBAAiB,KAAK,EAAU,KAGhC,EAAU,MAAM,QAAU,EAAU,WAAW,EAAU,MAAM,QAC/D,EAAU,IAAI,QAAU,EAAU,WAAW,EAAU,IAAI,QAC3D,EAAU,UAAY,GAElB,EAAU,aAAe,EAAU,MAAM,SACzC,EAAU,aAAe,EAAU,IAAI,QACvC,EAAU,UAAY,EAAU,WAEhC,EAAU,UAAY,EAIvB,CACX,EAGA,mBAAoB,SAAS,GACzB,IAAI,EAAQ,EAAM,eAAe,WAAW,GAC5C,GAAI,GAAS,EAAM,UAAW,CAC1B,IAAI,EAAS,EAAI,gBAAgB,GACjC,IAAK,EAAO,UAAW,OACvB,IAOI,EA2BA,EAlCA,EAAQ,EAAM,eACd,EAAsB,EAAO,WAAU,SAAS,EAAS,GACzD,OAAI,EAAQ,OAAO,IAEE,IADP,EAAQ,QAAQ,QAAQ,EAE1C,IAoBA,GAhBA,EAAO,SAAQ,SAAU,EAAG,GAExB,EAAE,QAAQ,SAAQ,SAAU,EAAI,GAEhC,GACJ,IACI,EAAoB,EAAI,EAAO,SAG/B,EAAkB,EAAO,EAAoB,GAAG,MAOhD,GAAmB,EAAgB,aAC/B,uBAAuB,KAAK,EAAgB,aAK5C,OAUR,GANI,EAAsB,IAGtB,EAAiB,EAAO,EAAoB,GAAG,MAGzB,IAAtB,EAAM,aAAqB,GAEvB,uBAAuB,KAAK,EAAe,aAK3C,YADA,EAAI,yBAAyB,EAIzC,CAEJ,EAEA,oBAAqB,SAAS,GA8D9B,EACA,gBAAiB,SAAS,GACtB,IAAI,EAAS,EAAM,wBAAwB,WAC3C,IAAK,EACD,OAAO,EAAM,wBAAwB,WAEzC,IAAI,EAAQ,MAAM,UAAU,MAAM,KAAK,EAAO,YAC1C,EAAsB,EAAM,QAAQ,EAAM,gBA2B9C,OAzBI,EAAoB,EAAI,EAAM,QAAU,EAAsB,GAI1D,EAAO,aACP,EAAS,EAAO,YAIxB,EAAQ,GACR,SAAS,EAAS,GACV,EAAK,KAAK,WAAW,OACJ,MAAM,UAAU,MAAM,KAAK,EAAK,KAAK,YAC3C,SAAQ,SAAS,GACxB,IAAI,EAAK,EAAK,QAAQ,QAClB,EAAG,OAAO,GAAG,KAAK,EAAK,MACvB,EAAG,KAAK,EAAK,MAEjB,EAAS,CAAC,QAAS,EAAI,KAAM,GACjC,IAEA,EAAM,KAAK,CAAC,QAAS,EAAK,QAAS,KAAM,EAAK,MAEtD,CACA,CAAS,CAAC,QAAS,CAAC,GAAS,KAAM,IAC5B,CACX,EACA,wBAAyB,WACrB,IAAI,EAAQ,EAAM,eAAe,WAAW,GACxC,EAAY,EAAM,wBAQtB,OAHA,EAAmC,IAAvB,EAAU,SAAiB,EAAU,WAAa,EAGvD,EAAM,SAAS,CAAC,IAAI,SAAS,GAChC,OAAO,EAAK,aAAe,CAC/B,GACJ,EAEA,uBAAwB,WACpB,IAAI,EAAQ,EAAM,eAAe,WAAW,GACxC,EAAY,EAAM,wBAKtB,EAAmC,IAAvB,EAAU,SAAiB,EAAU,WAAa,EAG9D,IAAI,EAAgB,EAAM,SAAS,CAAC,IAAI,SAAS,GAC7C,OAAO,EAAK,aAAe,CAC/B,IACI,EAAY,EAAU,UAM1B,IAJA,EAAY,EAAU,QAAQ,qDAAsD,OAIlE,EAAM,WAEoB,QAArC,EAAU,SAAS,gBAA4B,iBAAiB,KAAK,EAAU,KACpF,CAEE,IADA,IAAI,EAAM,GACF,EAAI,EAAc,OAAQ,IAAK,EAAI,QAAQ,EAAc,MACjE,EAAgB,GACF,KAAK,EAEvB,CACA,OAAO,CACX,EAEA,oBAAqB,WAEjB,OADQ,EAAI,eAED,EAAI,eAAe,eAE1B,CAER,EACA,aAAc,SAAS,EAAS,EAAO,EAAO,GAC1C,IAAI,EAAQ,EAAM,cAElB,EAAM,SAAS,EAAS,GACxB,EAAM,OAAO,EAAO,GAEpB,EAAM,eAAe,eAAe,EACxC,EACA,0BAA2B,SAAU,GACjC,IAAI,EAAQ,EAAM,cAElB,EAAM,WAAW,GACjB,EAAM,UAAS,GAEf,EAAM,eAAe,eAAe,EACxC,EACA,yBAA0B,SAAU,GAChC,IAAI,EAAQ,EAAM,cAElB,EAAM,WAAW,GACjB,EAAM,UAAS,GAEf,EAAM,eAAe,eAAe,EACxC,EACA,2BAA4B,SAAU,GAClC,IAAI,EAAQ,EAAM,cAElB,EAAM,mBAAmB,GACzB,EAAM,UAAS,GAEf,EAAM,eAAe,eAAe,EACxC,EACA,yBAA0B,SAAU,GAChC,IAAI,EAAQ,EAAM,cAElB,EAAM,mBAAmB,GACzB,EAAM,UAAS,GACZ,EAAG,YAAc,EAAG,WAAW,EAAG,WAAW,OAAS,IAA2D,OAArD,EAAG,WAAW,EAAG,WAAW,OAAS,GAAG,WACnG,EAAM,YAAc,EAAM,UAAY,EAAM,YAAc,GAE9D,EAAM,eAAe,eAAe,EACxC,EACA,iBAAkB,SAAS,GACvB,EAAc,CAClB,EACA,iBAAkB,WACd,OAAO,CACX,EAGA,WAAY,SAAS,EAAM,GACvB,IAAI,EAAQ,EAAc,EAAS,EAAO,EAAG,EAAU,EACnD,EAAU,QAAQ,QAAQ,QAAU,EAAO,UAC3C,EAAQ,EAAM,eAAe,WAAW,GACxC,EAAO,EAAU,yBACjB,EAAW,EAAQ,GAAG,WACtB,GAAW,EAEf,GAAG,EAAS,OAAS,EAAE,CAKnB,IADA,EAAQ,GACJ,EAAU,EAAG,EAAU,EAAS,OAAQ,IAAU,CAClD,IAAI,EAAS,EAAS,GACgB,MAAlC,EAAO,SAAS,eACY,KAA5B,EAAO,UAAU,SAkBrB,EAAW,IAAa,EAAc,KAAK,EAAO,UAClD,EAAM,KAAK,GACf,CACA,IAAI,IAAI,EAAK,EAAG,EAAK,EAAM,OAAQ,IAC/B,EAAW,EAAK,YAAY,EAAM,KAEjC,GACD,EAAM,WACN,kBAAkB,KAAK,EAAM,eAAe,YAC5C,EAAM,WAAW,EAAM,eAE/B,MACI,GAAW,EAEX,EAAW,EAAO,EAAU,eAAe,GAI/C,GAAG,EACC,EAAM,sBAEN,GAAG,EAAM,WAAa,EAAM,iBAAmB,EAC3C,GAAG,EAAM,eAAe,WAAa,EAAM,eAAe,UAAU,MAAM,cAEtE,EAAS,EAAM,eACU,IAAtB,EAAM,aAEL,EAAM,cAAc,GACpB,EAAM,YAAY,KAGlB,EAAM,eAAe,GACrB,EAAM,aAAa,QAEtB,CAED,GAAqC,IAAlC,EAAM,eAAe,UAAkB,EAAM,eAAe,aAAe,EAO1E,IALA,GADA,EAAS,EAAM,eAAe,YACR,YAEtB,EAAM,WAAW,EAAO,WAAY,EAAQ,EAAc,EAAM,eAAgB,EAAM,cAG/E,EAAc,KAAK,EAAO,WAAU,CACvC,QAAQ,QAAQ,GAAQ,MAAM,GAE9B,IAAI,EAAoB,EACxB,GAFA,EAAS,EAAO,YAEM,YAEtB,EAAM,WAAW,EAAO,WAAY,EAAQ,EAAc,EAC9D,MAGA,GADA,EAAS,EAAM,gBACO,YACtB,EAAM,WAAW,EAAO,WAAY,EAAQ,OAAc,OAAW,EAAW,EAAM,aAe1F,GAZA,QAAQ,QAAQ,GAAQ,MAAM,GAG9B,EAAM,cAAc,GACpB,EAAM,YAAY,GAEf,kBAAkB,KAAK,EAAO,UAAU,UACvC,EAAM,eAAe,GACrB,EAAM,aAAa,GACnB,QAAQ,QAAQ,GAAQ,UAEzB,kBAAkB,KAAK,EAAa,UAAU,SAAS,QAAQ,QAAQ,GAAc,SACnD,OAAlC,EAAO,SAAS,cAAuB,CAEtC,IADA,EAAY,EAAU,yBAClB,EAAI,EAAG,EAAI,EAAK,WAAW,OAAQ,IACnC,EAAU,QAAQ,QAAQ,QAC1B,EAAM,mBAAmB,EAAK,WAAW,GAAI,EAAQ,IACrD,EAAM,uBAAuB,EAAK,WAAW,GAAI,EAAQ,IACzD,EAAU,YAAY,EAAQ,IAElC,EAAO,EACJ,IAEC,GADA,EAAW,EAAK,WAAW,EAAK,WAAW,OAAS,IAChC,WAAW,EAAS,WAAW,OAAS,GAEpE,CACJ,MAEA,EAAM,iBAId,EAAM,WAAW,GACd,GACC,EAAI,yBAAyB,EAErC,GA2BJ,OAAO,CACX,IAAI,QAAQ,SAAS,WACjB,IAAI,EAAQ,CAER,eAAgB,SAAS,EAAS,GAC9B,IAAI,EAAoB,GACpB,EAAa,EAAQ,WAOzB,OANG,EAAW,QACV,QAAQ,QAAQ,GAAY,SAAS,GACjC,EAAoB,EAAkB,OAAO,EAAM,eAAe,QAAQ,QAAQ,GAAQ,GAC9F,SAE2B,IAA5B,EAAQ,KAAK,IAA0B,EAAkB,KAAK,GAC1D,CACX,EAEA,mBAAoB,SAAS,EAAQ,GAGjC,IADA,EAAO,UAAY,GACb,EAAO,WAAW,OAAS,GAAG,EAAO,YAAY,EAAO,WAAW,IACzE,OAAO,CACX,EAEA,WAAY,SAAS,EAAO,EAAS,EAAS,EAAW,EAAe,GACpE,IAAI,GAAa,MAAM,GAAa,MAAM,IAAI,MAAM,uDAKpD,IAJA,IAAI,EAAa,SAAS,yBACtB,EAAW,SAAS,yBACpB,EAAQ,EAEN,EAAM,OAAS,IAAM,MAAM,IAAe,IAAe,IAAU,EAAM,KAAO,GAClF,EAAW,YAAY,EAAM,IAC7B,IAOJ,KAJI,MAAM,IAAkB,GAAiB,GAAK,EAAM,KACpD,EAAW,YAAY,SAAS,eAAe,EAAM,GAAG,UAAU,UAAU,EAAG,KAC/E,EAAM,GAAG,UAAY,EAAM,GAAG,UAAU,UAAU,IAEhD,EAAM,OAAS,GAAG,EAAS,YAAY,EAAM,IAEnD,EAAM,mBAAmB,EAAY,GACrC,EAAM,mBAAmB,EAAU,EACvC,EAEA,uBAAwB,SAAS,EAAQ,GACrC,IAAI,IAAI,EAAI,EAAG,EAAI,EAAO,WAAW,OAAQ,IAAK,EAAO,aAAa,EAAO,WAAW,GAAG,KAAM,EAAO,WAAW,GAAG,OACtH,OAAO,CACX,GAEJ,OAAO,CACX,IAEA,QAAQ,OAAO,yBAA0B,IACxC,UAAU,aAAa,WACpB,MAAO,CACH,SAAU,IACV,QAAS,UACT,KAAM,SAAS,EAAO,EAAM,EAAO,GAC/B,IAAI,EAAM,SAAS,EAAM,MAAM,EAAM,YACrC,GAAI,MAAM,GACN,KAAK,8BAET,EAAM,SAAS,aAAa,SAAS,GAEjC,GADA,EAAM,SAAS,GACX,MAAM,GACN,KAAK,8BAEL,EAAK,QACL,EAAK,WAEb,IACA,EAAK,YAAY,UAAY,SAAS,GAClC,IAAI,EAAS,QAAQ,QAAQ,UAE7B,OADA,EAAO,KAAK,GACL,EAAO,OAAO,QAAU,CACnC,CACJ,EAER,IAAG,UAAU,aAAa,WACtB,MAAO,CACH,SAAU,IACV,QAAS,UACT,KAAM,SAAS,EAAO,EAAM,EAAO,GAC/B,IAAI,EAAM,SAAS,EAAM,MAAM,EAAM,YACrC,GAAI,MAAM,GACN,KAAK,8BAET,EAAM,SAAS,aAAa,SAAS,GAEjC,GADA,EAAM,SAAS,GACX,MAAM,GACN,KAAK,8BAEL,EAAK,QACL,EAAK,WAEb,IACA,EAAK,YAAY,UAAY,SAAS,GAClC,IAAI,EAAS,QAAQ,QAAQ,UAE7B,OADA,EAAO,KAAK,IACJ,EAAO,OAAO,QAAU,EAAO,OAAO,QAAU,CAC5D,CACJ,EAER,IACA,QAAQ,OAAO,qBAAsB,CAAC,wBAAyB,oBAC9D,QAAQ,eAAgB,CAAC,WACtB,OAAO,SAAS,GAKZ,OAAI,GAOU,KAhmDtB,SAAyB,GAExB,IAAI,EAAM,SAAS,cAAc,OACjC,EAAI,UAAY,EAChB,IAAI,EAAM,EAAI,aAAe,EAAI,WAAa,GAG9C,OAFA,EAAI,QAAQ,IAAU,IAChB,EAAI,MAEX,CAqlDqB,CAAgB,KAKrB,aAAa,KAAK,EAO9B,CACJ,IACC,UAAU,WAAY,CAAC,WACpB,MAAO,CACH,KAAM,SAAS,EAAO,EAAS,GAC3B,EAAQ,KAAK,eAAgB,MAC7B,EAAQ,GAAG,aAAa,SAAS,EAAG,GAKhC,OAHG,GAAW,QAAQ,OAAO,EAAG,GAEhC,EAAE,kBACK,CACX,GACJ,EAER,IACC,UAAU,SAAU,CACb,aAAc,WAAY,YAAa,cAAe,eACtD,cAAe,uBAAwB,yBAA0B,YACjE,eAAgB,SAAU,QAAS,qBACnC,SACI,EAAY,EAAU,EAAW,EAAa,EAC9C,EAAa,EAAsB,EAAwB,EAC3D,EAAc,EAAQ,EAAO,GAGrC,MAAO,CACH,SAAU,EACV,QAAS,CAAC,UAAU,mBACpB,KAAM,SAAS,EAAO,EAAS,EAAO,GAClC,IAUI,EAeA,EAIA,EA7BA,EAAU,EAAW,GACrB,EAAiB,EAAW,IAAM,CAAC,EAEnC,OAAyD,IAApC,EAAQ,KAAK,oBAAoC,EAAQ,KAAK,mBACnF,EAAmB,GAA2D,aAArC,EAAQ,GAAG,QAAQ,eAAqE,UAArC,EAAQ,GAAG,QAAQ,cAC/G,GAAc,EACd,GAAY,EACZ,GAAc,EACd,EAAoB,EAAM,mBAAqB,EAAU,iBACzD,EAAc,EAAM,cAAgB,EAAU,WAS9C,EAAe,qGAMf,EAAoB,gFAmBpB,EAAe,CAEf,CACI,WAAY,UACZ,mBAAoB,GACpB,kBAAmB,CAAC,GACpB,QAAS,IAGb,CACI,WAAY,UACZ,mBArBO,EAsBP,kBAAmB,CAAC,EArBX,GAsBT,QAAS,IAGb,CACI,WAAY,UACZ,mBAAoB,GACpB,kBAAmB,CAAC,GACpB,QAAS,IAGb,CACI,WAAY,SACZ,mBAAoB,GACpB,kBAAmB,GACnB,QAhCW,GAmCf,CACI,WAAY,cACZ,mBAAoB,EACpB,kBAAmB,CA1CV,GA2CT,QAvCW,SA4DQ,IAAxB,EAAM,gBAA6B,EAAM,cAAgB,KAEjC,KAAxB,EAAM,cACL,EAAc,IAGd,OAAqC,IAAtB,GAAmC,GAAqB,GAC5B,OAAtC,EAAM,cAAc,cAAyB,WAAa,IAAM,EAAM,cAAgB,UAAY,EAAM,cAAgB,IACxH,GAAqB,EAClB,IAAM,EAAM,cAAc,cAAgB,MAAQ,EAAM,cAAc,cAAgB,IACtF,IAAM,EAAM,cAAgB,MAAQ,EAAM,cAAgB,SAC5B,IAAtB,GAAmC,GAAqB,GAC7B,OAAtC,EAAM,cAAc,gBAA4C,EAAM,cAA4B,EAAM,eACxG,GAAqB,GACZ,EAAM,cAAc,cAA8B,EAAM,cAAc,gBACtE,EAAM,cAA8B,EAAM,gBAIxD,EAAe,WAAU,EAAe,SAAW,CAAC,GAExD,IA+DI,EA/DA,EAAwB,SAAS,GACjC,GAAI,EAAa,GAAQ,OAAO,EAChC,IAAI,EAAU,QAAQ,QAAQ,QAAU,EAAQ,UAIhD,GAAkC,IAA9B,EAAQ,WAAW,OAGnB,EAAQ,IAAM,EAAM,cAAgB,IAAM,EAAQ,KAAO,EAAM,cAAgB,QAC5E,CACH,IACI,EADA,EAAY,EAAQ,GAAG,WAEvB,GAAqB,EACzB,IAAK,EAAI,EAAG,EAAI,EAAU,UAClB,EAAqB,EAAU,GAAG,SAAS,cAAc,MAAM,IADrC,KAGlC,GAAK,EAKD,IADA,EAAQ,GACJ,EAAI,EAAG,EAAI,EAAU,OAAQ,IAAI,CACjC,IAAI,EAAO,EAAU,GACjB,EAAW,EAAK,SAAS,cAE7B,GAAgB,aAAb,EACC,GAAS,UAAS,EAAK,UAAY,cAChC,GAAgB,UAAb,EAAsB,CAG5B,IAAI,EAAO,EAAK,YACX,EAAK,OAKN,GAAS,IAAM,EAAM,cAAgB,IAAM,EAAO,KAAO,EAAM,cAAgB,IAH/E,GAAS,CAKjB,MAAO,GAAI,EAAS,MAAM,GAQtB,GAAS,EAAK,cARuB,CAErC,IAAI,EAAW,EAAK,WAAa,EAAK,UAEhB,KAAnB,EAAQ,OACP,GAAS,IAAM,EAAM,cAAgB,IAAM,EAAU,KAAO,EAAM,cAAgB,IACjF,GAAS,CAClB,CAIJ,MAhCA,EAAQ,IAAM,EAAM,cAAgB,IAAM,EAAQ,KAAO,EAAM,cAAgB,GAkCvF,CAEA,OAAO,CACX,EAEG,EAAM,UACL,EAAgB,EAAO,EAAM,UAGjC,EAAQ,SAAS,WAIjB,EAAM,gBAAkB,EAAM,IAAM,KAAO,EAAQ,aAAe,CAC9D,OAAQ,GACR,OAAQ,EACR,KAAM,IACN,KAAM,SAAS,GACX,OAAG,MAAQ,QACqB,IAAnB,KAAK,WAAgD,OAAnB,KAAK,WAAuB,IAAU,KAAK,YACvF,KAAK,OAAS,KAAK,OAAO,OAAS,IAClC,KAAK,OAAS,KAAK,OAAO,MAAM,EAAE,KAAK,OAAO,IAElD,KAAK,OAAO,KAAK,GACd,GAAmB,EAAS,OAAO,GACnC,KAAK,OAAO,OAAS,KAAK,MAAM,KAAK,OAAO,QAC/C,KAAK,OAAS,KAAK,OAAO,OAAS,GAP0E,CASjH,EACA,KAAM,WACF,OAAO,KAAK,WAAW,KAAK,OAAO,EACvC,EACA,KAAM,WACF,OAAO,KAAK,WAAW,KAAK,OAAO,EACvC,EACA,WAAY,SAAS,GACjB,KAAG,EAAQ,GAAK,EAAQ,KAAK,OAAO,OAAS,GAI7C,OADA,KAAK,OAAS,EACP,KAAK,SAChB,EACA,QAAS,WACL,OAAO,KAAK,OAAO,KAAK,OAC5B,GAIJ,IAkDI,EAlDA,EAAe,WACf,GAAG,EACC,OAAO,EAAQ,GAAG,UAEtB,GAAG,EACC,OAAO,EAAQ,MAEnB,KAAM,6DACV,EAEI,EAAuB,SAAS,GAIhC,OAFA,EAAM,MAAM,oBAAqB,MACjC,EAAM,kBACC,CACX,EAGI,EAA6B,EAAM,6BAA+B,EAAM,IAAM,KAAO,WAEjF,GAAa,QAAQ,QAAQ,GAAsB,SAAS,GAE5D,EAAQ,KAAK,GACR,IAAI,QAAS,GACb,GAAG,QAAS,EACrB,GACJ,EAEI,EAAgB,SAAS,EAAM,EAAa,GAC5C,EAAc,IAAc,EACzB,MAAO,IAAqD,EAAsB,GAClF,MAAO,IAAuC,EAAO,KACrD,EAAa,IAEc,KAAvB,EAAQ,YAAmB,EAAQ,cAAc,IACjD,GAAkD,KAAnC,EAAQ,aAAa,WAAkB,EAAQ,aAAa,KAAK,MAEnF,IACG,EAAQ,aAAe,IACtB,EAAQ,cAAc,GACnB,GAAa,EAAQ,aAAa,KAAK,KAGlD,EAAQ,SACZ,EAEI,EAAgB,SAAS,GACzB,EAAQ,GAAG,UAAY,CAC3B,EAGI,EAAQ,EAAM,eAAiB,EAAM,IAAM,KAAO,WAElD,IAAI,GAAe,EAAmB,CAClC,IAAI,EAAU,EAAQ,aAAa,OAChC,MAAO,IACN,EAAc,GACd,EAAc,GAAS,GACpB,GAAkB,EAAS,OAAO,GACrC,EAAmB,GAAS,WACxB,EAAQ,GAAG,QACX,EAAY,yBAAyB,EAAQ,GACjD,GAAG,GAEX,CACJ,EAEI,EAAQ,EAAM,eAAiB,EAAM,IAAM,KAAO,WAElD,IAAI,GAAe,EAAmB,CAClC,IAAI,EAAU,EAAQ,aAAa,OAChC,MAAO,IACN,EAAc,GACd,EAAc,GAAS,GAEpB,GAAkB,EAAS,OAAO,GACrC,EAAmB,GAAS,WACxB,EAAQ,GAAG,QACX,EAAY,yBAAyB,EAAQ,GACjD,GAAG,GAEX,CACJ,EAGA,EAAM,gBAAkB,EAAM,IAAM,KAAO,WACnC,GAAa,OAAc,OAAW,GAAW,EACzD,EAIA,IAAI,EAAY,SAAS,GACrB,OAAQ,EAAQ,cAAgB,EAAW,EAAY,EAAQ,GAAc,EAAQ,cAAe,EACxG,EAiBA,GAdG,EAAQ,KAAK,cAAa,EAAQ,YAAY,SAAW,SAAS,EAAY,GAC7E,OAAQ,EAAa,GAAc,EACvC,GAEA,EAAQ,SAAS,KAAK,GACtB,EAAQ,SAAS,QAAQ,GAEzB,EAAQ,YAAY,KAAK,GACzB,EAAQ,YAAY,QAAQ,GAC5B,EAAQ,YAAY,SAAQ,SAAS,GACjC,OAAO,EAAQ,aAAa,KAAK,GAAS,GAC9C,IAGG,EAEC,GADA,EAAM,OAAS,CAAC,EACZ,EA4IC,CAED,IA4UI,EAmGA,EA/aA,GAAmB,EAEnB,EAAe,SAAS,GACzB,IAAI,OAAoB,IAAP,GAAkB,EAAK,MAAM,8BAG7C,GAAG,GAAQ,EAAK,OAAO,OAAO,CAE1B,GAAG,EAAK,MAAM,iCAAmC,EAAK,MAAM,gCAAkC,EAAK,MAAM,8BAA8B,CACnI,IAAI,EAAe,EAAK,MAAM,qDAG9B,GADK,EADD,EACgB,EAAa,GADA,GAEL,QAAQ,yBAA0B,IAAI,QAAQ,kCAAmC,IAC7G,IAAI,EAAM,QAAQ,QAAQ,QAAU,EAAe,UAC/C,EAAY,QAAQ,QAAQ,eAC5B,EAAQ,CACR,QAAS,KACT,WAAY,GACZ,OAAQ,KACR,MAAM,GAEV,EAAM,WAAW,KAAO,WACpB,IAAI,EAAI,KAAK,OACb,GAAI,EAAE,EAAG,OAAO,KAAK,EAAE,EAC3B,EAWA,IAVA,IAAI,EAAa,SAAS,GACtB,EAAM,KAAO,EACb,EAAM,QAAU,QAAQ,QAAQ,EAAO,OAAS,QAChD,EAAM,WAAa,GACnB,EAAM,WAAW,KAAO,WACpB,IAAI,EAAI,KAAK,OACb,GAAI,EAAE,EAAG,OAAO,KAAK,EAAE,EAC3B,EACA,EAAM,eAAiB,IAC3B,EACQ,EAAI,EAAG,GAAK,EAAI,GAAG,WAAW,OAAQ,IAC1C,GAAI,EAAI,GAAG,WAAW,IAAwC,UAAlC,EAAI,GAAG,WAAW,GAAG,SAAjD,CAGI,IAAI,EAAU,EAAI,GAAG,WAAW,GAAG,QAAQ,cAC3C,GAAe,MAAZ,GACa,OAAZ,GACY,OAAZ,GACY,OAAZ,GACY,OAAZ,GACY,OAAZ,GACY,OAAZ,GACY,OAAZ,GACY,UAAZ,EARJ,CAYJ,IAAI,EAAK,QAAQ,QAAQ,EAAI,GAAG,WAAW,IACvC,GAAc,EAAG,KAAK,UAAY,IAAI,MAAM,+DAEhD,GAAG,EAAW,CACV,GAAG,EAAG,GAAG,WAAW,OAAS,GAAK,EAAG,GAAG,WAAW,GAAG,WAAW,OAAS,EACtE,SAEJ,IAAI,EAAuC,WAAhC,EAAW,GAAG,eAA+D,WAAhC,EAAW,GAAG,iBAAgC,qCAAqC,KAAK,EAAG,GAAG,WAAW,GAAG,YAAc,qCAAqC,KAAK,EAAG,GAAG,WAAW,GAAG,WAAW,GAAG,YAC1P,GAAgB,EAAG,KAAK,UAAY,IAAI,MAAM,6BAC9C,EAAS,WAAW,EAAe,EAAa,GAAG,GACnD,GAAe,EAAG,KAAK,UAAY,IAAI,MAAM,oDAKjD,GAFG,GAAe,EAAY,KAAI,EAAS,SAAS,EAAY,KAE3D,KAAiB,EAAM,gBAAkB,EAAY,KAAO,EAAM,eAAe,MAAS,EAAW,IAAsC,UAAhC,EAAW,GAAG,eAA0D,OAA5B,EAAM,WAAW,QAAqB,EAAM,OAAS,GAAQ,EAAM,WAAW,SAAW,EACjP,EAAW,GACX,EAAU,OAAO,EAAM,cACpB,GAA+B,MAA3B,EAAM,WAAW,QAAkB,EAAM,WAAW,OAAS,EACpE,EAAM,QAAU,QAAQ,QAAQ,EAAO,OAAS,QAChD,EAAM,OAAO,OAAO,EAAM,cACvB,GAA+B,MAA3B,EAAM,WAAW,QAAkB,EAAM,WAAW,OAAS,EAAO,CAC3E,KAAiC,MAA3B,EAAM,WAAW,QAAkB,EAAM,WAAW,OAAS,GAC/D,GAAuD,OAApD,EAAM,QAAQ,SAAS,GAAG,QAAQ,cAArC,CAGM,IAAG,SAAS,KAAK,EAAM,QAAQ,SAAS,GAAG,QAAQ,eAGrD,MAFA,EAAM,QAAU,EAAM,QAAQ,SAIlC,EAAM,WAAW,KADjB,MANI,EAAM,QAAU,EAAM,QAAQ,SAStC,EAAM,KAAkD,OAA3C,EAAM,QAAQ,GAAG,QAAQ,cAClC,IAAS,EAAM,OACf,EAAW,GACX,EAAU,OAAO,EAAM,SAE/B,CAEA,EAAM,eAAiB,EACpB,IAAW,EAAM,WAAW,QAAQ,EAAM,WAAW,KAAK,GAC7D,EAAM,OAAS,QAAQ,QAAQ,QAC/B,EAAM,QAAQ,OAAO,EAAM,QAC3B,EAAM,OAAO,KAAK,EAAG,OAAO,QAAQ,oEAAqE,KACzG,EAAG,QACP,MACI,GAAW,GACX,EAAU,OAAO,EAlDjB,CACJ,CAoDJ,IAAI,EAAiB,SAAS,GAE1B,IAAI,IAAI,GADR,EAAO,QAAQ,QAAQ,IACL,GAAG,WAAW,OAAS,EAAG,GAAM,EAAG,IAAM,EAAK,MAAM,EAAK,GAAG,WAAW,IACzF,EAAK,QACT,EAEA,QAAQ,QAAQ,EAAU,KAAK,SAAS,SAAS,GAC7C,EAAK,gBAAgB,QAClB,EAAK,WAAW,QAAU,GAAG,EAAe,EACnD,IACA,QAAQ,QAAQ,EAAU,KAAK,QAAS,GAExC,EAAO,EAAU,OACd,IACC,EAAO,EAAU,QAAU,EAAI,QAGnC,EAAO,EAAK,QAAQ,MAAO,IAC/B,KAAK,CAGD,IADA,EAAO,EAAK,QAAQ,sBAAuB,KACnC,MAAM,4BAEV,GAAG,EAAK,MAAM,gCAAgC,CAC1C,IAAI,EAAM,QAAQ,QAAQ,QAAU,EAAO,UAC3C,EAAI,KAAK,YAAY,SACrB,IAAI,IAAI,EAAK,EAAG,EAAK,MAAM,OAAQ,IAAK,CAEpC,IADA,IAAI,EAAU,MAAM,GAAI,GAAG,WAAW,WAC9B,EAAK,EAAG,EAAK,MAAM,GAAI,GAAG,WAAW,OAAQ,IACjD,EAAQ,WAAW,aAAa,MAAM,GAAI,GAAG,WAAW,GAAK,GAEjE,EAAQ,WAAW,YAAY,EACnC,CACA,EAAO,EAAI,OAAO,QAAQ,yCAA0C,GACxE,OACK,EAAK,MAAM,YAIX,EAAK,MAAM,mFACZ,EAAO,EAAK,QAAQ,sBAAuB,MAInD,EAAO,EAAK,QAAQ,iDAAkD,IAAI,QAAQ,2DAA4D,SAClJ,CAEI,eAAe,KAAK,KAAkE,IAAzD,0CAA0C,KAAK,KAE5E,EAAO,EAAK,QAAQ,8BAA+B,gBAIvD,EAAO,EAAK,QAAQ,kBAAkB,SAAU,GAE5C,IADA,IAAI,EAAS,GACJ,EAAI,EAAG,EAAI,EAAM,OAAQ,IAC9B,GAAU,SAEd,OAAO,CACX,IAAG,QAAQ,cAAe,UAAU,QAAQ,MAAO,4BAEhD,IAAe,EAAO,EAAc,EAAO,CAAC,MAAO,KAAU,GAGhE,EAAO,EAAK,QAAQ,uFAAwF,8BAE5G,EAAO,EAAW,EAAM,GAAI,GAG5B,EAAY,WAAW,EAAM,EAAQ,IACrC,GAAS,WACL,EAAQ,cAAc,KACtB,GAAmB,EACnB,EAAQ,YAAY,mBACxB,GAAG,EACP,MACI,GAAmB,EACnB,EAAQ,YAAY,mBAE5B,EAgSA,GA9RA,EAAQ,GAAG,QAAS,EAAM,OAAO,MAAQ,SAAS,EAAG,GAGjD,GADG,GAAW,QAAQ,OAAO,EAAG,GAC7B,GAAe,EAGd,OAFA,EAAE,kBACF,EAAE,kBACK,EAMX,IAAI,EAFJ,GAAmB,EACnB,EAAQ,SAAS,oBAEjB,IAAI,GAAiB,EAAE,eAAiB,GAAG,cAE3C,IAAM,GAAiB,OAAO,eAAiB,OAAO,cAAc,QAKhE,OAJA,EAAgB,OAAO,cAAc,QAAQ,QAC7C,EAAa,GACb,EAAE,kBACF,EAAE,kBACK,EAEX,GAAI,GAAiB,EAAc,SAAW,EAAc,MAAM,OAAS,EAAG,CAE1E,IADA,IAAI,EAAS,GACL,EAAK,EAAG,EAAK,EAAc,MAAM,OAAQ,IAC7C,GAAU,IAAM,EAAc,MAAM,GAWxC,MARI,cAAc,KAAK,GACnB,EAAgB,EAAc,QAAQ,aAC/B,eAAe,KAAK,KAC3B,EAAgB,EAAc,QAAQ,eAE1C,EAAa,GACb,EAAE,kBACF,EAAE,kBACK,CACX,CACI,IAAI,EAAkB,EAAM,gBACxB,EAAW,QAAQ,QAAQ,8DAC/B,EAAU,KAAK,QAAQ,OAAO,GAC9B,EAAS,GAAG,QACZ,GAAS,WAEL,EAAM,iBAAiB,GACvB,EAAa,EAAS,GAAG,WACzB,EAAQ,GAAG,QACX,EAAS,QACb,GAAG,EAEX,GACA,EAAQ,GAAG,MAAO,EAAM,OAAO,IAAM,SAAS,GAEtC,EAGC,EAAE,iBAHU,GAAS,WACtB,EAAQ,cAAc,IAC1B,GAAG,EAEP,GAEA,EAAQ,GAAG,UAAW,EAAM,OAAO,QAAU,SAAS,EAAO,GASzD,IAAI,EA8BJ,GArCG,GAAW,QAAQ,OAAO,EAAO,GAzqB3B,KA0qBL,EAAM,QACN,EAAY,kBAAiB,GAE7B,EAAY,kBAAiB,GAEjC,EAAM,WApoBlB,SAAkB,GACd,IAAI,EAaJ,OAZA,EAAa,SAAQ,SAAU,GAC3B,GAAI,EAAI,UAAY,EAAM,QAAS,CAC/B,IAAI,GAAgB,EAAM,QApDtB,EAoD2C,IAC1C,EAAM,QAtDP,EAsD4B,IAC3B,EAAM,SApDN,EAoD6B,IAC7B,EAAM,OAtDR,EAsD2B,GAC9B,GAAI,EAAI,mBAAqB,EAAc,OACvC,EAAI,kBAAkB,OAAM,SAAU,GAAY,OAAO,EAAe,CAAU,MAClF,EAAa,EAAI,WAEzB,CACJ,IACO,CACX,CAqnB+B,CAAS,GAG5B,EAAU,YAAY,SAAQ,SAAU,GAChC,EAAM,aAAe,EAAQ,iBAG7B,EAAM,gBAAa,GAEnB,EAAQ,WAAW,KACnB,EAAiB,EAAQ,gBAEG,YAA3B,EAAQ,gBAA6D,YAA3B,EAAQ,gBAE9C,EAAQ,mBACT,EAAM,gBAGlB,SAE8B,IAAnB,IACP,EAAM,WAAa,QAGU,IAArB,EAAM,YACW,YAArB,EAAM,YAAiD,YAArB,EAAM,aAE5C,EAAM,iBACN,EAAmB,eAAe,EAAO,MAGzC,IACuB,YAAnB,EAAM,aACN,IACA,EAAM,kBAEa,YAAnB,EAAM,aACN,IACA,EAAM,kBAttBL,KAytBF,EAAM,SAA+B,EAAM,UAAa,EAAM,SAAY,EAAM,SAAY,EAAM,SACrG,CACI,IAQI,EACA,EAAY,EAAY,sBAE5B,IAAI,EAAU,SAAS,MAAM,GAAgB,OAC7C,IAAI,EAAO,QAAQ,QAAQ,GAI3B,GAhBe,SAAS,EAAG,GACvB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAE,OAAQ,IAC1B,GAAI,EAAE,KAAO,EACT,OAAO,EAGf,OAAO,CACX,CASI,CADsB,CAAC,aAAc,KAAM,MACb,EAAU,WAAW,QAAQ,eAAgB,CAC3E,GAAI,eAAe,KAAK,EAAU,UAAU,UAAY,EAAU,YAAa,CAG3E,IAAI,GADJ,EAAa,QAAQ,QAAQ,IACJ,SACzB,EAAQ,MAAM,GACd,EAAW,SACuB,IAA9B,EAAQ,WAAW,QAAc,EAAQ,SAC7C,EAAY,2BAA2B,EAAK,IAC5C,EAAM,gBACV,CACI,+BAA+B,KAAK,EAAU,UAAU,WACxD,EAAa,QAAQ,QAAQ,IAClB,MAAM,GACjB,EAAW,SACX,EAAY,2BAA2B,EAAK,IAC5C,EAAM,iBAEd,CACJ,CAER,GAEA,EAAQ,GAAG,QAAS,EAAM,OAAO,MAAQ,SAAS,EAAO,GAKrD,GAHG,GAAW,QAAQ,OAAO,EAAO,GACpC,EAAY,kBAAiB,GAnwBtB,IAqwBH,EAAM,SAcV,GAlxBc,KA0wBV,EAAM,SAAoC,EAAM,UAChD,EAAY,mBAAmB,GA1wBpB,KA6wBX,EAAM,SAAqC,EAAM,UACjD,EAAY,oBAAoB,GAEjC,GAAmB,EAAS,OAAO,IAClC,IAAgB,EAAa,KAAK,EAAM,SAExC,GAvxBK,KAuxBD,EAAM,UAA+B,EAAM,SAAW,EAAM,SAAW,EAAM,aAE1E,CAEH,GAAmB,KAAhB,GAAsC,aAAhB,GA3xBxB,KA2xBsD,EAAM,UAA+B,EAAM,UAAY,EAAM,UAAY,EAAM,OAAO,CAEzI,IADA,IAAI,EAAY,EAAY,uBACrB,EAAU,SAAS,MAAM,IAAkB,IAAc,EAAQ,IACpE,EAAY,EAAU,WAE1B,GAAI,EAAM,SAeH,CAEH,IAAI,EAAU,EAAU,QAAQ,cAOhC,IAAI,IAAY,EAAM,eACN,OAAZ,GACY,QAAZ,GACY,QAAZ,KACC,aAAa,KAAK,EAAU,UAAU,QAAS,CAChD,IAAI,EAAK,EAAU,gBAGf,IACA,EAAG,UAAY,EAAG,UAAY,WAC9B,QAAQ,QAAQ,GAAW,SAC3B,EAAY,yBAAyB,GAE7C,CACJ,MAlCI,GAAG,EAAU,QAAQ,gBACjB,EAAM,eAC+B,OAArC,EAAU,SAAS,gBACa,KAA/B,EAAU,UAAU,QAAgD,SAA/B,EAAU,UAAU,QAC5D,CAGE,IAAI,EAAO,QAAQ,QAAQ,GAC3B,QAAQ,QAAQ,GAAW,YAAY,GACvC,EAAY,2BAA2B,EAAK,GAChD,CAyBR,CACA,IAAI,EAAM,IACS,KAAhB,GAAsC,KAAf,EAAI,QAAgC,SAAf,EAAI,OAGlB,MAAxB,EAAI,UAAU,EAAG,IAAc,EAAM,eAF1C,EAAc,GACd,EAAY,2BAA2B,EAAQ,WAAW,KAU9D,IAAI,EAAc,IAAa,EAAM,SAAW,EAAkB,KAAK,EAAM,SAC1E,GAAe,EAAS,OAAO,GAClC,EAAgB,GAAS,WACrB,EAAc,EAAK,GAAa,EACpC,GAAG,EAAe,SAAS,UAAY,KACnC,IAAa,EAAoB,GAAS,WAAY,EAAQ,aAAa,KAAK,EAAM,GAAG,MAC7F,EAAW,EAAM,OACrB,OApFiB,EAAY,eACf,MAAM,UAAY,EAAQ,IAAM,EAAQ,WAAW,QAAQ,EAAY,2BAA2B,EAAQ,WAAW,GAqF3I,GAOA,EAAQ,GAAG,SAAS,WACZ,MAAmB,EAAQ,aAKxB,GAAe,EAAS,OAAO,GAElC,EAAgB,GAAS,WACrB,IAAI,EAAkB,EAAM,gBACxB,EAAO,IACP,IAAS,EAAQ,YAIjB,EAAc,GAAM,GAIU,IAA9B,EAAQ,WAAW,QACnB,EAAM,iBAAiB,EAE/B,GAAG,KAEX,IAEA,EAAQ,GAAG,OAAQ,EAAM,OAAO,KAAO,WACnC,GAAY,EAER,GAGA,GAAc,EACd,EAAQ,WAHR,OAAc,OAAW,GAAW,EAK5C,GAGG,EAAM,cAAgB,EAAoB,QAA2B,IAAtB,GAAiC,CAC/E,IAAI,EACJ,IAAG,EAAM,GACJ,KAAK,uEADG,EAAO,EAAW,IAAM,EAAM,GAAK,2BAA4B,aAAe,EAAM,YAAc,KAG/G,EAAM,IAAI,YAAY,WAClB,EAAc,EAClB,GACJ,CAEA,EAAQ,GAAG,QAAS,EAAM,OAAO,MAAQ,WACrC,GAAY,EACZ,EAAQ,YAAY,oBACpB,GACJ,GAEA,EAAQ,GAAG,UAAW,EAAM,OAAO,QAAU,WACzC,IAAI,EAAa,EAAY,eAC1B,GAAc,EAAW,MAAM,UAAY,EAAQ,IAAM,EAAQ,WAAW,QAAQ,EAAY,2BAA2B,EAAQ,WAAW,GACrJ,GAGA,EAAQ,GAAG,YAAa,EAAM,OAAO,UAAY,SAAS,EAAO,GAE1D,GAAW,QAAQ,OAAO,EAAO,GACpC,EAAM,iBACV,EACJ,KA/nBuB,CAEnB,EAAQ,GAAG,cAAe,EAAM,OAAO,OAAS,EAAM,OAAO,KAAO,WAC5D,GAAa,EAAQ,cAAc,IAC3C,GAEA,EAAQ,GAAG,UAAW,EAAM,OAAO,QAAU,SAAS,EAAO,GAKzD,GAHG,GAAW,QAAQ,OAAO,EAAO,GA7S7B,IAgTJ,EAAM,QAAyB,CAE9B,IAAI,EAAQ,KAAK,eACb,EAAM,KAAK,aAEX,EAAQ,EAAQ,MACpB,GAAG,EAAM,SAAS,CAEd,IAAI,EAAa,EAAM,YAAY,KAAM,GAAQ,EAAO,EAAM,YAAY,KAAM,IACnE,IAAV,GAAe,GAAQ,IAEtB,EAAQ,IAAI,EAAM,UAAU,EAAG,GAAQ,EAAM,UAAU,EAAO,IAG9D,KAAK,eAAiB,KAAK,aAAe,EAAQ,EAE1D,MAEI,EAAQ,IAAI,EAAM,UAAU,EAAG,GAAS,KAAO,EAAM,UAAU,IAG/D,KAAK,eAAiB,KAAK,aAAe,EAAQ,EAGtD,EAAM,gBACV,CACJ,GAEA,IAAI,EAAU,SAAS,EAAQ,GAE3B,IADA,IAAI,EAAS,GACL,EAAK,EAAG,EAAK,EAAG,IAAM,GAAU,EACxC,OAAO,CACX,EAGI,GAAU,SAAU,EAAO,EAAU,GACrC,IAAK,IAAI,EAAG,EAAG,EAAE,EAAM,OAAQ,IAC3B,EAAS,KAAK,EAAO,EAAG,EAAM,GAEtC,EAGI,GAAsB,SAAS,EAAU,GACzC,IAAI,EAAQ,GACR,EAAY,EAAS,WA8BzB,OA7BA,IAEA,GAAS,EAAQ,KAAM,EAAS,GAAK,EAAS,UAAU,UAAU,EAAG,GACrE,GAAQ,GAAW,SAAU,EAAO,GAEhC,IAAI,EAAW,EAAK,SAAS,cACZ,aAAb,EAIa,UAAb,EAKA,EAAK,YAKL,GADY,OAAb,GAAkC,OAAb,EACX,KAAO,GAAoB,EAAM,GAIjC,KAAO,EAAQ,KAAM,GAAY,EAAK,WAb/C,GAAS,EAAK,YAJd,GAAS,UAAS,EAAK,UAAY,QAmB3C,IAEA,GAAS,KAAO,EAAQ,KAAM,EAAS,GAAK,EAAS,UAAU,UAAU,EAAS,UAAU,YAAY,KAE5G,EAUA,EAAQ,YAAY,SAAQ,SAAS,GAQjC,IAAI,EAAS,QAAQ,QAAQ,QAAU,EAAY,UAAU,GAAG,WAkChE,OAjCG,EAAO,OAAS,IAEf,EAAY,GACZ,GAAQ,GAAQ,SAAU,EAAO,GAC7B,IAAI,EAAW,EAAK,SAAS,cACZ,aAAb,EAIa,UAAb,EAKA,EAAK,YAKN,EAAU,OAAS,IAElB,GAAa,MAIb,GAFY,OAAb,GAAkC,OAAb,EAEP,GAAK,GAAoB,EAAM,GAI/B,GAAK,EAAK,WAnBvB,GAAa,EAAK,YAJlB,GAAa,UAAS,EAAK,UAAY,QAyB/C,KAEG,CACX,GACJ,CAsfJ,IAiBI,GAjBA,GAAkB,SAAS,EAAO,GAM9B,IAAI,GAJL,GAAW,QAAQ,OAAO,EAAO,GAEhC,GAAc,KACd,GAAY,EAEY,EAArB,EAAM,cAA8B,EAAM,cAAc,aACvC,EAAM,aAC1B,EAAM,MAAM,gBAAiB,KAAM,EAAO,GAC1C,GAAS,WACL,GAAY,EACZ,OAAc,OAAW,GAAW,EACxC,GAAG,KAEX,EAGI,IAAoB,EAExB,EAAQ,QAAU,WAEd,IAAG,GAAH,CACK,IAAoB,EAEzB,IAAI,EAAM,EAAQ,YAAc,GAE5B,IAEG,GAAsB,IAErB,EAAQ,YAAY,oBAEjB,IAAgB,EAAS,OAAO,IACnC,GAAiB,GAAS,WAElB,IACA,EAAQ,GAAG,QACX,EAAY,yBAAyB,EAAQ,WAAW,EAAQ,WAAW,OAAS,KAExF,QAAiB,CACrB,GAAG,IAEJ,GAEI,EAAM,YAGD,EAFO,KAAR,EAEe,EAGA,GAMlB,EAIA,EAAQ,IAAI,OAAQ,KAHpB,IACA,EAAQ,GAAG,OAAQ,MAImB,aAArC,EAAQ,GAAG,QAAQ,eAAqE,UAArC,EAAQ,GAAG,QAAQ,cAE3E,EAAc,EAAuB,IAGrC,EAAQ,IAAI,IAGjB,GAAsB,EAAM,cAChB,KAAR,EACI,EAAW,EAAQ,YAAY,oBAC7B,EAAQ,SAAS,oBAEtB,EAAQ,YAAY,qBAG5B,GAAoB,GAAc,CAxDL,CAyDjC,EAEG,EAAM,cAEL,EAAc,EAAM,MAAM,EAAM,cAE5B,EAAQ,SAAS,eAEuB,aAArC,EAAQ,GAAG,QAAQ,eAAqE,UAArC,EAAQ,GAAG,QAAQ,eACrE,EAAQ,KAAK,WAAY,iBAEU,IAApC,EAAQ,KAAK,oBAAoC,EAAQ,KAAK,oBAC7D,EAAQ,WAAW,qBAGvB,EAAQ,YAAY,eAEoB,aAArC,EAAQ,GAAG,QAAQ,eAAqE,UAArC,EAAQ,GAAG,QAAQ,cACrE,EAAQ,WAAW,YACd,GACL,EAAQ,KAAK,kBAAmB,SAKxC,EAAM,OAAO,EAAM,YAAY,SAAS,EAAQ,GACzC,IAAW,IACX,GACC,EAAQ,SAAS,eAEuB,aAArC,EAAQ,GAAG,QAAQ,eAAqE,UAArC,EAAQ,GAAG,QAAQ,eACrE,EAAQ,KAAK,WAAY,iBAEU,IAApC,EAAQ,KAAK,oBAAoC,EAAQ,KAAK,oBAC7D,EAAQ,WAAW,mBAGvB,QAAQ,QAAQ,GAAsB,SAAS,GAC3C,EAAQ,KAAK,GAAU,GAAG,QAAS,EACvC,IACA,EAAQ,IAAI,OAAQ,MAEpB,EAAQ,YAAY,eAEoB,aAArC,EAAQ,GAAG,QAAQ,eAAqE,UAArC,EAAQ,GAAG,QAAQ,cACrE,EAAQ,WAAW,YACd,GACL,EAAQ,KAAK,kBAAmB,QAGpC,QAAQ,QAAQ,GAAsB,SAAS,GAC3C,EAAQ,KAAK,GAAU,IAAI,QAAS,EACxC,IACA,EAAQ,GAAG,OAAQ,KAEvB,EAAc,EAClB,KAKD,IAAuB,IACtB,QAAQ,QAAQ,GAAsB,SAAS,GAC3C,EAAQ,KAAK,GAAU,GAAG,QAAS,EACvC,IACA,EAAQ,GAAG,OAAQ,IAE3B,EAER,IAGA,IAAI,GAAY,EACZ,EAAc,QAAQ,OAAO,cAAe,CAAC,aAAc,mBAAoB,wBAAyB,kBAAmB,yBAA0B,uBAq4CzJ,OAn4CA,EAAY,OAAO,CAAC,WAEhB,QAAQ,QAAQ,GAAS,SAAS,EAAO,UAAa,EAAQ,EAAM,GACxE,IAEA,EAAY,UAAU,cAAe,CACjC,WAAY,WAAY,YAAa,cAAe,gBACpD,qBAAsB,YAAa,WAAY,OAAQ,KAAM,SAC7D,SAAS,EAAU,EAAU,EAAW,EAAa,EACjD,EAAoB,EAAW,EAAU,EAAM,EAAI,GACnD,MAAO,CACH,QAAS,WACT,MAAO,CAAC,EACR,SAAU,KACV,SAAU,EACV,KAAM,SAAS,EAAO,EAAS,EAAO,GAElC,IAAI,EAAU,EAAQ,EAAW,EAAU,EAAU,EACjD,EAAmB,EAEnB,EAAgB,EAAkB,EAClC,EAFA,EAAW,EAAY,OAAI,EAAM,OAAS,KAAK,MAAsB,KAAhB,KAAK,UAI9D,EAAM,MAAS,EAAU,KAAI,EAAM,KAAO,oBAAsB,EAEhE,IAAI,EAAW,SAAS,EAAU,EAAO,GACrC,GAAS,WACL,EAAS,IAAI,EAAO,EACxB,GAAG,IACP,EA+BA,GA9BA,EAAiB,EAAc,EAAM,eAErC,QAAQ,OAAO,EAAO,QAAQ,KAAK,GAAY,CAE3C,cAAe,SAAS,EAAS,EAAK,GAIL,SAA1B,EAAQ,cACP,EAAM,2BAA6B,KACJ,SAA1B,EAAQ,cACb,EAAM,2BAA6B,MAGnC,EAAe,GAAS,EAAO,EAAK,EAAM,sBACvC,GAEC,EAAM,yCAA2C,KAIrD,EAAM,gBAAgB,KAAK,GAAG,QAEtC,EACA,SAAU,EAAM,MAAM,EAAM,cAAe,IAG5C,EAAM,kBAAmB,EAAM,QAAQ,SAAW,EAAM,iBACxD,EAAM,oBAAqB,EAAM,QAAQ,WAAa,EAAM,mBAC5D,EAAM,oBAAqB,EAAM,QAAQ,WAAa,EAAM,mBAC5D,EAAM,uBACL,IAEI,QAAQ,OAAO,EAAM,qBAAsB,QAAQ,SAAS,EAAM,wBACtE,CAAE,MAAO,GACL,EAAK,MAAM,EACf,CAGD,EAAM,oBAAqB,EAAM,MAAM,gBAAkB,EAAM,QAAQ,MAAM,EAAM,oBACnF,EAAM,oBAAqB,EAAM,MAAM,gBAAkB,EAAM,QAAQ,MAAM,EAAM,oBAEnF,EAAM,WAAe,EAAM,gBAAkB,EAAM,QAAQ,MAAM,EAAM,YAC9D,EAAM,gBAAkB,EAAM,uBAE1C,EAAoB,EAAQ,GAAG,UAE/B,EAAQ,GAAG,UAAY,GAGvB,EAAM,gBAAkB,CAGpB,UAAW,QAAQ,QAAQ,8DAC3B,KAAM,QAAQ,QAAQ,yBACtB,KAAM,QAAQ,QAAQ,eAEtB,aAAc,QAAQ,QAAQ,wCAC9B,QAAS,QAAQ,QAAQ,kFACzB,aAAc,QAAQ,QAAQ,6BAC9B,iBAAkB,QAAQ,QAAQ,uCAClC,OAAQ,CACJ,QAAS,QAAQ,QAAQ,iDACzB,WAAY,QAAQ,QAAQ,oDAC5B,QAAS,CACL,QAAQ,QAAQ,4EAChB,QAAQ,QAAQ,4EAChB,QAAQ,QAAQ,4EAChB,QAAQ,QAAQ,6EAEpB,KAAM,QAAQ,QAAQ,gDAK9B,EAAM,gBAAgB,QAAQ,OAAO,EAAM,gBAAgB,cAC3D,EAAM,gBAAgB,QAAQ,OAAO,EAAM,gBAAgB,kBAC3D,EAAM,gBAAgB,aAAa,OAAO,EAAM,gBAAgB,SAEhE,EAAM,gBAAgB,QAAQ,GAAG,aAAa,SAAS,EAAG,GAKtD,OAHG,GAAW,QAAQ,OAAO,EAAG,GAEhC,EAAE,kBACK,CACX,IAGA,EAAM,oBAAsB,WAC2B,UAA/C,EAAM,gBAAgB,QAAQ,IAAI,aAC/B,GAAgB,EAAS,OAAO,GACnC,EAAiB,GAAS,WAEtB,EAAM,cAAc,EAAM,eAC1B,EAAM,oBAAoB,EAAM,cACpC,GAAG,KAEX,EAGA,QAAQ,QAAQ,QAAQ,GAAG,SAAU,EAAM,qBAG3C,QAAQ,QAAQ,QAAQ,GAAG,SAAU,EAAM,qBAuC3C,EAAM,aAAe,SAAU,EAAK,GAChC,IAAI,EAAY,EAAI,UAWpB,YAVyB,IAAd,IACP,EAAY,GAGZ,GAzCW,SAAS,GACxB,IAAI,EACA,EAAiB,CACjB,UAAU,EACV,YAAY,GAEhB,IAEI,GAAW,QADX,EAAK,OAAO,iBAAiB,IAEzB,OAAO,CAEf,CAAE,MAAO,GAEL,OAAO,CACX,CACA,IAAI,EAAY,EAAG,cACf,EAAY,EAAG,cACnB,MAAO,CACH,UAAyB,WAAd,GAAwC,SAAd,IAEzB,EAAK,aAAe,EAAK,aACrC,YAA2B,WAAd,GAAwC,SAAd,IAE3B,EAAK,YAAc,EAAK,YAE5C,CAgBwB,CAAa,GAAK,WAElC,EAAI,oBAAoB,SAAU,EAAM,iBAAiB,GACzD,EAAI,iBAAiB,SAAU,EAAM,iBAAiB,IAGxC,IAAd,EACO,CAAE,KAAK,EAAI,SAAU,IAAI,GAGhC,EAAI,WACG,EAAM,aAAa,EAAI,WAAY,GAEnC,CAAE,KAAK,SAAU,IAAI,EAEpC,EAGA,EAAM,YAAc,SAAS,GACzB,EAAM,aAAa,EAAM,gBAAgB,aAAa,IAAI,GAC1D,EAAM,gBAAgB,QAAQ,IAAI,UAAW,SAI7C,GAAS,WACL,EAAM,gBAAgB,OAAO,QAAQ,IAAI,UAAW,QACxD,IACA,EAAM,cAAgB,EACtB,EAAM,cAAc,GACpB,EAAS,SAAS,EAAM,gBAAgB,QAAS,MACjD,EAAS,EAAU,KAAK,QAAS,eAAe,WAAW,EAAM,aAAc,GACnF,EAGA,EAAM,gBAAkB,SAAU,EAAG,GACjC,EAAM,qBACV,EAEA,EAAM,cAAgB,SAAS,GAC3B,IAAI,EAAY,EAAM,aAAa,EAAM,gBAAgB,aAAa,IAAI,GACpD,EAAI,GAAG,UAAU,EAAU,IAK5B,IACjB,EAAM,gBAAgB,QAAQ,IAAI,MAAO,EAAI,GAAG,UAAY,EAAI,GAAG,aAAe,EAAM,gBAAgB,aAAa,GAAG,UAAY,MACpI,EAAM,gBAAgB,QAAQ,YAAY,OAAO,SAAS,YAE1D,EAAM,gBAAgB,QAAQ,IAAI,MAAO,EAAI,GAAG,UAAY,GAAK,EAAM,gBAAgB,aAAa,GAAG,UAAY,MACnH,EAAM,gBAAgB,QAAQ,YAAY,UAAU,SAAS,QAEjE,IAAI,EAAW,EAAM,gBAAgB,KAAK,GAAG,YAAc,EAAM,gBAAgB,QAAQ,GAAG,YACxF,EAAc,EAAI,GAAG,WAAc,EAAI,GAAG,YAAc,EAAQ,EAAM,gBAAgB,QAAQ,GAAG,YAAc,EAC/G,EAAS,KAAK,IAAI,EAAG,KAAK,IAAI,EAAU,IACxC,EAAe,KAAK,IAAI,EAAc,KAAK,IAAI,EAAG,EAAc,IAAc,GAClF,GAAU,OAAO,QACjB,GAAe,OAAO,QACtB,EAAM,gBAAgB,QAAQ,IAAI,OAAQ,EAAS,MACnD,EAAM,gBAAgB,aAAa,IAAI,cAAe,EAAc,KACxE,EACA,EAAM,YAAc,WAChB,EAAM,gBAAgB,QAAQ,IAAI,UAAW,QAC7C,EAAM,gBAAgB,iBAAiB,KAAK,QAAS,IACrD,EAAM,gBAAgB,iBAAiB,KAAK,QAAS,mBACrD,EAAM,gBAAgB,QAAQ,YAAY,MAC1C,EAAM,gBAAgB,OAAO,QAAQ,IAAI,UAAW,OACxD,EAGA,EAAM,gBAAgB,OAAO,QAAQ,OAAO,EAAM,gBAAgB,OAAO,YACzE,QAAQ,QAAQ,EAAM,gBAAgB,OAAO,SAAS,SAAS,GAAS,EAAM,gBAAgB,OAAO,QAAQ,OAAO,EAAQ,IAC5H,EAAM,gBAAgB,OAAO,QAAQ,OAAO,EAAM,gBAAgB,OAAO,MACzE,EAAM,gBAAgB,aAAa,OAAO,EAAM,gBAAgB,OAAO,SAIvE,EAAM,gBAAgB,OAAO,WAAW,GAAG,SAAS,SAAS,GACzD,EAAM,gBAAgB,KAAK,GAAG,OAClC,IAGA,EAAM,oBAAsB,SAAS,GACjC,EAAM,QAAQ,QAAQ,GAAK,GAC3B,EAAM,gBAAgB,OAAO,QAAQ,IAAI,CACrC,QAAW,QACX,KAAQ,EAAI,WAAa,EAAI,KAC7B,IAAO,EAAI,UAAY,EAAI,KAC3B,MAAS,EAAI,YAAc,GAAK,KAChC,OAAU,EAAI,aAAe,GAAK,OAEtC,EAAM,gBAAgB,OAAO,KAAK,KAAK,EAAI,YAAc,MAAQ,EAAI,aACzE,EAEA,EAAM,kBAAoB,SAAS,GAC/B,IAAI,EAAQ,EAAU,KAAK,QAC3B,EAAmB,SAAS,GACxB,IAAI,EAAgB,CAChB,MAAO,SAAS,EAAI,KAAK,UACzB,OAAQ,SAAS,EAAI,KAAK,WAC1B,EAAG,EAAM,QACT,EAAG,EAAM,eAEc,IAAxB,EAAc,OAAuB,MAAM,EAAc,UAAQ,EAAc,MAAQ,EAAI,GAAG,mBACrE,IAAzB,EAAc,QAAwB,MAAM,EAAc,WAAS,EAAc,OAAS,EAAI,GAAG,cACpG,EAAM,cACN,IAAI,EAAQ,EAAc,OAAS,EAAc,MAC7C,EAAY,SAAS,GAErB,IAAI,EAAM,CACN,EAAG,KAAK,IAAI,EAAG,EAAc,OAAS,EAAM,QAAU,EAAc,IACpE,EAAG,KAAK,IAAI,EAAG,EAAc,QAAU,EAAM,QAAU,EAAc,KAMrE,OAAwD,IAAnC,EAAM,yBAG3B,EAAkB,EAAM,4BAE5B,GADkB,GAAsB,IAAoB,EAAM,SACnD,CACX,IAAI,EAAW,EAAI,EAAI,EAAI,EAC3B,EAAI,EAAI,EAAQ,EAAW,EAAI,EAAI,EAAI,EAAI,EAC3C,EAAI,EAAI,EAAQ,EAAW,EAAI,EAAI,EAAQ,EAAI,CACnD,CACA,IAAI,EAAK,QAAQ,QAAQ,GACzB,SAAS,EAAc,GACnB,OAAO,KAAK,MAAM,KAAK,IAAI,EAAG,GAClC,CACA,EAAG,IAAI,SAAU,EAAc,EAAI,GAAK,MACxC,EAAG,IAAI,QAAS,EAAc,EAAI,GAAK,MAGvC,EAAM,oBAAoB,EAC9B,EACA,EAAM,GAAG,YAAa,GACtB,EAAS,EAAO,WAAW,SAAS,GAChC,EAAM,iBACN,EAAM,kBACN,EAAM,IAAI,YAAa,GAKvB,EAAM,QAAO,WACT,EAAM,cACN,EAAM,2BACV,GAAG,IACP,IACA,EAAM,kBACN,EAAM,gBACV,EAEA,EAAM,gBAAgB,OAAO,QAAQ,GAAG,IAAI,aAC5C,EAAM,gBAAgB,OAAO,QAAQ,GAAG,GAAG,YAAa,GAExD,EAAM,oBAAoB,GAC1B,EAAS,EAAO,SAAS,WAAW,EAAM,mBAAoB,GAClE,EAEA,EAAM,kBAAoB,WACtB,EAAM,gBAAgB,OAAO,QAAQ,GAAG,IAAI,YAAa,GACzD,EAAM,gBAAgB,OAAO,QAAQ,IAAI,UAAW,OACxD,EAGA,EAAM,MAAM,gBAAgB,EAAM,gBAAgB,MAClD,EAAM,MAAM,gBAAgB,EAAM,gBAAgB,MAClD,EAAM,gBAAgB,KAAK,KAAK,CAC5B,GAAM,gBAAkB,EACxB,UAAW,WACX,UAAW,UACX,WAAY,OACZ,mBAAoB,EAAQ,KAAK,sBAErC,EAAM,gBAAgB,KAAK,KAAK,CAC5B,GAAM,gBAAkB,EACxB,gBAAmB,OACnB,UAAW,UACX,WAAY,OACZ,mBAAoB,EAAQ,KAAK,sBAErC,EAAM,gBAAgB,aAAa,KAAK,CAAC,UAAW,aACjD,EAAM,eAEL,EAAM,gBAAgB,KAAK,KAAK,kBAAmB,EAAM,eAG1D,EAAM,oBACL,EAAM,gBAAgB,KAAK,KAAK,sBAAuB,EAAM,mBAC7D,EAAM,gBAAgB,KAAK,KAAK,sBAAuB,EAAM,oBAG9D,EAAM,eACL,EAAM,gBAAgB,KAAK,KAAK,iBAAkB,EAAM,cACxD,EAAM,gBAAgB,KAAK,KAAK,iBAAkB,EAAM,eAI5D,EAAM,gBAAgB,aAAa,OAAO,EAAM,gBAAgB,MAChE,EAAQ,OAAO,EAAM,gBAAgB,cACrC,EAAQ,OAAO,EAAM,gBAAgB,MAErC,EAAM,gBAAgB,UAAU,KAAK,OAAQ,EAAM,OACnD,EAAQ,OAAO,EAAM,gBAAgB,WAElC,EAAM,WACL,EAAQ,WAAW,YACnB,EAAM,gBAAgB,KAAK,KAAK,WAAY,EAAM,UAClD,EAAM,gBAAgB,KAAK,KAAK,WAAY,EAAM,WAGlD,EAAM,cACN,EAAM,gBAAgB,KAAK,KAAK,cAAe,EAAM,aACrD,EAAM,gBAAgB,KAAK,KAAK,cAAe,EAAM,cAGtD,EAAM,aACL,EAAM,gBAAgB,KAAK,KAAK,cAAe,YAC/C,EAAM,gBAAgB,KAAK,KAAK,cAAe,YAC/C,EAAM,SAAW,EAAM,QAAQ,MAAM,EAAM,YAC3C,EAAM,QAAQ,OAAO,EAAM,YAAY,SAAS,GAC5C,EAAM,SAAW,EACd,EAAM,SACL,EAAQ,SAAS,EAAM,QAAQ,UAE/B,EAAQ,YAAY,EAAM,QAAQ,SAE1C,KAGD,EAAM,UACL,EAAM,cAAgB,SAAS,GAC3B,OAAO,EAAO,EAAM,QAAb,CAAsB,EAAM,QAAS,CAAC,MAAO,GACxD,EACA,EAAM,gBAAgB,KAAK,KAAK,WAAY,yBAIhD,EAAS,EAAM,gBAAgB,aAA/B,CAA6C,GAC7C,EAAS,EAAM,gBAAgB,KAA/B,CAAqC,GAErC,EAAM,0BAA4B,EAAM,4BAA8B,GACtE,EAAM,0BAA4B,EAAM,4BAA8B,GAGtE,EAAQ,SAAS,WACjB,EAAM,gBAAgB,aAAa,SAAS,qBAAuB,EAAM,QAAQ,YACjF,EAAM,gBAAgB,KAAK,SAAS,qBAAuB,EAAM,QAAQ,YAEzE,IAAI,EAAa,SAAS,EAAQ,GAE1B,IAAgB,EAAU,GAAG,kBAAkB,IAC/C,EAAU,GAAG,YAAY,GAAQ,EAAO,KAEhD,EAEA,EAAM,gBAAiB,EACvB,IAAI,GAAkB,EAoKtB,GAnKA,EAAM,YAAc,WAChB,IAAI,EACA,EACA,EACA,EAiBJ,OAhBA,EAAM,gBAAiB,EACvB,EAAmB,EAAU,GAAG,kBAAkB,QAClD,EAAqB,EAAU,GAAG,kBAAkB,UACpD,EAAwB,EAAU,GAAG,kBAAkB,aACvD,EAA2B,EAAU,GAAG,kBAAkB,iBAG1D,EAAkB,EAAM,gBAIxB,EAAW,OAAQ,GACnB,EAAW,SAAU,GACrB,EAAW,YAAa,GACxB,EAAW,gBAAiB,GAErB,WACA,GAAiB,EAAM,iBAAiB,EAG/C,CACJ,EACA,EAAM,UAAY,WACd,EAAM,gBAAiB,EACpB,IACI,EAAM,SACL,EAAM,gBAAgB,KAAK,GAAG,QAE9B,EAAM,gBAAgB,KAAK,GAAG,QAGlC,EAAM,cAAc,IAExB,GAAkB,EAClB,EAAM,uBAEF,EAAM,UAAU,EAAM,4BAA8B,IAC5D,EAIA,EAAW,SAAS,GAChB,EAAM,UAAW,EACjB,EAAQ,SAAS,EAAM,QAAQ,UAQ/B,EAAiB,QACjB,EAAQ,eAAe,SAIpB,EAAM,uBAAyB,EAAM,wBAMpC,GAAS,WACL,EAAM,sBACV,GAAG,EAEX,EACA,EAAM,gBAAgB,KAAK,GAAG,QAAS,GACvC,EAAM,gBAAgB,KAAK,GAAG,QAAS,GACvC,EAAY,SAAS,GA2BjB,OAdI,EAAM,gBACN,EAAU,GAAG,gBAAkB,EAAM,gBAAgB,KAAK,IAC1D,EAAU,GAAG,gBAAkB,EAAM,gBAAgB,KAAK,KAE1D,EAAQ,YAAY,EAAM,QAAQ,UAClC,EAAiB,UAEjB,GAAS,WACL,EAAM,wBAAyB,EAC/B,EAAQ,eAAe,QACvB,EAAM,UAAW,CACrB,GAAG,IAEP,EAAE,kBACK,CACX,EACA,EAAM,gBAAgB,KAAK,GAAG,OAAQ,GACtC,EAAM,gBAAgB,KAAK,GAAG,OAAQ,GAEtC,EAAM,gBAAgB,KAAK,GAAG,SAAS,SAAS,GAC5C,EAAQ,eAAe,QAAS,EACpC,IAIA,EAAM,sBAAwB,SAAS,GAEnC,OAAQ,EAAM,UAAY,EAAQ,gBAAkB,EAAU,GAAG,kBAAkB,eAAe,aACtG,EACA,EAAM,kBAAoB,SAAS,GAE/B,OAAS,EAAM,SAAsD,GAA1C,EAAU,GAAG,kBAAkB,EAC9D,EACA,EAAM,WAAa,WACf,EAAM,UAAY,EAAM,SACxB,EAAS,SAAQ,EAAO,EAAM,gBAAgB,MAC9C,EAAS,SAAQ,EAAO,EAAM,gBAAgB,MAkB3C,EAAM,SAEL,GAAS,WAIL,OAHA,EAAS,SAAQ,EAAM,EAAM,gBAAgB,MAC7C,EAAS,SAAQ,EAAM,EAAM,gBAAgB,MAEtC,EAAM,gBAAgB,KAAK,GAAG,OACzC,GAAG,KAIH,GAAS,WAIL,OAHA,EAAS,SAAQ,EAAM,EAAM,gBAAgB,MAC7C,EAAS,SAAQ,EAAM,EAAM,gBAAgB,MAEtC,EAAM,gBAAgB,KAAK,GAAG,OACzC,GAAG,IAEX,EAIG,EAAM,QAAQ,CACb,IAAI,GAAY,EAChB,EAAQ,QAAU,WACd,GAAG,EAAU,CAET,GAAY,EAEZ,IAAI,EAAgB,EAAM,QAAQ,MAAM,EAAM,SAC3C,MAAC,GAA2D,GAA2C,KAAtB,GAEhF,EAAQ,cAAc,EAE9B,CACA,EAAM,gBAAgB,UAAU,IAAI,EAAQ,YAE5C,EAAM,KAAO,EAAQ,YAAc,EACvC,EAEG,EAAQ,KAAK,cAAa,EAAQ,YAAY,SAAW,SAAS,EAAY,GAC7E,IAAI,EAAQ,GAAc,EAC1B,SAAU,GAA0B,KAAjB,EAAM,OAC7B,EACJ,MAEI,EAAM,gBAAgB,UAAU,IAAI,GACpC,EAAM,KAAO,EAajB,GATA,EAAM,OAAO,QAAQ,SAAS,EAAU,GACjC,IAAa,IACT,EAAM,SAAW,EAAQ,aAAe,GACvC,EAAQ,cAAc,GAE1B,EAAM,gBAAgB,UAAU,IAAI,GAE5C,IAEG,EAAM,iBACL,EAAmB,EAAmB,eAAe,EAAM,MAAO,EAAO,EAAM,iBAAiB,MAAM,UAEtG,CACA,IAAI,EAAW,QAAQ,QAAQ,qDAAuD,EAAU,MAE7F,EAAM,WAAgB,EAAS,KAAK,aAAc,EAAM,WACxD,EAAM,gBAAmB,EAAS,KAAK,mBAAoB,EAAM,gBACjE,EAAM,qBAAuB,EAAS,KAAK,yBAA0B,EAAM,qBAC3E,EAAM,sBAAwB,EAAS,KAAK,0BAA2B,EAAM,sBAC7E,EAAM,4BAA4B,EAAS,KAAK,iCAAkC,EAAM,4BACxF,EAAM,iBAAoB,EAAS,KAAK,oBAAqB,EAAM,iBAEtE,EAAQ,QAAQ,GAChB,EAAS,EAAT,CAAmB,EAAM,SACzB,EAAmB,EAAmB,eAAe,EAAM,MAAO,EAAO,CAAC,qBAAuB,GACrG,CAEA,EAAM,IAAI,YAAY,WAClB,EAAmB,iBAAiB,EAAM,OAC1C,QAAQ,QAAQ,QAAQ,IAAI,QAC5B,QAAQ,QAAQ,QAAQ,IAAI,SAAU,EAAM,qBAC5C,QAAQ,QAAQ,QAAQ,IAAI,SAAU,EAAM,oBAChD,IAGA,EAAM,IAAI,qBAAqB,SAAS,EAAO,GACxC,EAAiB,qBAAqB,EAAO,IAC5C,EAAM,yCAA2C,IAEzD,IAkDA,EAAM,IAAI,iBAAiB,SAAS,EAAO,EAAS,EAAW,GACxD,GAAgB,EAAa,OAAS,EAAa,MAAM,OAAS,GACjE,EAAM,gBAAgB,KAAK,GAAG,QAG9B,EAAY,yBAAyB,EAAU,QAC/C,QAAQ,QAAQ,EAAa,OAAO,SAAS,GAIzC,IACI,EAAG,KAAK,EAAM,gBAAgB,EAAM,EAAM,gBACrC,EAAM,kBAAoB,EAAM,wBACjC,EAAG,KAAK,EAAM,uBAAuB,EAAM,EAAM,iBAAkB,MAAK,WACpE,EAAM,4BAA8B,IACxC,GACR,CAAC,MAAM,GACH,EAAK,MAAM,EACf,CACJ,IACA,EAAU,iBACV,EAAU,mBAGV,GAAS,WACL,EAAM,4BAA8B,IACxC,GAAG,EAEX,IAGA,EAAM,wBAAyB,EAE/B,QAAQ,QAAQ,QAAQ,GAAG,QAAQ,WAC/B,EAAM,wBAAyB,EAC/B,EAAM,UAAW,CACrB,IAEA,EAAM,qBAAuB,WACzB,IAAI,EAED,GAA8B,EAAS,OAAO,QAEO,KAApD,EAAa,EAAY,wBAAwC,EAAW,aAAe,EAAM,gBAAgB,KAAK,GACtH,EAAiB,qBAAqB,QAAQ,QAAQ,IACpD,EAAiB,uBAGpB,EAAM,yBAAwB,EAA+B,EAAS,EAAM,qBAAsB,KACzG,EAEA,EAAW,WAEH,EAAM,SAKN,EAAM,yBACN,EAAM,wBAAyB,EAC/B,EAAM,QAAO,WACT,EAAM,sBACV,KARA,EAAM,wBAAyB,CAUvC,EACA,EAAM,gBAAgB,KAAK,GAAG,UAAW,GACzC,EAAM,gBAAgB,KAAK,GAAG,UAAW,GAEzC,EAAS,WACL,EAAM,wBAAyB,CACnC,EACA,EAAM,gBAAgB,KAAK,GAAG,QAAS,GACvC,EAAM,gBAAgB,KAAK,GAAG,QAAS,GAEvC,EAAY,SAAS,EAAO,GAIxB,GAAI,EAAY,aAAc,CAC1B,IAAI,EAAa,EAAY,eAIzB,EAAY,uBAAsF,MAA7D,EAAY,sBAAsB,SAAS,gBAEtC,IAAtC,EAAW,MAAM,QAAQ,UACzB,EAAW,MAAM,QAAQ,YAAY,SAAW,EAAW,IAAI,QAG/D,EAAY,yBAAyB,EAAY,uBAEX,IAAtC,EAAW,MAAM,QAAQ,UACG,IAA5B,EAAW,MAAM,QAGjB,EAAY,0BAA0B,EAAY,uBAG9D,CAEG,GAAW,QAAQ,OAAO,EAAO,GACpC,EAAM,QAAO,WACT,GAAG,EAAiB,eAAe,GAM/B,OAJI,EAAM,wBACN,EAAM,uBAEV,EAAM,kBACC,CAEf,GACJ,EACA,EAAM,gBAAgB,KAAK,GAAG,WAAY,GAC1C,EAAM,gBAAgB,KAAK,GAAG,WAAY,GAE1C,EAAW,WAEP,EAAM,wBAAyB,EAK/B,GAAS,WAAa,EAAM,sBAAwB,GAAG,EAC3D,EACA,EAAM,gBAAgB,KAAK,GAAG,UAAW,GACzC,EAAM,gBAAgB,KAAK,GAAG,UAAW,EAC7C,EAER,IAEJ,EAAY,QAAQ,qBAAsB,CAAC,sBAAuB,UAAW,iBAAkB,YAAa,aAAc,OACtH,SAAS,EAAqB,EAAS,EAAgB,EAAW,EAAY,GAI9E,IAUI,EAVA,EAAW,CAAC,EAAG,EAAU,CAAC,EAG1B,EAAyB,EACzB,EAAe,SAAS,GACxB,QAAQ,QAAQ,GAAS,SAAS,GAC9B,EAAO,gBAAgB,qBAAqB,EAChD,GACJ,EAYA,EAAW,IAAI,WAAW,WAClB,IACA,EAAU,OAAO,GACjB,OAAuB,EAE/B,IACA,IAAI,EAAoB,WAChB,KAAK,IAAI,KAAK,MAAQ,GAlBR,KAGlB,EAAyB,KAAK,MAE9B,EAAuB,GAAU,WAC7B,IACA,OAAuB,CAC3B,GARkB,GAQE,GAcxB,EAGA,MAAO,CAEH,eAAgB,SAAS,EAAY,EAAa,GAI9C,IAAI,GAA6B,KAAf,EAAmB,KAAK,+CAC1C,IAAI,EAAa,KAAK,gDACtB,GAAG,EAAQ,GAAa,KAAM,2CAA6C,EAAa,mBA4IxF,OA3IA,EAAQ,GAAc,CAClB,MAAO,EACP,SAAU,EAEV,cAAe,GACf,sBAAuB,SAAS,GAEzB,KAAK,SAAS,QAAQ,EAAa,OAAS,GAE3C,KAAK,cAAc,KAAK,EAEhC,EAEA,gBAAiB,CACb,QAAS,WAEL,QAAQ,QAAQ,EAAQ,GAAY,eAAe,SAAS,GACxD,EAAa,UAAW,CAC5B,GACJ,EACA,OAAQ,WAEJ,QAAQ,QAAQ,EAAQ,GAAY,eAAe,SAAS,GACxD,EAAa,UAAW,CAC5B,GACJ,EACA,MAAO,WAEH,QAAQ,QAAQ,EAAQ,GAAY,eAAe,SAAS,GACxD,EAAa,QAAU,EACvB,EAAa,UAAW,EACxB,EAAa,UAAW,CAC5B,IACA,EAAY,UAAW,CAC3B,EACA,QAAS,WAEL,QAAQ,QAAQ,EAAQ,GAAY,eAAe,SAAS,GACxD,EAAa,UAAW,EACxB,EAAa,UAAW,CAC5B,IACA,EAAY,UAAW,CAC3B,EACA,qBAAsB,SAAS,GAE3B,QAAQ,QAAQ,EAAQ,GAAY,eAAe,SAAS,GACxD,QAAQ,QAAQ,EAAa,OAAO,SAAS,GACtC,EAAU,cACT,EAAa,QAAU,EAEvB,EAAU,OAAS,EAAU,YAAY,GAEjD,GACJ,GACJ,EACA,eAAgB,SAAS,GAErB,IAAI,GAAS,EAYb,OAXG,EAAM,SAAW,EAAM,SAAW,EAAM,aAAY,QAAQ,QAAQ,GAAS,SAAS,EAAM,GAC3F,GAAG,EAAK,iBAAmB,EAAK,iBAAmB,EAAM,OAAS,EAAK,iBAAmB,EAAM,YAC5F,IAAI,IAAI,EAAK,EAAG,EAAK,EAAQ,GAAY,cAAc,OAAQ,IAC3D,QAAyD,IAAtD,EAAQ,GAAY,cAAc,GAAI,MAAM,GAAoB,CAC/D,EAAoB,KAAK,EAAQ,GAAY,cAAc,GAAI,MAAM,GAAO,GAC5E,GAAS,EACT,KACJ,CAGZ,IACO,CACX,EACA,qBAAsB,SAAS,EAAO,GAIlC,IAAI,EAAkB,SAAS,EAAU,GAErC,IADA,IAAI,GAAS,EACL,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,EAAS,GAAU,EAAS,KAAK,EAAM,IAC7E,OAAO,CACX,EACI,EAAc,GACd,EAAkB,CAAC,EACnB,GAAS,EACb,EAAU,QAAQ,QAAQ,GAE1B,IAAI,GAAsB,EA6B1B,GA5BA,QAAQ,QAAQ,GAAS,SAAS,EAAM,GAEhC,EAAK,iBACL,EAAK,gBAAgB,SACrB,EAAK,gBAAgB,QAAQ,gBAAkB,EAAQ,GAAG,QAAQ,iBAChE,EAAK,gBAAgB,QAAU,EAAK,gBAAgB,OAAO,MAG7D,EAAsB,GACjB,QAAQ,QAAQ,EAAK,gBAAgB,gBAAkB,EAAgB,EAAS,EAAK,gBAAgB,eACtG,EAAK,gBAAgB,gBAAiB,EAAgB,EAAS,EAAK,gBAAgB,iBAAgB,EAAgB,GAAQ,GAExI,IAEG,GACC,QAAQ,QAAQ,GAAiB,SAAS,EAAM,GACzC,EAAK,gBAAgB,eAAiB,EAAgB,EAAS,EAAK,gBAAgB,gBAAgB,EAAY,KAAK,CAAC,KAAQ,EAAM,KAAQ,GACnJ,IAEA,EAAY,MAAK,SAAS,EAAE,GACxB,OAAO,EAAE,KAAK,gBAAgB,cAAc,OAAS,EAAE,KAAK,gBAAgB,cAAc,MAC9F,KAEA,QAAQ,QAAQ,GAAiB,SAAS,EAAM,GAC5C,EAAY,KAAK,CAAC,KAAQ,EAAM,KAAQ,GAC5C,IAGD,EAAY,OAAS,EACpB,IAAI,IAAI,EAAK,EAAG,EAAK,EAAY,OAAQ,IAAK,CAG1C,IAFA,IAAI,EAAO,EAAY,GAAI,KACvB,EAAO,EAAY,GAAI,KACnB,EAAK,EAAG,EAAK,EAAQ,GAAY,cAAc,OAAQ,IAC3D,QAAyD,IAAtD,EAAQ,GAAY,cAAc,GAAI,MAAM,GAAoB,CAC/D,EAAK,gBAAgB,OAAO,KAAK,EAAQ,GAAY,cAAc,GAAI,MAAM,GAAO,EAAO,EAAS,GACpG,GAAS,EACT,KACJ,CAEJ,GAAG,EAAQ,KACf,CAEJ,OAAO,CACX,IAGR,QAAQ,QAAQ,GAAgB,SAAS,GAClC,EAAS,IACR,EAAQ,GAAY,cAAc,KAAK,EAAS,GAGxD,IACA,IACO,EAAQ,GAAY,eAC/B,EAEA,eAAgB,SAAS,GACrB,OAAO,EAAQ,EACnB,EACA,iBAAkB,SAAS,UAChB,EAAQ,GACf,GACJ,EAEA,gBAAiB,SAAS,GACtB,IAAI,EAAc,KAAK,gDACvB,IAAI,EAAa,MAA8B,KAAtB,EAAa,KAAa,KAAK,+CACxD,GAAG,EAAS,EAAa,MAAO,KAAM,2CAA6C,EAAa,KAAO,mBACvG,EAAS,EAAa,MAAQ,EAI9B,QAAQ,QAAQ,GAAS,SAAS,GAC9B,EAAQ,sBAAsB,EAClC,IACA,GACJ,EAEA,gBAAiB,SAAS,GACtB,OAAO,EAAS,EACpB,EAEA,0BAA2B,SAAS,GAChC,IAAI,EAAS,GAAI,EAAQ,KAIzB,OAHA,QAAQ,QAAQ,KAAK,eAAe,GAAM,UAAU,SAAS,GACzD,EAAO,KAAK,EAAM,gBAAgB,GACtC,IACO,CACX,EACA,kBAAmB,SAAS,UACjB,EAAS,GAChB,GACJ,EAEA,mBAAoB,SAAS,GAEzB,IAAI,EAAQ,KACZ,QAAQ,QAAQ,GAAY,SAAS,EAAU,GAC3C,EAAM,kBAAkB,EAAK,EACjC,GACJ,EAEA,kBAAmB,WACf,IAAI,EAAQ,KACZ,QAAQ,QAAQ,GAAS,SAAS,EAAU,GACxC,EAAM,iBAAiB,EAC3B,IACA,GACJ,EAEA,kBAAmB,SAAS,EAAS,GACjC,IAAI,EAAQ,KACZ,QAAQ,QAAQ,GAAU,SAAS,EAAc,GAC7C,EAAM,yBAAyB,EAAY,EAAS,EACxD,IACA,GACJ,EAEA,iBAAkB,SAAS,GACvB,IAAI,EAAQ,KACZ,QAAQ,QAAQ,GAAU,SAAS,EAAc,GAC7C,EAAM,wBAAwB,EAAY,EAC9C,IACA,GACJ,EAEA,yBAA0B,SAAS,EAAY,EAAS,GACpD,IAAG,EAAS,GACP,KAAM,4CAA8C,EAAa,WAD7C,EAAS,GAAY,kBAAkB,EAAS,EAE7E,EAEA,wBAAyB,SAAS,EAAY,GAC1C,IAAG,EAAS,GACP,KAAM,4CAA8C,EAAa,WAD7C,EAAS,GAAY,kBAAkB,EAAS,EAAQ,IAAU,EAE/F,EAEA,WAAY,SAAS,UACV,EAAQ,GACf,QAAQ,QAAQ,GAAU,SAAS,UACxB,EAAa,MAAM,GAC1B,IAAI,IAAI,EAAI,EAAG,EAAI,EAAa,QAAQ,OAAQ,IAAI,CAEhD,IADA,IAAI,EACI,EAAI,EAAG,EAAI,EAAa,QAAQ,GAAG,OAAQ,IAAI,CACnD,GAAG,EAAa,QAAQ,GAAG,KAAO,EAAQ,CACtC,EAAe,CACX,MAAO,EACP,MAAO,GAEX,KACJ,CACA,QAAoB,IAAjB,EAA4B,KACnC,MACoB,IAAjB,IACC,EAAa,QAAQ,EAAa,OAAO,MAAM,EAAa,MAAO,GACnE,EAAa,UAAU,WAAW,GAAG,EAAa,OAAO,WAAW,GAAG,EAAa,OAAO,SAEnG,CACJ,IACA,GACJ,EAEA,QAAS,SAAS,EAAS,EAAgB,EAAO,GAC9C,EAAe,EAAS,GACxB,QAAQ,QAAQ,GAAU,SAAS,GAC/B,EAAa,QAAQ,EAAS,EAAgB,EAAO,EACzD,IACA,GACJ,EAEA,iBAAkB,SAAS,EAAS,EAAgB,EAAY,EAAO,GACnE,EAAe,EAAS,GACxB,EAAS,GAAY,QAAQ,EAAS,EAAgB,EAAO,GAC7D,GACJ,EAGA,cAAe,SAAS,GACpB,IAAG,EAAQ,GAIL,KAAM,2CAA6C,EAAO,WAH5D,EAAQ,GAAM,MAAM,4BAEhB,EAAQ,GAAM,MAAM,SAAS,EAAQ,GAAM,MAAM,UAEzD,GACJ,EAEA,eAAgB,SAAS,EAAO,GAC5B,IAAI,EAAU,EAAQ,EAAM,OAE5B,GAAI,GAAW,EAAQ,gBAAgB,eAAe,GAMlD,OAJI,EAAM,wBACN,EAAM,uBAEV,EAAM,kBACC,CAEf,EAOA,aAAc,EAEd,WAAY,WAAc,MA58HT,SA48HoC,EAErD,iBAAkB,WACd,IAAI,EAAI,GAIR,OAHA,QAAQ,QAAQ,GAAS,SAAU,GAC/B,EAAM,EAAI,OAAO,EAAQ,cAC7B,IACO,CACX,EAmBR,IACA,EAAY,UAAU,qBAAsB,CACxC,WAAY,qBAAsB,YAAa,UAAW,sBAAuB,UACjF,SAAS,EAAU,EAAoB,EAAW,EAAS,EAAqB,GAC5E,MAAO,CACH,MAAO,CACH,KAAM,KAEV,SAAU,KACV,KAAM,SAAS,EAAO,EAAS,GAC3B,IAAI,EAAM,MAAuB,KAAf,EAAM,KAAa,KAAK,+CAC1C,QAAQ,OAAO,EAAO,QAAQ,KAAK,IAChC,EAAM,YAAgB,EAAM,QAAU,EAAM,QAAQ,MAAM,EAAM,YAChE,EAAM,iBAAmB,EAAM,QAAQ,QAAU,EAAM,gBACvD,EAAM,sBAAuB,EAAM,QAAQ,aAAe,EAAM,qBAChE,EAAM,uBAAwB,EAAM,QAAQ,cAAgB,EAAM,sBAClE,EAAM,6BAA4B,EAAM,QAAQ,oBAAsB,EAAM,4BAC5E,EAAM,kBAAoB,EAAM,QAAQ,SAAW,EAAM,iBAE5D,EAAM,UAAW,EACjB,EAAM,UAAW,EACjB,EAAM,UAAY,EAClB,EAAQ,GAAG,UAAY,GACvB,EAAQ,SAAS,cAAgB,EAAM,QAAQ,SAE/C,EAAM,OAAO,YAAY,WAClB,EAAM,SAAU,EAAQ,SAAS,EAAM,QAAQ,UAC7C,EAAQ,YAAY,EAAM,QAAQ,SAC3C,IAEA,IAAI,EAAmB,SAAS,EAAgB,GAC5C,IAAI,EAoBJ,GAlBI,EADD,GAAkB,EAAe,QAClB,QAAQ,QAAQ,EAAe,SAE9B,QAAQ,QAAQ,0BAEhC,GAAkB,EAAsB,MAAG,EAAY,SAAS,EAAsB,OACpF,EAAY,SAAS,EAAM,QAAQ,eAExC,EAAY,KAAK,OAAQ,EAAU,MAEnC,EAAY,KAAK,YAAa,aAC9B,EAAY,KAAK,cAAe,gBAChC,EAAY,KAAK,WAAY,MAC7B,EAAY,KAAK,WAAY,mBAC7B,EAAY,KAAK,WAAY,kCAEzB,GAAkB,EAAe,aACjC,EAAY,KAAK,QAAS,EAAe,aAE1C,IAAmB,EAAe,UAAY,EAAU,WAEvD,EAAY,GAAG,UAAY,GAExB,EAAe,aAAY,EAAY,GAAG,UAAY,EAAe,YAErE,EAAe,WAAU,CACxB,IAAI,EAAO,QAAQ,QAAQ,OAAQ,EAAU,EAAY,GAAG,UAC5D,EAAK,SAAS,EAAe,WAC7B,EAAY,GAAG,UAAY,GAC3B,EAAY,OAAO,GAChB,GAAuB,KAAZ,GAAgB,EAAY,OAAO,SAAW,EAChE,CAKJ,OAFA,EAAU,oBAAsB,QAAQ,KAAK,GAEtC,EAAS,EAAT,CAAsB,EACjC,EAGA,EAAM,MAAQ,CAAC,EAGf,EAAM,QAAU,CACZ,UAAU,EACV,UAAU,EACV,sBAAuB,WAAY,OAAO,CAAO,EACjD,kBAAmB,WAAY,OAAO,CAAO,GAEjD,IAAI,EAAoB,CACpB,QAAS,EACT,QAAS,WAEL,OAAO,EAAM,OACjB,EACA,WAAY,WAER,OAAkB,SAAd,KAAK,OAAmB,EAAM,QAAQ,eAMH,mBAA3B,KAAK,MAAM,aAA8B,KAAK,MAAM,aAAgB,KAAK,MAAM,eAExE,SAAd,KAAK,MAAmB,KAAK,UAAU,UAExC,KAAK,QAAQ,UAEb,KAAK,UAAU,SAEvB,EACA,uBAAwB,SAAS,GAC7B,OAAO,EAAU,EAAM,QAAQ,oBAAsB,EACzD,EACA,cAAe,GAGnB,QAAQ,QAAQ,EAAM,SAAS,SAAS,GAEpC,IAAI,EAAe,QAAQ,QAAQ,SACnC,EAAa,SAAS,EAAM,QAAQ,cACpC,QAAQ,QAAQ,GAAO,SAAS,GAK5B,EAAM,MAAM,GAAQ,QAAQ,OAAO,EAAM,MAAK,GAAO,EAAQ,GAAO,EAAmB,CAAC,KAAM,IAC9F,EAAM,MAAM,GAAM,SAAW,EAAiB,EAAQ,GAAO,EAAM,MAAM,IAEzE,EAAa,OAAO,EAAM,MAAM,GAAM,SAC1C,IAEA,EAAQ,OAAO,EACnB,IAMA,EAAM,kBAAoB,SAAS,EAAK,EAAU,GAC9C,IAAI,EAAe,EAAM,MAAM,GAC/B,GAAG,EAAa,CAGZ,GADG,EAAa,sBAAwB,IAAU,EAAW,QAAQ,OAAO,CAAC,EAAG,EAAa,oBAAqB,IACvF,OAAxB,EAAS,YAA8C,OAAvB,EAAS,WAA2C,OAArB,EAAS,QACvE,KAAM,oDAAsD,EAAM,6DAG3C,OAAxB,EAAS,mBACD,EAAS,WAEM,OAAvB,EAAS,kBACD,EAAS,UAEI,OAArB,EAAS,gBACD,EAAS,QAGpB,IAAI,EAAc,EAAiB,EAAU,GAC7C,EAAa,SAAS,YAAY,GAClC,EAAa,SAAW,CAC5B,CACJ,EAGA,EAAM,QAAU,SAAS,EAAK,EAAU,EAAY,GAGhD,IAAI,EAFJ,EAAM,MAAM,GAAO,QAAQ,OAAO,EAAM,MAAK,GAAO,EAAQ,GAAM,EAAmB,CAAC,KAAM,IAC5F,EAAM,MAAM,GAAK,SAAW,EAAiB,EAAQ,GAAM,EAAM,MAAM,SAErD,IAAf,IAA0B,EAAa,EAAM,QAAQ,OAAS,GACjE,EAAQ,QAAQ,QAAQ,EAAQ,WAAW,SAE9B,IAAV,GACC,EAAM,OAAO,EAAM,MAAM,GAAK,UAC9B,EAAM,QAAQ,GAAY,EAAM,QAAQ,GAAY,OAAS,GAAK,IAElE,EAAM,WAAW,GAAG,GAAO,MAAM,EAAM,MAAM,GAAK,UAClD,EAAM,QAAQ,GAAY,GAAS,EAE3C,EAEA,EAAmB,gBAAgB,GAEnC,EAAM,IAAI,YAAY,WAClB,EAAmB,kBAAkB,EAAM,KAC/C,GACJ,EAER,IAEJ,EAAY,UAAU,qBAAsB,CAAC,qBACzC,SAAS,GACL,IAAI,EAAU,EAAmB,aACjC,MAAO,CACH,SAAU,KACV,KAAM,SAAU,EAAO,EAAS,GAC5B,EAAQ,KAAK,EACjB,EAER,IAGG,EAAY,IAEnB","file":"/npm/dash-textangular@1.5.20/dist/textAngular.umd.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/dash-textangular@1.5.20/dist/textAngular.umd.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","(function (root, factory) {\n  if (typeof define === 'function' && define.amd) {\n    // AMD. Register as an anonymous module unless amdModuleId is set\n    define('textAngular', [\"rangy\",\"rangy/lib/rangy-selectionsaverestore\"], function (a0,b1) {\n      return (root['textAngular.name'] = factory(a0,b1));\n    });\n  } else if (typeof exports === 'object') {\n    // Node. Does not work with strict CommonJS, but\n    // only CommonJS-like environments that support module.exports,\n    // like Node.\n    module.exports = factory(require(\"rangy\"),require(\"rangy/lib/rangy-selectionsaverestore\"));\n  } else {\n    root['textAngular'] = factory(rangy);\n  }\n}(this, function (rangy) {\n\n\n// tests against the current jqLite/jquery implementation if this can be an element\nfunction validElementString(string){\n    try{\n        return angular.element(string).length !== 0;\n    }catch(any){\n        return false;\n    }\n}\n// setup the global contstant functions for setting up the toolbar\n\n// all tool definitions\nvar taTools = {};\n/*\n    A tool definition is an object with the following key/value parameters:\n        action: [function(deferred, restoreSelection)]\n                a function that is executed on clicking on the button - this will allways be executed using ng-click and will\n                overwrite any ng-click value in the display attribute.\n                The function is passed a deferred object ($q.defer()), if this is wanted to be used `return false;` from the action and\n                manually call `deferred.resolve();` elsewhere to notify the editor that the action has finished.\n                restoreSelection is only defined if the rangy library is included and it can be called as `restoreSelection()` to restore the users\n                selection in the WYSIWYG editor.\n        display: [string]?\n                Optional, an HTML element to be displayed as the button. The `scope` of the button is the tool definition object with some additional functions\n                If set this will cause buttontext and iconclass to be ignored\n        class: [string]?\n                Optional, if set will override the taOptions.classes.toolbarButton class.\n        buttontext: [string]?\n                if this is defined it will replace the contents of the element contained in the `display` element\n        iconclass: [string]?\n                if this is defined an icon (<i>) will be appended to the `display` element with this string as it's class\n        tooltiptext: [string]?\n                Optional, a plain text description of the action, used for the title attribute of the action button in the toolbar by default.\n        activestate: [function(commonElement)]?\n                this function is called on every caret movement, if it returns true then the class taOptions.classes.toolbarButtonActive\n                will be applied to the `display` element, else the class will be removed\n        disabled: [function()]?\n                if this function returns true then the tool will have the class taOptions.classes.disabled applied to it, else it will be removed\n    Other functions available on the scope are:\n        name: [string]\n                the name of the tool, this is the first parameter passed into taRegisterTool\n        isDisabled: [function()]\n                returns true if the tool is disabled, false if it isn't\n        displayActiveToolClass: [function(boolean)]\n                returns true if the tool is 'active' in the currently focussed toolbar\n        onElementSelect: [Object]\n                This object contains the following key/value pairs and is used to trigger the ta-element-select event\n                element: [String]\n                    an element name, will only trigger the onElementSelect action if the tagName of the element matches this string\n                filter: [function(element)]?\n                    an optional filter that returns a boolean, if true it will trigger the onElementSelect.\n                action: [function(event, element, editorScope)]\n                    the action that should be executed if the onElementSelect function runs\n*/\n// name and toolDefinition to add into the tools available to be added on the toolbar\nfunction registerTextAngularTool(name, toolDefinition){\n    if(!name || name === '' || taTools.hasOwnProperty(name)) throw('textAngular Error: A unique name is required for a Tool Definition');\n    if(\n        (toolDefinition.display && (toolDefinition.display === '' || !validElementString(toolDefinition.display))) ||\n        (!toolDefinition.display && !toolDefinition.buttontext && !toolDefinition.iconclass)\n    )\n        throw('textAngular Error: Tool Definition for \"' + name + '\" does not have a valid display/iconclass/buttontext value');\n    taTools[name] = toolDefinition;\n}\n\nangular.module('textAngularSetup', [])\n.constant('taRegisterTool', registerTextAngularTool)\n.value('taTools', taTools)\n// Here we set up the global display defaults, to set your own use a angular $provider#decorator.\n.value('taOptions',  {\n    //////////////////////////////////////////////////////////////////////////////////////\n    // forceTextAngularSanitize\n    // set false to allow the textAngular-sanitize provider to be replaced\n    // with angular-sanitize or a custom provider.\n    forceTextAngularSanitize: true,\n    ///////////////////////////////////////////////////////////////////////////////////////\n    // keyMappings\n    // allow customizable keyMappings for specialized key boards or languages\n    //\n    // keyMappings provides key mappings that are attached to a given commandKeyCode.\n    // To modify a specific keyboard binding, simply provide function which returns true\n    // for the event you wish to map to.\n    // Or to disable a specific keyboard binding, provide a function which returns false.\n    // Note: 'RedoKey' and 'UndoKey' are internally bound to the redo and undo functionality.\n    // At present, the following commandKeyCodes are in use:\n    // 98, 'TabKey', 'ShiftTabKey', 105, 117, 'UndoKey', 'RedoKey'\n    //\n    // To map to an new commandKeyCode, add a new key mapping such as:\n    // {commandKeyCode: 'CustomKey', testForKey: function (event) {\n    //  if (event.keyCode=57 && event.ctrlKey && !event.shiftKey && !event.altKey) return true;\n    // } }\n    // to the keyMappings. This example maps ctrl+9 to 'CustomKey'\n    // Then where taRegisterTool(...) is called, add a commandKeyCode: 'CustomKey' and your\n    // tool will be bound to ctrl+9.\n    //\n    // To disble one of the already bound commandKeyCodes such as 'RedoKey' or 'UndoKey' add:\n    // {commandKeyCode: 'RedoKey', testForKey: function (event) { return false; } },\n    // {commandKeyCode: 'UndoKey', testForKey: function (event) { return false; } },\n    // to disable them.\n    //\n    keyMappings : [],\n    toolbar: [\n        ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre', 'quote'],\n        ['bold', 'italics', 'underline', 'strikeThrough', 'ul', 'ol', 'redo', 'undo', 'clear'],\n        ['justifyLeft','justifyCenter','justifyRight','justifyFull','indent','outdent'],\n        ['html', 'insertImage', 'insertLink', 'insertVideo', 'wordcount', 'charcount']\n    ],\n    classes: {\n        focussed: \"focussed\",\n        toolbar: \"btn-toolbar\",\n        toolbarGroup: \"btn-group\",\n        toolbarButton: \"btn btn-default\",\n        toolbarButtonActive: \"active\",\n        disabled: \"disabled\",\n        textEditor: 'form-control',\n        htmlEditor: 'form-control'\n    },\n    defaultTagAttributes : {\n        a: {target:\"\"}\n    },\n    setup: {\n        // wysiwyg mode\n        textEditorSetup: function($element){ /* Do some processing here */ },\n        // raw html\n        htmlEditorSetup: function($element){ /* Do some processing here */ }\n    },\n    defaultFileDropHandler:\n        /* istanbul ignore next: untestable image processing */\n        function(file, insertAction){\n            var reader = new FileReader();\n            if(file.type.substring(0, 5) === 'image'){\n                reader.onload = function() {\n                    if(reader.result !== '') insertAction('insertImage', reader.result, true);\n                };\n\n                reader.readAsDataURL(file);\n                // NOTE: For async procedures return a promise and resolve it when the editor should update the model.\n                return true;\n            }\n            return false;\n        }\n})\n\n// This is the element selector string that is used to catch click events within a taBind, prevents the default and $emits a 'ta-element-select' event\n// these are individually used in an angular.element().find() call. What can go here depends on whether you have full jQuery loaded or just jQLite with angularjs.\n// div is only used as div.ta-insert-video caught in filter.\n.value('taSelectableElements', ['a','img'])\n\n// This is an array of objects with the following options:\n//\t\t\t\tselector: <string> a jqLite or jQuery selector string\n//\t\t\t\tcustomAttribute: <string> an attribute to search for\n//\t\t\t\trenderLogic: <function(element)>\n// Both or one of selector and customAttribute must be defined.\n.value('taCustomRenderers', [\n    {\n        // Parse back out: '<div class=\"ta-insert-video\" ta-insert-video src=\"' + urlLink + '\" allowfullscreen=\"true\" width=\"300\" frameborder=\"0\" height=\"250\"></div>'\n        // To correct video element. For now only support youtube\n        selector: 'img',\n        customAttribute: 'ta-insert-video',\n        renderLogic: function(element){\n            var iframe = angular.element('<iframe></iframe>');\n            var attributes = element.prop(\"attributes\");\n            // loop through element attributes and apply them on iframe\n            angular.forEach(attributes, function(attr) {\n                iframe.attr(attr.name, attr.value);\n            });\n            iframe.attr('src', iframe.attr('ta-insert-video'));\n            element.replaceWith(iframe);\n        }\n    }\n])\n\n.value('taTranslations', {\n    // moved to sub-elements\n    //toggleHTML: \"Toggle HTML\",\n    //insertImage: \"Please enter a image URL to insert\",\n    //insertLink: \"Please enter a URL to insert\",\n    //insertVideo: \"Please enter a youtube URL to embed\",\n    html: {\n        tooltip: 'Toggle html / Rich Text'\n    },\n    // tooltip for heading - might be worth splitting\n    heading: {\n        tooltip: 'Heading '\n    },\n    p: {\n        tooltip: 'Paragraph'\n    },\n    pre: {\n        tooltip: 'Preformatted text'\n    },\n    ul: {\n        tooltip: 'Unordered List'\n    },\n    ol: {\n        tooltip: 'Ordered List'\n    },\n    quote: {\n        tooltip: 'Quote/unquote selection or paragraph'\n    },\n    undo: {\n        tooltip: 'Undo'\n    },\n    redo: {\n        tooltip: 'Redo'\n    },\n    bold: {\n        tooltip: 'Bold'\n    },\n    italic: {\n        tooltip: 'Italic'\n    },\n    underline: {\n        tooltip: 'Underline'\n    },\n    strikeThrough:{\n        tooltip: 'Strikethrough'\n    },\n    justifyLeft: {\n        tooltip: 'Align text left'\n    },\n    justifyRight: {\n        tooltip: 'Align text right'\n    },\n    justifyFull: {\n        tooltip: 'Justify text'\n    },\n    justifyCenter: {\n        tooltip: 'Center'\n    },\n    indent: {\n        tooltip: 'Increase indent'\n    },\n    outdent: {\n        tooltip: 'Decrease indent'\n    },\n    clear: {\n        tooltip: 'Clear formatting'\n    },\n    insertImage: {\n        dialogPrompt: 'Please enter an image URL to insert',\n        tooltip: 'Insert image',\n        hotkey: 'the - possibly language dependent hotkey ... for some future implementation'\n    },\n    insertVideo: {\n        tooltip: 'Insert video',\n        dialogPrompt: 'Please enter a youtube URL to embed'\n    },\n    insertLink: {\n        tooltip: 'Insert / edit link',\n        dialogPrompt: \"Please enter a URL to insert\"\n    },\n    editLink: {\n        reLinkButton: {\n            tooltip: \"Relink\"\n        },\n        unLinkButton: {\n            tooltip: \"Unlink\"\n        },\n        targetToggle: {\n            buttontext: \"Open in New Window\"\n        }\n    },\n    wordcount: {\n        tooltip: 'Display words Count'\n    },\n        charcount: {\n        tooltip: 'Display characters Count'\n    }\n})\n.factory('taToolFunctions', ['$window','taTranslations', function($window, taTranslations) {\n    return {\n        imgOnSelectAction: function(event, $element, editorScope){\n            // setup the editor toolbar\n            // Credit to the work at http://hackerwins.github.io/summernote/ for this editbar logic/display\n            var finishEdit = function(){\n                editorScope.updateTaBindtaTextElement();\n                editorScope.hidePopover();\n            };\n            event.preventDefault();\n            editorScope.displayElements.popover.css('width', '375px');\n            var container = editorScope.displayElements.popoverContainer;\n            container.empty();\n            var buttonGroup = angular.element('<div class=\"btn-group\" style=\"padding-right: 6px;\">');\n            var fullButton = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" unselectable=\"on\" tabindex=\"-1\">100% </button>');\n            fullButton.on('click', function(event){\n                event.preventDefault();\n                $element.css({\n                    'width': '100%',\n                    'height': ''\n                });\n                finishEdit();\n            });\n            var halfButton = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" unselectable=\"on\" tabindex=\"-1\">50% </button>');\n            halfButton.on('click', function(event){\n                event.preventDefault();\n                $element.css({\n                    'width': '50%',\n                    'height': ''\n                });\n                finishEdit();\n            });\n            var quartButton = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" unselectable=\"on\" tabindex=\"-1\">25% </button>');\n            quartButton.on('click', function(event){\n                event.preventDefault();\n                $element.css({\n                    'width': '25%',\n                    'height': ''\n                });\n                finishEdit();\n            });\n            var resetButton = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" unselectable=\"on\" tabindex=\"-1\">Reset</button>');\n            resetButton.on('click', function(event){\n                event.preventDefault();\n                $element.css({\n                    width: '',\n                    height: ''\n                });\n                finishEdit();\n            });\n            buttonGroup.append(fullButton);\n            buttonGroup.append(halfButton);\n            buttonGroup.append(quartButton);\n            buttonGroup.append(resetButton);\n            container.append(buttonGroup);\n\n            buttonGroup = angular.element('<div class=\"btn-group\" style=\"padding-right: 6px;\">');\n            var floatLeft = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" unselectable=\"on\" tabindex=\"-1\"><i class=\"fa fa-align-left\"></i></button>');\n            floatLeft.on('click', function(event){\n                event.preventDefault();\n                // webkit\n                $element.css('float', 'left');\n                // firefox\n                $element.css('cssFloat', 'left');\n                // IE < 8\n                $element.css('styleFloat', 'left');\n                finishEdit();\n            });\n            var floatRight = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" unselectable=\"on\" tabindex=\"-1\"><i class=\"fa fa-align-right\"></i></button>');\n            floatRight.on('click', function(event){\n                event.preventDefault();\n                // webkit\n                $element.css('float', 'right');\n                // firefox\n                $element.css('cssFloat', 'right');\n                // IE < 8\n                $element.css('styleFloat', 'right');\n                finishEdit();\n            });\n            var floatNone = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" unselectable=\"on\" tabindex=\"-1\"><i class=\"fa fa-align-justify\"></i></button>');\n            floatNone.on('click', function(event){\n                event.preventDefault();\n                // webkit\n                $element.css('float', '');\n                // firefox\n                $element.css('cssFloat', '');\n                // IE < 8\n                $element.css('styleFloat', '');\n                finishEdit();\n            });\n            buttonGroup.append(floatLeft);\n            buttonGroup.append(floatNone);\n            buttonGroup.append(floatRight);\n            container.append(buttonGroup);\n\n            buttonGroup = angular.element('<div class=\"btn-group\">');\n            var remove = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" unselectable=\"on\" tabindex=\"-1\"><i class=\"fa fa-trash-o\"></i></button>');\n            remove.on('click', function(event){\n                event.preventDefault();\n                $element.remove();\n                finishEdit();\n            });\n            buttonGroup.append(remove);\n            container.append(buttonGroup);\n\n            editorScope.showPopover($element);\n            editorScope.showResizeOverlay($element);\n        },\n        aOnSelectAction: function(event, $element, editorScope){\n            // setup the editor toolbar\n            // Credit to the work at http://hackerwins.github.io/summernote/ for this editbar logic\n            event.preventDefault();\n            editorScope.displayElements.popover.css('width', '436px');\n            var container = editorScope.displayElements.popoverContainer;\n            container.empty();\n            container.css('line-height', '28px');\n            var link = angular.element('<a href=\"' + $element.attr('href') + '\" target=\"_blank\">' + $element.attr('href') + '</a>');\n            link.css({\n                'display': 'inline-block',\n                'max-width': '200px',\n                'overflow': 'hidden',\n                'text-overflow': 'ellipsis',\n                'white-space': 'nowrap',\n                'vertical-align': 'middle'\n            });\n            container.append(link);\n            var buttonGroup = angular.element('<div class=\"btn-group pull-right\">');\n            var reLinkButton = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" tabindex=\"-1\" unselectable=\"on\" title=\"' + taTranslations.editLink.reLinkButton.tooltip + '\"><i class=\"fa fa-edit icon-edit\"></i></button>');\n            reLinkButton.on('click', function(event){\n                event.preventDefault();\n                var urlLink = $window.prompt(taTranslations.insertLink.dialogPrompt, $element.attr('href'));\n                if(urlLink && urlLink !== '' && urlLink !== 'http://'){\n                    $element.attr('href', urlLink);\n                    editorScope.updateTaBindtaTextElement();\n                }\n                editorScope.hidePopover();\n            });\n            buttonGroup.append(reLinkButton);\n            var unLinkButton = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" tabindex=\"-1\" unselectable=\"on\" title=\"' + taTranslations.editLink.unLinkButton.tooltip + '\"><i class=\"fa fa-unlink icon-unlink\"></i></button>');\n            // directly before this click event is fired a digest is fired off whereby the reference to $element is orphaned off\n            unLinkButton.on('click', function(event){\n                event.preventDefault();\n                $element.replaceWith($element.contents());\n                editorScope.updateTaBindtaTextElement();\n                editorScope.hidePopover();\n            });\n            buttonGroup.append(unLinkButton);\n            var targetToggle = angular.element('<button type=\"button\" class=\"btn btn-default btn-sm btn-small\" tabindex=\"-1\" unselectable=\"on\">' + taTranslations.editLink.targetToggle.buttontext + '</button>');\n            if($element.attr('target') === '_blank'){\n                targetToggle.addClass('active');\n            }\n            targetToggle.on('click', function(event){\n                event.preventDefault();\n                $element.attr('target', ($element.attr('target') === '_blank') ? '' : '_blank');\n                targetToggle.toggleClass('active');\n                editorScope.updateTaBindtaTextElement();\n            });\n            buttonGroup.append(targetToggle);\n            container.append(buttonGroup);\n            editorScope.showPopover($element);\n        },\n        extractYoutubeVideoId: function(url) {\n            var re = /(?:youtube(?:-nocookie)?\\.com\\/(?:[^\\/\\n\\s]+\\/\\S+\\/|(?:v|e(?:mbed)?)\\/|\\S*?[?&]v=)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/i;\n            var match = url.match(re);\n            return (match && match[1]) || null;\n        }\n    };\n}])\n.run(['taRegisterTool', '$window', 'taTranslations', 'taSelection', 'taToolFunctions', '$sanitize', 'taOptions', '$log',\n    function(taRegisterTool, $window, taTranslations, taSelection, taToolFunctions, $sanitize, taOptions, $log){\n    // test for the version of $sanitize that is in use\n    // You can disable this check by setting taOptions.textAngularSanitize == false\n    var gv = {}; $sanitize('', gv);\n    /* istanbul ignore next, throws error */\n    if ((taOptions.forceTextAngularSanitize===true) && (gv.version !== 'taSanitize')) {\n        throw angular.$$minErr('textAngular')(\"textAngularSetup\", \"The textAngular-sanitize provider has been replaced by another -- have you included angular-sanitize by mistake?\");\n    }\n    taRegisterTool(\"html\", {\n        iconclass: 'fa fa-code',\n        tooltiptext: taTranslations.html.tooltip,\n        action: function(){\n            this.$editor().switchView();\n        },\n        activeState: function(){\n            return this.$editor().showHtml;\n        }\n    });\n    // add the Header tools\n    // convenience functions so that the loop works correctly\n    var _retActiveStateFunction = function(q){\n        return function(){ return this.$editor().queryFormatBlockState(q); };\n    };\n    var headerAction = function(){\n        return this.$editor().wrapSelection(\"formatBlock\", \"<\" + this.name.toUpperCase() +\">\");\n    };\n    angular.forEach(['h1','h2','h3','h4','h5','h6'], function(h){\n        taRegisterTool(h.toLowerCase(), {\n            buttontext: h.toUpperCase(),\n            tooltiptext: taTranslations.heading.tooltip + h.charAt(1),\n            action: headerAction,\n            activeState: _retActiveStateFunction(h.toLowerCase())\n        });\n    });\n    taRegisterTool('p', {\n        buttontext: 'P',\n        tooltiptext: taTranslations.p.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"formatBlock\", \"<P>\");\n        },\n        activeState: function(){ return this.$editor().queryFormatBlockState('p'); }\n    });\n    // key: pre -> taTranslations[key].tooltip, taTranslations[key].buttontext\n    taRegisterTool('pre', {\n        buttontext: 'pre',\n        tooltiptext: taTranslations.pre.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"formatBlock\", \"<PRE>\");\n        },\n        activeState: function(){ return this.$editor().queryFormatBlockState('pre'); }\n    });\n    taRegisterTool('ul', {\n        iconclass: 'fa fa-list-ul',\n        tooltiptext: taTranslations.ul.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"insertUnorderedList\", null);\n        },\n        activeState: function(){ return this.$editor().queryCommandState('insertUnorderedList'); }\n    });\n    taRegisterTool('ol', {\n        iconclass: 'fa fa-list-ol',\n        tooltiptext: taTranslations.ol.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"insertOrderedList\", null);\n        },\n        activeState: function(){ return this.$editor().queryCommandState('insertOrderedList'); }\n    });\n    taRegisterTool('quote', {\n        iconclass: 'fa fa-quote-right',\n        tooltiptext: taTranslations.quote.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"formatBlock\", \"<BLOCKQUOTE>\");\n        },\n        activeState: function(){ return this.$editor().queryFormatBlockState('blockquote'); }\n    });\n    taRegisterTool('undo', {\n        iconclass: 'fa fa-undo',\n        tooltiptext: taTranslations.undo.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"undo\", null);\n        }\n    });\n    taRegisterTool('redo', {\n        iconclass: 'fa fa-repeat',\n        tooltiptext: taTranslations.redo.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"redo\", null);\n        }\n    });\n    taRegisterTool('bold', {\n        iconclass: 'fa fa-bold',\n        tooltiptext: taTranslations.bold.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"bold\", null);\n        },\n        activeState: function(){\n            return this.$editor().queryCommandState('bold');\n        },\n        commandKeyCode: 98\n    });\n    taRegisterTool('justifyLeft', {\n        iconclass: 'fa fa-align-left',\n        tooltiptext: taTranslations.justifyLeft.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"justifyLeft\", null);\n        },\n        activeState: function(commonElement){\n            /* istanbul ignore next: */\n            if (commonElement && commonElement.nodeName === '#document') return false;\n            var result = false;\n            if (commonElement) {\n                // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions\n                // so we do try catch here...\n                try {\n                    result =\n                        commonElement.css('text-align') === 'left' ||\n                        commonElement.attr('align') === 'left' ||\n                        (\n                            commonElement.css('text-align') !== 'right' &&\n                            commonElement.css('text-align') !== 'center' &&\n                            commonElement.css('text-align') !== 'justify' && !this.$editor().queryCommandState('justifyRight') && !this.$editor().queryCommandState('justifyCenter')\n                        ) && !this.$editor().queryCommandState('justifyFull');\n                } catch(e) {\n                    /* istanbul ignore next: error handler */\n                    //console.log(e);\n                    result = false;\n                }\n            }\n            result = result || this.$editor().queryCommandState('justifyLeft');\n            return result;\n        }\n    });\n    taRegisterTool('justifyRight', {\n        iconclass: 'fa fa-align-right',\n        tooltiptext: taTranslations.justifyRight.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"justifyRight\", null);\n        },\n        activeState: function(commonElement){\n            /* istanbul ignore next: */\n            if (commonElement && commonElement.nodeName === '#document') return false;\n            var result = false;\n            if(commonElement) {\n                // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions\n                // so we do try catch here...\n                try {\n                    result = commonElement.css('text-align') === 'right';\n                } catch(e) {\n                    /* istanbul ignore next: error handler */\n                    //console.log(e);\n                    result = false;\n                }\n            }\n            result = result || this.$editor().queryCommandState('justifyRight');\n            return result;\n        }\n    });\n    taRegisterTool('justifyFull', {\n        iconclass: 'fa fa-align-justify',\n        tooltiptext: taTranslations.justifyFull.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"justifyFull\", null);\n        },\n        activeState: function(commonElement){\n            var result = false;\n            if(commonElement) {\n                // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions\n                // so we do try catch here...\n                try {\n                    result = commonElement.css('text-align') === 'justify';\n                } catch(e) {\n                    /* istanbul ignore next: error handler */\n                    //console.log(e);\n                    result = false;\n                }\n            }\n            result = result || this.$editor().queryCommandState('justifyFull');\n            return result;\n        }\n    });\n    taRegisterTool('justifyCenter', {\n        iconclass: 'fa fa-align-center',\n        tooltiptext: taTranslations.justifyCenter.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"justifyCenter\", null);\n        },\n        activeState: function(commonElement){\n            /* istanbul ignore next: */\n            if (commonElement && commonElement.nodeName === '#document') return false;\n            var result = false;\n            if(commonElement) {\n                // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions\n                // so we do try catch here...\n                try {\n                    result = commonElement.css('text-align') === 'center';\n                } catch(e) {\n                    /* istanbul ignore next: error handler */\n                    //console.log(e);\n                    result = false;\n                }\n\n            }\n            result = result || this.$editor().queryCommandState('justifyCenter');\n            return result;\n        }\n    });\n    taRegisterTool('indent', {\n        iconclass: 'fa fa-indent',\n        tooltiptext: taTranslations.indent.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"indent\", null);\n        },\n        activeState: function(){\n            return this.$editor().queryFormatBlockState('blockquote');\n        },\n        commandKeyCode: 'TabKey'\n    });\n    taRegisterTool('outdent', {\n        iconclass: 'fa fa-outdent',\n        tooltiptext: taTranslations.outdent.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"outdent\", null);\n        },\n        activeState: function(){\n            return false;\n        },\n        commandKeyCode: 'ShiftTabKey'\n    });\n    taRegisterTool('italics', {\n        iconclass: 'fa fa-italic',\n        tooltiptext: taTranslations.italic.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"italic\", null);\n        },\n        activeState: function(){\n            return this.$editor().queryCommandState('italic');\n        },\n        commandKeyCode: 105\n    });\n    taRegisterTool('underline', {\n        iconclass: 'fa fa-underline',\n        tooltiptext: taTranslations.underline.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"underline\", null);\n        },\n        activeState: function(){\n            return this.$editor().queryCommandState('underline');\n        },\n        commandKeyCode: 117\n    });\n    taRegisterTool('strikeThrough', {\n        iconclass: 'fa fa-strikethrough',\n        tooltiptext: taTranslations.strikeThrough.tooltip,\n        action: function(){\n            return this.$editor().wrapSelection(\"strikeThrough\", null);\n        },\n        activeState: function(){\n            return document.queryCommandState('strikeThrough');\n        }\n    });\n    taRegisterTool('clear', {\n        iconclass: 'fa fa-ban',\n        tooltiptext: taTranslations.clear.tooltip,\n        action: function(deferred, restoreSelection){\n            var i, selectedElements, elementsSeen;\n\n            this.$editor().wrapSelection(\"removeFormat\", null);\n            var possibleNodes = angular.element(taSelection.getSelectionElement());\n            selectedElements = taSelection.getAllSelectedElements();\n            //$log.log('selectedElements:', selectedElements);\n            // remove lists\n            var removeListElements = function(list, pe){\n                list = angular.element(list);\n                var prevElement = pe;\n                if (!pe) {\n                    prevElement = list;\n                }\n                angular.forEach(list.children(), function(liElem){\n                    if (liElem.tagName.toLowerCase() === 'ul' ||\n                        liElem.tagName.toLowerCase() === 'ol') {\n                        prevElement = removeListElements(liElem, prevElement);\n                    } else {\n                        var newElem = angular.element('<p></p>');\n                        newElem.html(angular.element(liElem).html());\n                        prevElement.after(newElem);\n                        prevElement = newElem;\n                    }\n                });\n                list.remove();\n                return prevElement;\n            };\n\n            angular.forEach(selectedElements, function(element) {\n                if (element.nodeName.toLowerCase() === 'ul' ||\n                    element.nodeName.toLowerCase() === 'ol') {\n                    //console.log('removeListElements', element);\n                    removeListElements(element);\n                }\n            });\n\n            angular.forEach(possibleNodes.find(\"ul\"), removeListElements);\n            angular.forEach(possibleNodes.find(\"ol\"), removeListElements);\n\n            // clear out all class attributes. These do not seem to be cleared via removeFormat\n            var $editor = this.$editor();\n            var recursiveRemoveClass = function(node){\n                node = angular.element(node);\n                /* istanbul ignore next: this is not triggered in tests any longer since we now never select the whole displayELement */\n                if(node[0] !== $editor.displayElements.text[0]) {\n                    node.removeAttr('class');\n                }\n                angular.forEach(node.children(), recursiveRemoveClass);\n            };\n            angular.forEach(possibleNodes, recursiveRemoveClass);\n            // check if in list. If not in list then use formatBlock option\n            if(possibleNodes[0] && possibleNodes[0].tagName.toLowerCase() !== 'li' &&\n                possibleNodes[0].tagName.toLowerCase() !== 'ol' &&\n                possibleNodes[0].tagName.toLowerCase() !== 'ul' &&\n                possibleNodes[0].getAttribute(\"contenteditable\") !== \"true\") {\n                this.$editor().wrapSelection(\"formatBlock\", \"default\");\n            }\n            restoreSelection();\n        }\n    });\n\n        /* jshint -W099 */\n    /****************************\n     //  we don't use this code - since the previous way CLEAR is expected to work does not clear partially selected <li>\n\n     var removeListElement = function(listE){\n                console.log(listE);\n                var _list = listE.parentNode.childNodes;\n                console.log('_list', _list);\n                var _preLis = [], _postLis = [], _found = false;\n                for (i = 0; i < _list.length; i++) {\n                    if (_list[i] === listE) {\n                        _found = true;\n                    } else if (!_found) _preLis.push(_list[i]);\n                    else _postLis.push(_list[i]);\n                }\n                var _parent = angular.element(listE.parentNode);\n                var newElem = angular.element('<p></p>');\n                newElem.html(angular.element(listE).html());\n                if (_preLis.length === 0 || _postLis.length === 0) {\n                    if (_postLis.length === 0) _parent.after(newElem);\n                    else _parent[0].parentNode.insertBefore(newElem[0], _parent[0]);\n\n                    if (_preLis.length === 0 && _postLis.length === 0) _parent.remove();\n                    else angular.element(listE).remove();\n                } else {\n                    var _firstList = angular.element('<' + _parent[0].tagName + '></' + _parent[0].tagName + '>');\n                    var _secondList = angular.element('<' + _parent[0].tagName + '></' + _parent[0].tagName + '>');\n                    for (i = 0; i < _preLis.length; i++) _firstList.append(angular.element(_preLis[i]));\n                    for (i = 0; i < _postLis.length; i++) _secondList.append(angular.element(_postLis[i]));\n                    _parent.after(_secondList);\n                    _parent.after(newElem);\n                    _parent.after(_firstList);\n                    _parent.remove();\n                }\n                taSelection.setSelectionToElementEnd(newElem[0]);\n            };\n\n     elementsSeen = [];\n     if (selectedElements.length !==0) console.log(selectedElements);\n     angular.forEach(selectedElements, function (element) {\n                if (elementsSeen.indexOf(element) !== -1 || elementsSeen.indexOf(element.parentElement) !== -1) {\n                    return;\n                }\n                elementsSeen.push(element);\n                if (element.nodeName.toLowerCase() === 'li') {\n                    console.log('removeListElement', element);\n                    removeListElement(element);\n                }\n                else if (element.parentElement && element.parentElement.nodeName.toLowerCase() === 'li') {\n                    console.log('removeListElement', element.parentElement);\n                    elementsSeen.push(element.parentElement);\n                    removeListElement(element.parentElement);\n                }\n            });\n     **********************/\n\n    /**********************\n     if(possibleNodes[0].tagName.toLowerCase() === 'li'){\n                var _list = possibleNodes[0].parentNode.childNodes;\n                var _preLis = [], _postLis = [], _found = false;\n                for(i = 0; i < _list.length; i++){\n                    if(_list[i] === possibleNodes[0]){\n                        _found = true;\n                    }else if(!_found) _preLis.push(_list[i]);\n                    else _postLis.push(_list[i]);\n                }\n                var _parent = angular.element(possibleNodes[0].parentNode);\n                var newElem = angular.element('<p></p>');\n                newElem.html(angular.element(possibleNodes[0]).html());\n                if(_preLis.length === 0 || _postLis.length === 0){\n                    if(_postLis.length === 0) _parent.after(newElem);\n                    else _parent[0].parentNode.insertBefore(newElem[0], _parent[0]);\n\n                    if(_preLis.length === 0 && _postLis.length === 0) _parent.remove();\n                    else angular.element(possibleNodes[0]).remove();\n                }else{\n                    var _firstList = angular.element('<'+_parent[0].tagName+'></'+_parent[0].tagName+'>');\n                    var _secondList = angular.element('<'+_parent[0].tagName+'></'+_parent[0].tagName+'>');\n                    for(i = 0; i < _preLis.length; i++) _firstList.append(angular.element(_preLis[i]));\n                    for(i = 0; i < _postLis.length; i++) _secondList.append(angular.element(_postLis[i]));\n                    _parent.after(_secondList);\n                    _parent.after(newElem);\n                    _parent.after(_firstList);\n                    _parent.remove();\n                }\n                taSelection.setSelectionToElementEnd(newElem[0]);\n            }\n     *******************/\n\n\n    /* istanbul ignore next: if it's javascript don't worry - though probably should show some kind of error message */\n    var blockJavascript = function (link) {\n        if (link.toLowerCase().indexOf('javascript')!==-1) {\n            return true;\n        }\n        return false;\n    };\n\n    taRegisterTool('insertImage', {\n        iconclass: 'fa fa-picture-o',\n        tooltiptext: taTranslations.insertImage.tooltip,\n        action: function(){\n            var imageLink;\n            imageLink = $window.prompt(taTranslations.insertImage.dialogPrompt, 'http://');\n            if(imageLink && imageLink !== '' && imageLink !== 'http://'){\n                /* istanbul ignore next: don't know how to test this... since it needs a dialogPrompt */\n                // block javascript here\n                if (!blockJavascript(imageLink)) {\n                    if (taSelection.getSelectionElement().tagName && taSelection.getSelectionElement().tagName.toLowerCase() === 'a') {\n                        // due to differences in implementation between FireFox and Chrome, we must move the\n                        // insertion point past the <a> element, otherwise FireFox inserts inside the <a>\n                        // With this change, both FireFox and Chrome behave the same way!\n                        taSelection.setSelectionAfterElement(taSelection.getSelectionElement());\n                    }\n                    // In the past we used the simple statement:\n                    //return this.$editor().wrapSelection('insertImage', imageLink, true);\n                    //\n                    // However on Firefox only, when the content is empty this is a problem\n                    // See Issue #1201\n                    // Investigation reveals that Firefox only inserts a <p> only!!!!\n                    // So now we use insertHTML here and all is fine.\n                    // NOTE: this is what 'insertImage' is supposed to do anyway!\n                    var embed = '<img src=\"' + imageLink + '\">';\n                    return this.$editor().wrapSelection('insertHTML', embed, true);\n                }\n            }\n        },\n        onElementSelect: {\n            element: 'img',\n            action: taToolFunctions.imgOnSelectAction\n        }\n    });\n    taRegisterTool('insertVideo', {\n        iconclass: 'fa fa-youtube-play',\n        tooltiptext: taTranslations.insertVideo.tooltip,\n        action: function(){\n            var urlPrompt;\n            urlPrompt = $window.prompt(taTranslations.insertVideo.dialogPrompt, 'https://');\n            // block javascript here\n            /* istanbul ignore else: if it's javascript don't worry - though probably should show some kind of error message */\n            if (!blockJavascript(urlPrompt)) {\n\n                if (urlPrompt && urlPrompt !== '' && urlPrompt !== 'https://') {\n\n                    videoId = taToolFunctions.extractYoutubeVideoId(urlPrompt);\n\n                    /* istanbul ignore else: if it's invalid don't worry - though probably should show some kind of error message */\n                    if (videoId) {\n                        // create the embed link\n                        var urlLink = \"https://www.youtube.com/embed/\" + videoId;\n                        // create the HTML\n                        // for all options see: http://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api\n                        // maxresdefault.jpg seems to be undefined on some.\n                        var embed = '<img class=\"ta-insert-video\" src=\"https://img.youtube.com/vi/' + videoId + '/hqdefault.jpg\" ta-insert-video=\"' + urlLink + '\" contenteditable=\"false\" allowfullscreen=\"true\" frameborder=\"0\" />';\n                        /* istanbul ignore next: don't know how to test this... since it needs a dialogPrompt */\n                        if (taSelection.getSelectionElement().tagName && taSelection.getSelectionElement().tagName.toLowerCase() === 'a') {\n                            // due to differences in implementation between FireFox and Chrome, we must move the\n                            // insertion point past the <a> element, otherwise FireFox inserts inside the <a>\n                            // With this change, both FireFox and Chrome behave the same way!\n                            taSelection.setSelectionAfterElement(taSelection.getSelectionElement());\n                        }\n                        // insert\n                        return this.$editor().wrapSelection('insertHTML', embed, true);\n                    }\n                }\n            }\n        },\n        onElementSelect: {\n            element: 'img',\n            onlyWithAttrs: ['ta-insert-video'],\n            action: taToolFunctions.imgOnSelectAction\n        }\n    });\n    taRegisterTool('insertLink', {\n        tooltiptext: taTranslations.insertLink.tooltip,\n        iconclass: 'fa fa-link',\n        action: function(){\n            var urlLink;\n            // if this link has already been set, we need to just edit the existing link\n            /* istanbul ignore if: we do not test this */\n            if (taSelection.getSelectionElement().tagName && taSelection.getSelectionElement().tagName.toLowerCase() === 'a') {\n                urlLink = $window.prompt(taTranslations.insertLink.dialogPrompt, taSelection.getSelectionElement().href);\n            } else {\n                urlLink = $window.prompt(taTranslations.insertLink.dialogPrompt, 'http://');\n            }\n            if(urlLink && urlLink !== '' && urlLink !== 'http://'){\n                // block javascript here\n                /* istanbul ignore else: if it's javascript don't worry - though probably should show some kind of error message */\n                if (!blockJavascript(urlLink)) {\n                    return this.$editor().wrapSelection('createLink', urlLink, true);\n                }\n            }\n        },\n        activeState: function(commonElement){\n            if(commonElement) return commonElement[0].tagName === 'A';\n            return false;\n        },\n        onElementSelect: {\n            element: 'a',\n            action: taToolFunctions.aOnSelectAction\n        }\n    });\n    taRegisterTool('wordcount', {\n        display: '<div id=\"toolbarWC\" style=\"display:block; min-width:100px;\">Words: <span ng-bind=\"wordcount\"></span></div>',\n        disabled: true,\n        wordcount: 0,\n        activeState: function(){ // this fires on keyup\n            var textElement = this.$editor().displayElements.text;\n            /* istanbul ignore next: will default to '' when undefined */\n            var workingHTML = textElement[0].innerHTML || '';\n            var noOfWords = 0;\n\n            /* istanbul ignore if: will default to '' when undefined */\n            if (workingHTML.replace(/\\s*<[^>]*?>\\s*/g, '') !== '') {\n                if (workingHTML.trim() !== '') {\n                    noOfWords = workingHTML.replace(/<\\/?(b|i|em|strong|span|u|strikethrough|a|img|small|sub|sup|label)( [^>*?])?>/gi, '') // remove inline tags without adding spaces\n                        .replace(/(<[^>]*?>\\s*<[^>]*?>)/ig, ' ') // replace adjacent tags with possible space between with a space\n                        .replace(/(<[^>]*?>)/ig, '') // remove any singular tags\n                        .replace(/\\s+/ig, ' ') // condense spacing\n                        .match(/\\S+/g).length; // count remaining non-space strings\n                }\n            }\n\n            //Set current scope\n            this.wordcount = noOfWords;\n            //Set editor scope\n            this.$editor().wordcount = noOfWords;\n\n            return false;\n        }\n    });\n    taRegisterTool('charcount', {\n        display: '<div id=\"toolbarCC\" style=\"display:block; min-width:120px;\">Characters: <span ng-bind=\"charcount\"></span></div>',\n        disabled: true,\n        charcount: 0,\n        activeState: function(){ // this fires on keyup\n            var textElement = this.$editor().displayElements.text;\n            var sourceText = textElement[0].innerText || textElement[0].textContent; // to cover the non-jquery use case.\n\n            // Caculate number of chars\n            var noOfChars = sourceText.replace(/(\\r\\n|\\n|\\r)/gm,\"\").replace(/^\\s+/g,' ').replace(/\\s+$/g, ' ').length;\n            //Set current scope\n            this.charcount = noOfChars;\n            //Set editor scope\n            this.$editor().charcount = noOfChars;\n            return false;\n        }\n    });\n}]);\n\n/*\n@license textAngular\nAuthor : Austin Anderson\nLicense : 2013 MIT\nVersion 1.5.16\n\nSee README.md or https://github.com/fraywing/textAngular/wiki for requirements and use.\n*/\n\n/*\nCommonjs package manager support (eg componentjs).\n*/\n\n\n\"use strict\";// NOTE: textAngularVersion must match the Gruntfile.js 'setVersion' task.... and have format v/d+./d+./d+\nvar textAngularVersion = 'v1.5.16';   // This is automatically updated during the build process to the current release!\n\n\n// IE version detection - http://stackoverflow.com/questions/4169160/javascript-ie-detection-why-not-use-simple-conditional-comments\n// We need this as IE sometimes plays funny tricks with the contenteditable.\n// ----------------------------------------------------------\n// If you're not in IE (or IE version is less than 5) then:\n// ie === undefined\n// If you're in IE (>=5) then you can determine which version:\n// ie === 7; // IE7\n// Thus, to detect IE:\n// if (ie) {}\n// And to detect the version:\n// ie === 6 // IE6\n// ie > 7 // IE8, IE9, IE10 ...\n// ie < 9 // Anything less than IE9\n// ----------------------------------------------------------\n/* istanbul ignore next: untestable browser check */\nvar _browserDetect = {\n\tie: (function(){\n\t\tvar undef,\n\t\t\tv = 3,\n\t\t\tdiv = document.createElement('div'),\n\t\t\tall = div.getElementsByTagName('i');\n\n\t\twhile (\n\t\t\tdiv.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',\n\t\t\tall[0]\n\t\t);\n\n\t\treturn v > 4 ? v : undef;\n\t}()),\n\twebkit: /AppleWebKit\\/([\\d.]+)/i.test(navigator.userAgent),\n\tisFirefox: navigator.userAgent.toLowerCase().indexOf('firefox') > -1\n};\n\n// Global to textAngular to measure performance where needed\n/* istanbul ignore next: untestable browser check */\nvar performance = performance || {};\n/* istanbul ignore next: untestable browser check */\nperformance.now = (function() {\n\treturn performance.now       ||\n\t\tperformance.mozNow    ||\n\t\tperformance.msNow     ||\n\t\tperformance.oNow      ||\n\t\tperformance.webkitNow ||\n\t\tfunction() { return new Date().getTime(); };\n})();\n// usage is:\n// var t0 = performance.now();\n// doSomething();\n// var t1 = performance.now();\n// console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to do something!');\n//\n\n// turn html into pure text that shows visiblity\nfunction stripHtmlToText(html)\n{\n\tvar tmp = document.createElement(\"DIV\");\n\ttmp.innerHTML = html;\n\tvar res = tmp.textContent || tmp.innerText || '';\n\tres.replace('\\u200B', ''); // zero width space\n\tres = res.trim();\n\treturn res;\n}\n// get html\nfunction getDomFromHtml(html)\n{\n\tvar tmp = document.createElement(\"DIV\");\n\ttmp.innerHTML = html;\n\treturn tmp;\n}\n\n\n// Global to textAngular REGEXP vars for block and list elements.\n\nvar BLOCKELEMENTS = /^(address|article|aside|audio|blockquote|canvas|center|dd|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|noscript|ol|output|p|pre|section|table|tfoot|ul|video)$/i;\nvar LISTELEMENTS = /^(ul|li|ol)$/i;\n// updated VALIDELEMENTS to include #text and span so that we can use nodeName instead of tagName\nvar VALIDELEMENTS = /^(#text|span|address|article|aside|audio|blockquote|canvas|center|dd|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|noscript|ol|output|p|pre|section|table|tfoot|ul|video|li)$/i;\n\n\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Compatibility\n/* istanbul ignore next: trim shim for older browsers */\nif (!String.prototype.trim) {\n\tString.prototype.trim = function () {\n\t\treturn this.replace(/^\\s+|\\s+$/g, '');\n\t};\n}\n\n/*\n\tCustom stylesheet for the placeholders rules.\n\tCredit to: http://davidwalsh.name/add-rules-stylesheets\n*/\nvar sheet, addCSSRule, removeCSSRule, _addCSSRule, _removeCSSRule, _getRuleIndex;\n/* istanbul ignore else: IE <8 test*/\nif(_browserDetect.ie > 8 || _browserDetect.ie === undefined){\n\tvar _sheets = document.styleSheets;\n\t/* istanbul ignore next: preference for stylesheet loaded externally */\n\tfor(var i = 0; i < _sheets.length; i++){\n\t\tif(_sheets[i].media.length === 0 || _sheets[i].media.mediaText.match(/(all|screen)/ig)){\n\t\t\tif(_sheets[i].href){\n\t\t\t\tif(_sheets[i].href.match(/textangular\\.(min\\.|)css/ig)){\n\t\t\t\t\tsheet = _sheets[i];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t/* istanbul ignore next: preference for stylesheet loaded externally */\n\tif(!sheet){\n\t\t// this sheet is used for the placeholders later on.\n\t\tsheet = (function() {\n\t\t\t// Create the <style> tag\n\t\t\tvar style = document.createElement(\"style\");\n\t\t\t/* istanbul ignore else : WebKit hack :( */\n\t\t\tif(_browserDetect.webkit) style.appendChild(document.createTextNode(\"\"));\n\n\t\t\t// Add the <style> element to the page, add as first so the styles can be overridden by custom stylesheets\n\t\t\tdocument.getElementsByTagName('head')[0].appendChild(style);\n\n\t\t\treturn style.sheet;\n\t\t})();\n\t}\n\n\t// use as: addCSSRule(\"header\", \"float: left\");\n\taddCSSRule = function(selector, rules) {\n\t\treturn _addCSSRule(sheet, selector, rules);\n\t};\n\t_addCSSRule = function(_sheet, selector, rules){\n\t\tvar insertIndex;\n\t\tvar insertedRule;\n\t\t// This order is important as IE 11 has both cssRules and rules but they have different lengths - cssRules is correct, rules gives an error in IE 11\n\t\t/* istanbul ignore next: browser catches */\n\t\tif(_sheet.cssRules) insertIndex = Math.max(_sheet.cssRules.length - 1, 0);\n\t\telse if(_sheet.rules) insertIndex = Math.max(_sheet.rules.length - 1, 0);\n\n\t\t/* istanbul ignore else: untestable IE option */\n\t\tif(_sheet.insertRule) {\n\t\t\t_sheet.insertRule(selector + \"{\" + rules + \"}\", insertIndex);\n\t\t}\n\t\telse {\n\t\t\t_sheet.addRule(selector, rules, insertIndex);\n\t\t}\n\t\t/* istanbul ignore next: browser catches */\n\t\tif(sheet.rules) insertedRule = sheet.rules[insertIndex];\n\t\telse if(sheet.cssRules) insertedRule = sheet.cssRules[insertIndex];\n\t\t// return the inserted stylesheet rule\n\t\treturn insertedRule;\n\t};\n\n\t_getRuleIndex = function(rule, rules) {\n\t\tvar i, ruleIndex;\n\t\tfor (i=0; i < rules.length; i++) {\n\t\t\t/* istanbul ignore else: check for correct rule */\n\t\t\tif (rules[i].cssText === rule.cssText) {\n\t\t\t\truleIndex = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn ruleIndex;\n\t};\n\n\tremoveCSSRule = function(rule){\n\t\t_removeCSSRule(sheet, rule);\n\t};\n\t/* istanbul ignore next: tests are browser specific */\n\t_removeCSSRule = function(sheet, rule){\n\t\tvar rules = sheet.cssRules || sheet.rules;\n\t\tif(!rules || rules.length === 0) return;\n\t\tvar ruleIndex = _getRuleIndex(rule, rules);\n\t\tif(sheet.removeRule){\n\t\t\tsheet.removeRule(ruleIndex);\n\t\t}else{\n\t\t\tsheet.deleteRule(ruleIndex);\n\t\t}\n\t};\n}\n\nangular.module('textAngular.factories', [])\n.factory('taBrowserTag', [function(){\n    return function(tag){\n        /* istanbul ignore next: ie specific test */\n        if(!tag) return (_browserDetect.ie <= 8)? 'P' : 'p';\n        else if(tag === '') return (_browserDetect.ie === undefined)? 'div' : (_browserDetect.ie <= 8)? 'P' : 'p';\n        else return (_browserDetect.ie <= 8)? tag.toUpperCase() : tag;\n    };\n}]).factory('taApplyCustomRenderers', ['taCustomRenderers', 'taDOM', function(taCustomRenderers, taDOM){\n    return function(val){\n        var element = angular.element('<div></div>');\n        element[0].innerHTML = val;\n\n        angular.forEach(taCustomRenderers, function(renderer){\n            var elements = [];\n            // get elements based on what is defined. If both defined do secondary filter in the forEach after using selector string\n            if(renderer.selector && renderer.selector !== '')\n                elements = element.find(renderer.selector);\n            /* istanbul ignore else: shouldn't fire, if it does we're ignoring everything */\n            else if(renderer.customAttribute && renderer.customAttribute !== '')\n                elements = taDOM.getByAttribute(element, renderer.customAttribute);\n            // process elements if any found\n            angular.forEach(elements, function(_element){\n                _element = angular.element(_element);\n                if(renderer.selector && renderer.selector !== '' && renderer.customAttribute && renderer.customAttribute !== ''){\n                    if(_element.attr(renderer.customAttribute) !== undefined) renderer.renderLogic(_element);\n                } else renderer.renderLogic(_element);\n            });\n        });\n\n        return element[0].innerHTML;\n    };\n}]).factory('taFixChrome', function(){\n    // get whaterever rubbish is inserted in chrome\n    // should be passed an html string, returns an html string\n    var taFixChrome = function(html, keepStyles){\n        if(!html || !angular.isString(html) || html.length <= 0) return html;\n        // grab all elements with a style attibute\n        // a betterSpanMatch matches only a style=... with matching quotes\n        // this captures the whole:\n        // 'style=\"background-color: rgb(255, 255, 255);\"'\n        var betterSpanMatch = /style\\s?=\\s?([\"'])(?:(?=(\\\\?))\\2.)*?\\1/ig;\n        // where the original spanMatch = /<([^>\\/]+?)style=(\"([^\\\"]+)\"|'([^']+)')([^>]*)>/ig;\n        // captures too much and includes the front tag!\n        var spanMatch = /<([^>\\/]+?)style=(\"([^\\\"]+)\"|'([^']+)')([^>]*)>/ig;\n        var appleConvertedSpaceMatch = /<span class=\"Apple-converted-space\">([^<]+)<\\/span>/ig;\n        var match, styleVal, appleSpaceVal, newTag, finalHtml = '', lastIndex = 0;\n        // remove all the Apple-converted-space spans and replace with the content of the span\n        //console.log('before:', html);\n        /* istanbul ignore next: apple-contereted-space span match */\n        while(match = appleConvertedSpaceMatch.exec(html)){\n            appleSpaceVal = match[1];\n            appleSpaceVal = appleSpaceVal.replace(/&nbsp;/ig, ' ');\n            finalHtml += html.substring(lastIndex, match.index) + appleSpaceVal;\n            lastIndex = match.index + match[0].length;\n        }\n        /* istanbul ignore next: apple-contereted-space span has matched */\n        if (lastIndex) {\n            // modified....\n            finalHtml += html.substring(lastIndex);\n            html=finalHtml;\n            finalHtml='';\n            lastIndex=0;\n        }\n        /////////////////////////////////////////////////////////////\n        //\n        // Allow control of this modification\n        // taKeepStyles: False - removes these modification\n        //\n        // taFixChrome removes the following styles:\n        //    font-family: inherit;\n        //    line-height: <number>\n        //    color: inherit;\n        //    color: rgb( <rgb-component>#{3} )\n        //    background-color: rgb( <rgb-component>#{3} )\n        //\n        /////////////////////////////////////////////////////////////\n        if (!keepStyles) {\n            while (match = betterSpanMatch.exec(html)) {\n                finalHtml += html.substring(lastIndex, match.index-1);\n                styleVal = match[0];\n                // test for chrome inserted junk\n                match = /font-family: inherit;|line-height: 1.[0-9]{3,12};|color: inherit; line-height: 1.1;|color: rgb\\(\\d{1,3}, \\d{1,3}, \\d{1,3}\\);|background-color: rgb\\(\\d{1,3}, \\d{1,3}, \\d{1,3}\\);/gi.exec(styleVal);\n                if (match) {\n                    styleVal = styleVal.replace(/( |)font-family: inherit;|( |)line-height: 1.[0-9]{3,12};|( |)color: inherit;|( |)color: rgb\\(\\d{1,3}, \\d{1,3}, \\d{1,3}\\);|( |)background-color: rgb\\(\\d{1,3}, \\d{1,3}, \\d{1,3}\\);/ig, '');\n                    //console.log(styleVal, styleVal.length);\n                    if (styleVal.length > 8) {\n                        finalHtml += ' ' + styleVal;\n                    }\n                } else {\n                    finalHtml += ' ' + styleVal;\n                }\n                lastIndex = betterSpanMatch.lastIndex;\n            }\n            finalHtml += html.substring(lastIndex);\n        }\n        //console.log('final:', finalHtml);\n        // only replace when something has changed, else we get focus problems on inserting lists\n        if(lastIndex > 0){\n            // replace all empty strings\n            var fe = finalHtml.replace(/<span\\s?>(.*?)<\\/span>(<br(\\/|)>|)/ig, '$1');\n            return fe;\n        } else return html;\n    };\n    return taFixChrome;\n}).factory('taSanitize', ['$sanitize', function taSanitizeFactory($sanitize){\n\n    var convert_infos = [\n        {\n            property: 'font-weight',\n            values: [ 'bold' ],\n            tag: 'b'\n        },\n        {\n            property: 'font-style',\n            values: [ 'italic' ],\n            tag: 'i'\n        }\n    ];\n\n    var styleMatch = [];\n    for(var i = 0; i < convert_infos.length; i++){\n        var _partialStyle = '(' + convert_infos[i].property + ':\\\\s*(';\n        for(var j = 0; j < convert_infos[i].values.length; j++){\n            /* istanbul ignore next: not needed to be tested yet */\n            if(j > 0) _partialStyle += '|';\n            _partialStyle += convert_infos[i].values[j];\n        }\n        _partialStyle += ');)';\n        styleMatch.push(_partialStyle);\n    }\n    var styleRegexString = '(' + styleMatch.join('|') + ')';\n\n    function wrapNested(html, wrapTag) {\n        var depth = 0;\n        var lastIndex = 0;\n        var match;\n        var tagRegex = /<[^>]*>/ig;\n        while(match = tagRegex.exec(html)){\n            lastIndex = match.index;\n            if(match[0].substr(1, 1) === '/'){\n                if(depth === 0) break;\n                else depth--;\n            }else depth++;\n        }\n        return wrapTag +\n            html.substring(0, lastIndex) +\n            // get the start tags reversed - this is safe as we construct the strings with no content except the tags\n            angular.element(wrapTag)[0].outerHTML.substring(wrapTag.length) +\n            html.substring(lastIndex);\n    }\n\n    function transformLegacyStyles(html){\n        if(!html || !angular.isString(html) || html.length <= 0) return html;\n        var i;\n        var styleElementMatch = /<([^>\\/]+?)style=(\"([^\"]+)\"|'([^']+)')([^>]*)>/ig;\n        var match, subMatch, styleVal, newTag, lastNewTag = '', newHtml, finalHtml = '', lastIndex = 0;\n        while(match = styleElementMatch.exec(html)){\n            // one of the quoted values ' or \"\n            /* istanbul ignore next: quotations match */\n            styleVal = match[3] || match[4];\n            var styleRegex = new RegExp(styleRegexString, 'i');\n            // test for style values to change\n            if(angular.isString(styleVal) && styleRegex.test(styleVal)){\n                // remove build tag list\n                newTag = '';\n                // init regex here for exec\n                var styleRegexExec = new RegExp(styleRegexString, 'ig');\n                // find relevand tags and build a string of them\n                while(subMatch = styleRegexExec.exec(styleVal)){\n                    for(i = 0; i < convert_infos.length; i++){\n                        if(!!subMatch[(i*2) + 2]){\n                            newTag += '<' + convert_infos[i].tag + '>';\n                        }\n                    }\n                }\n                // recursively find more legacy styles in html before this tag and after the previous match (if any)\n                newHtml = transformLegacyStyles(html.substring(lastIndex, match.index));\n                // build up html\n                if(lastNewTag.length > 0){\n                    finalHtml += wrapNested(newHtml, lastNewTag);\n                }else finalHtml += newHtml;\n                // grab the style val without the transformed values\n                styleVal = styleVal.replace(new RegExp(styleRegexString, 'ig'), '');\n                // build the html tag\n                finalHtml += '<' + match[1].trim();\n                if(styleVal.length > 0) finalHtml += ' style=\"' + styleVal + '\"';\n                finalHtml += match[5] + '>';\n                // update the start index to after this tag\n                lastIndex = match.index + match[0].length;\n                lastNewTag = newTag;\n            }\n        }\n        if(lastNewTag.length > 0){\n            finalHtml += wrapNested(html.substring(lastIndex), lastNewTag);\n        }\n        else finalHtml += html.substring(lastIndex);\n        return finalHtml;\n    }\n\n    function transformLegacyAttributes(html){\n        if(!html || !angular.isString(html) || html.length <= 0) return html;\n        // replace all align='...' tags with text-align attributes\n        var attrElementMatch = /<([^>\\/]+?)align=(\"([^\"]+)\"|'([^']+)')([^>]*)>/ig;\n        var match, finalHtml = '', lastIndex = 0;\n        // match all attr tags\n        while(match = attrElementMatch.exec(html)){\n            // add all html before this tag\n            finalHtml += html.substring(lastIndex, match.index);\n            // record last index after this tag\n            lastIndex = match.index + match[0].length;\n            // construct tag without the align attribute\n            var newTag = '<' + match[1] + match[5];\n            // add the style attribute\n            if(/style=(\"([^\"]+)\"|'([^']+)')/ig.test(newTag)){\n                /* istanbul ignore next: quotations match */\n                newTag = newTag.replace(/style=(\"([^\"]+)\"|'([^']+)')/i, 'style=\"$2$3 text-align:' + (match[3] || match[4]) + ';\"');\n            }else{\n                /* istanbul ignore next: quotations match */\n                newTag += ' style=\"text-align:' + (match[3] || match[4]) + ';\"';\n            }\n            newTag += '>';\n            // add to html\n            finalHtml += newTag;\n        }\n        // return with remaining html\n        return finalHtml + html.substring(lastIndex);\n    }\n\n    // use precompiled regexp for speed\n    var rsb1 = new RegExp(/<span id=\"selectionBoundary_\\d+_\\d+\" class=\"rangySelectionBoundary\">[^<>]+?<\\/span>/ig);\n    var rsb2 = new RegExp(/<span class=\"rangySelectionBoundary\" id=\"selectionBoundary_\\d+_\\d+\">[^<>]+?<\\/span>/ig);\n    var rsb3 = new RegExp(/<span id=\"selectionBoundary_\\d+_\\d+\" class=\"rangySelectionBoundary\">[^<>]+?<\\/span>/ig);\n\n    return function taSanitize(unsafe, oldsafe, ignore){\n        // unsafe html should NEVER built into a DOM object via angular.element. This allows XSS to be inserted and run.\n        if ( !ignore ) {\n            try {\n                unsafe = transformLegacyStyles(unsafe);\n            } catch (e) {\n            }\n        }\n\n        // unsafe and oldsafe should be valid HTML strings\n        // any exceptions (lets say, color for example) should be made here but with great care\n        // setup unsafe element for modification\n        unsafe = transformLegacyAttributes(unsafe);\n\n        // we had an issue in the past, where we dumped a whole bunch of <span>'s into the content...\n        // so we remove them here\n        // IN A FUTURE release this can be removed after all have updated through release 1.5.9\n        if (unsafe) {\n            try {\n                unsafe = unsafe.replace(rsb1, '');\n                unsafe = unsafe.replace(rsb2, '');\n                unsafe = unsafe.replace(rsb1, '');\n                unsafe = unsafe.replace(rsb3, '');\n            } catch (e) {\n            }\n        }\n\n        var safe;\n        try {\n            safe = $sanitize(unsafe);\n            // do this afterwards, then the $sanitizer should still throw for bad markup\n            if(ignore) safe = unsafe;\n        } catch (e){\n            safe = oldsafe || '';\n        }\n\n        // Do processing for <pre> tags, removing tabs and return carriages outside of them\n\n        var _preTags = safe.match(/(<pre[^>]*>.*?<\\/pre[^>]*>)/ig);\n        var processedSafe = safe.replace(/(&#(9|10);)*/ig, '');\n        var re = /<pre[^>]*>.*?<\\/pre[^>]*>/ig;\n        var index = 0;\n        var lastIndex = 0;\n        var origTag;\n        safe = '';\n        while((origTag = re.exec(processedSafe)) !== null && index < _preTags.length){\n            safe += processedSafe.substring(lastIndex, origTag.index) + _preTags[index];\n            lastIndex = origTag.index + origTag[0].length;\n            index++;\n        }\n        return safe + processedSafe.substring(lastIndex);\n    };\n}]).factory('taToolExecuteAction', ['$q', '$log', function($q, $log){\n    // this must be called on a toolScope or instance\n    return function(editor){\n        if(editor !== undefined) this.$editor = function(){ return editor; };\n        var deferred = $q.defer(),\n            promise = deferred.promise,\n            _editor = this.$editor();\n        // pass into the action the deferred function and also the function to reload the current selection if rangy available\n        var result;\n        try{\n            result = this.action(deferred, _editor.startAction());\n            // We set the .finally callback here to make sure it doesn't get executed before any other .then callback.\n            promise['finally'](function(){\n                _editor.endAction.call(_editor);\n            });\n        }catch(exc){\n            $log.error(exc);\n        }\n        if(result || result === undefined){\n            // if true or undefined is returned then the action has finished. Otherwise the deferred action will be resolved manually.\n            deferred.resolve();\n        }\n    };\n}]);\n\nangular.module('textAngular.DOM', ['textAngular.factories'])\n.factory('taExecCommand', ['taSelection', 'taBrowserTag', '$document', function(taSelection, taBrowserTag, $document){\n    var listToDefault = function(listElement, defaultWrap){\n        var $target, i;\n        // if all selected then we should remove the list\n        // grab all li elements and convert to taDefaultWrap tags\n        var children = listElement.find('li');\n        for(i = children.length - 1; i >= 0; i--){\n            $target = angular.element('<' + defaultWrap + '>' + children[i].innerHTML + '</' + defaultWrap + '>');\n            listElement.after($target);\n        }\n        listElement.remove();\n        taSelection.setSelectionToElementEnd($target[0]);\n    };\n    var listElementToSelfTag = function(list, listElement, selfTag, bDefault, defaultWrap){\n        var $target, i;\n        // if all selected then we should remove the list\n        // grab all li elements\n        var priorElement;\n        var nextElement;\n        var children = list.find('li');\n        var foundIndex;\n        for (i = 0; i<children.length; i++) {\n            if (children[i].outerHTML === listElement[0].outerHTML) {\n                // found it...\n                foundIndex = i;\n                if (i>0) {\n                    priorElement = children[i-1];\n                }\n                if (i+1<children.length) {\n                    nextElement = children[i+1];\n                }\n                break;\n            }\n        }\n        //console.log('listElementToSelfTag', list, listElement, selfTag, bDefault, priorElement, nextElement);\n        // un-list the listElement\n        var html = '';\n        if (bDefault) {\n            html += '<' + defaultWrap + '>' + listElement[0].innerHTML + '</' + defaultWrap + '>';\n        } else {\n            html += '<' + taBrowserTag(selfTag) + '>';\n            html += '<li>' + listElement[0].innerHTML + '</li>';\n            html += '</' + taBrowserTag(selfTag) + '>';\n        }\n        $target = angular.element(html);\n        //console.log('$target', $target[0]);\n        if (!priorElement) {\n            // this is the first the list, so we just remove it...\n            listElement.remove();\n            list.after(angular.element(list[0].outerHTML));\n            list.after($target);\n            list.remove();\n            taSelection.setSelectionToElementEnd($target[0]);\n            return;\n        } else if (!nextElement) {\n            // this is the last in the list, so we just remove it..\n            listElement.remove();\n            list.after($target);\n            taSelection.setSelectionToElementEnd($target[0]);\n        } else {\n            var p = list.parent();\n            // okay it was some where in the middle... so we need to break apart the list...\n            var html1 = '';\n            var listTag = list[0].nodeName.toLowerCase();\n            html1 += '<' + listTag + '>';\n            for(i = 0; i < foundIndex; i++){\n                html1 += '<li>' + children[i].innerHTML + '</li>';\n            }\n            html1 += '</' + listTag + '>';\n            var html2 = '';\n            html2 += '<' + listTag + '>';\n            for(i = foundIndex+1; i < children.length; i++){\n                html2 += '<li>' + children[i].innerHTML + '</li>';\n            }\n            html2 += '</' + listTag + '>';\n            //console.log(html1, $target[0], html2);\n            list.after(angular.element(html2));\n            list.after($target);\n            list.after(angular.element(html1));\n            list.remove();\n            //console.log('parent ******XXX*****', p[0]);\n            taSelection.setSelectionToElementEnd($target[0]);\n        }\n    };\n    var listElementsToSelfTag = function(list, listElements, selfTag, bDefault, defaultWrap){\n        var $target, i, j, p;\n        // grab all li elements\n        var priorElement;\n        var afterElement;\n        //console.log('list:', list, 'listElements:', listElements, 'selfTag:', selfTag, 'bDefault:', bDefault);\n        var children = list.find('li');\n        var foundIndexes = [];\n        for (i = 0; i<children.length; i++) {\n            for (j = 0; j<listElements.length; j++) {\n                if (children[i].isEqualNode(listElements[j])) {\n                    // found it...\n                    foundIndexes[j] = i;\n                }\n            }\n        }\n        if (foundIndexes[0] > 0) {\n            priorElement = children[foundIndexes[0] - 1];\n        }\n        if (foundIndexes[listElements.length-1] + 1 < children.length) {\n            afterElement = children[foundIndexes[listElements.length-1] + 1];\n        }\n        //console.log('listElementsToSelfTag', list, listElements, selfTag, bDefault, !priorElement, !afterElement, foundIndexes[listElements.length-1], children.length);\n        // un-list the listElements\n        var html = '';\n        if (bDefault) {\n            for (j = 0; j < listElements.length; j++) {\n                html += '<' + defaultWrap + '>' + listElements[j].innerHTML + '</' + defaultWrap + '>';\n                listElements[j].remove();\n            }\n        } else {\n            html += '<' + taBrowserTag(selfTag) + '>';\n            for (j = 0; j < listElements.length; j++) {\n                html += listElements[j].outerHTML;\n                listElements[j].remove();\n            }\n            html += '</' + taBrowserTag(selfTag) + '>';\n        }\n        $target = angular.element(html);\n        if (!priorElement) {\n            // this is the first the list, so we just remove it...\n            list.after(angular.element(list[0].outerHTML));\n            list.after($target);\n            list.remove();\n            taSelection.setSelectionToElementEnd($target[0]);\n            return;\n        } else if (!afterElement) {\n            // this is the last in the list, so we just remove it..\n            list.after($target);\n            taSelection.setSelectionToElementEnd($target[0]);\n            return;\n        } else {\n            // okay it was some where in the middle... so we need to break apart the list...\n            var html1 = '';\n            var listTag = list[0].nodeName.toLowerCase();\n            html1 += '<' + listTag + '>';\n            for(i = 0; i < foundIndexes[0]; i++){\n                html1 += '<li>' + children[i].innerHTML + '</li>';\n            }\n            html1 += '</' + listTag + '>';\n            var html2 = '';\n            html2 += '<' + listTag + '>';\n            for(i = foundIndexes[listElements.length-1]+1; i < children.length; i++){\n                html2 += '<li>' + children[i].innerHTML + '</li>';\n            }\n            html2 += '</' + listTag + '>';\n            list.after(angular.element(html2));\n            list.after($target);\n            list.after(angular.element(html1));\n            list.remove();\n            //console.log('parent ******YYY*****', list.parent()[0]);\n            taSelection.setSelectionToElementEnd($target[0]);\n        }\n    };\n    var selectLi = function(liElement){\n        if(/(<br(|\\/)>)$/i.test(liElement.innerHTML.trim())) taSelection.setSelectionBeforeElement(angular.element(liElement).find(\"br\")[0]);\n        else taSelection.setSelectionToElementEnd(liElement);\n    };\n    var listToList = function(listElement, newListTag){\n        var $target = angular.element('<' + newListTag + '>' + listElement[0].innerHTML + '</' + newListTag + '>');\n        listElement.after($target);\n        listElement.remove();\n        selectLi($target.find('li')[0]);\n    };\n    var childElementsToList = function(elements, listElement, newListTag){\n        var html = '';\n        for(var i = 0; i < elements.length; i++){\n            html += '<' + taBrowserTag('li') + '>' + elements[i].innerHTML + '</' + taBrowserTag('li') + '>';\n        }\n        var $target = angular.element('<' + newListTag + '>' + html + '</' + newListTag + '>');\n        listElement.after($target);\n        listElement.remove();\n        selectLi($target.find('li')[0]);\n    };\n    var turnBlockIntoBlocks = function(element, options) {\n        for(var i = 0; i<element.childNodes.length; i++) {\n            var _n = element.childNodes[i];\n            /* istanbul ignore next - more complex testing*/\n            if (_n.tagName && _n.tagName.match(BLOCKELEMENTS)) {\n                turnBlockIntoBlocks(_n, options);\n            }\n        }\n        /* istanbul ignore next - very rare condition that we do not test*/\n        if (element.parentNode === null) {\n            // nothing left to do..\n            return element;\n        }\n        /* istanbul ignore next - not sure have to test this */\n        if (options === '<br>'){\n            return element;\n        }\n        else {\n            var $target = angular.element(options);\n            $target[0].innerHTML = element.innerHTML;\n            element.parentNode.insertBefore($target[0], element);\n            element.parentNode.removeChild(element);\n            return $target;\n        }\n    };\n    return function(taDefaultWrap, topNode){\n        // NOTE: here we are dealing with the html directly from the browser and not the html the user sees.\n        // IF you want to modify the html the user sees, do it when the user does a switchView\n        taDefaultWrap = taBrowserTag(taDefaultWrap);\n        return function(command, showUI, options, defaultTagAttributes){\n            var i, $target, html, _nodes, next, optionsTagName, selectedElement, ourSelection;\n            var defaultWrapper = angular.element('<' + taDefaultWrap + '>');\n            try{\n                if (taSelection.getSelection) {\n                    ourSelection = taSelection.getSelection();\n                }\n                selectedElement = taSelection.getSelectionElement();\n                // special checks and fixes when we are selecting the whole container\n                var __h, _innerNode;\n                /* istanbul ignore next */\n                if (selectedElement.tagName !== undefined) {\n                    if (selectedElement.tagName.toLowerCase() === 'div' &&\n                        /taTextElement.+/.test(selectedElement.id) &&\n                        ourSelection && ourSelection.start &&\n                        ourSelection.start.offset === 1 &&\n                        ourSelection.end.offset === 1) {\n                        // opps we are actually selecting the whole container!\n                        //console.log('selecting whole container!');\n                        __h = selectedElement.innerHTML;\n                        if (/<br>/i.test(__h)) {\n                            // Firefox adds <br>'s and so we remove the <br>\n                            __h = __h.replace(/<br>/i, '&#8203;');  // no space-space\n                        }\n                        if (/<br\\/>/i.test(__h)) {\n                            // Firefox adds <br/>'s and so we remove the <br/>\n                            __h = __h.replace(/<br\\/>/i, '&#8203;');  // no space-space\n                        }\n                        // remove stacked up <span>'s\n                        if (/<span>(<span>)+/i.test(__h)) {\n                            __h = __.replace(/<span>(<span>)+/i, '<span>');\n                        }\n                        // remove stacked up </span>'s\n                        if (/<\\/span>(<\\/span>)+/i.test(__h)) {\n                            __h = __.replace(/<\\/span>(<\\/span>)+/i, '<\\/span>');\n                        }\n                        if (/<span><\\/span>/i.test(__h)) {\n                            // if we end up with a <span></span> here we remove it...\n                            __h = __h.replace(/<span><\\/span>/i, '');\n                        }\n                        //console.log('inner whole container', selectedElement.childNodes);\n                        _innerNode = '<div>' + __h + '</div>';\n                        selectedElement.innerHTML = _innerNode;\n                        taSelection.setSelectionToElementEnd(selectedElement.childNodes[0]);\n                        selectedElement = taSelection.getSelectionElement();\n                    } else if (selectedElement.tagName.toLowerCase() === 'span' &&\n                        ourSelection && ourSelection.start &&\n                        ourSelection.start.offset === 1 &&\n                        ourSelection.end.offset === 1) {\n                        // just a span -- this is a problem...\n                        //console.log('selecting span!');\n                        __h = selectedElement.innerHTML;\n                        if (/<br>/i.test(__h)) {\n                            // Firefox adds <br>'s and so we remove the <br>\n                            __h = __h.replace(/<br>/i, '&#8203;');  // no space-space\n                        }\n                        if (/<br\\/>/i.test(__h)) {\n                            // Firefox adds <br/>'s and so we remove the <br/>\n                            __h = __h.replace(/<br\\/>/i, '&#8203;');  // no space-space\n                        }\n                        // remove stacked up <span>'s\n                        if (/<span>(<span>)+/i.test(__h)) {\n                            __h = __.replace(/<span>(<span>)+/i, '<span>');\n                        }\n                        // remove stacked up </span>'s\n                        if (/<\\/span>(<\\/span>)+/i.test(__h)) {\n                            __h = __.replace(/<\\/span>(<\\/span>)+/i, '<\\/span>');\n                        }\n                        if (/<span><\\/span>/i.test(__h)) {\n                            // if we end up with a <span></span> here we remove it...\n                            __h = __h.replace(/<span><\\/span>/i, '');\n                        }\n                        //console.log('inner span', selectedElement.childNodes);\n                        // we wrap this in a <div> because otherwise the browser get confused when we attempt to select the whole node\n                        // and the focus is not set correctly no matter what we do\n                        _innerNode = '<div>' + __h + '</div>';\n                        selectedElement.innerHTML = _innerNode;\n                        taSelection.setSelectionToElementEnd(selectedElement.childNodes[0]);\n                        selectedElement = taSelection.getSelectionElement();\n                        //console.log(selectedElement.innerHTML);\n                    } else if (selectedElement.tagName.toLowerCase() === 'p' &&\n                        ourSelection && ourSelection.start &&\n                        ourSelection.start.offset === 1 &&\n                        ourSelection.end.offset === 1) {\n                        //console.log('p special');\n                        // we need to remove the </br> that firefox adds!\n                        __h = selectedElement.innerHTML;\n                        if (/<br>/i.test(__h)) {\n                            // Firefox adds <br>'s and so we remove the <br>\n                            __h = __h.replace(/<br>/i, '&#8203;');  // no space-space\n                            selectedElement.innerHTML = __h;\n                        }\n                    } else if (selectedElement.tagName.toLowerCase() === 'li' &&\n                        ourSelection && ourSelection.start &&\n                        ourSelection.start.offset === ourSelection.end.offset) {\n                        // we need to remove the </br> that firefox adds!\n                        __h = selectedElement.innerHTML;\n                        if (/<br>/i.test(__h)) {\n                            // Firefox adds <br>'s and so we remove the <br>\n                            __h = __h.replace(/<br>/i, '');  // nothing\n                            selectedElement.innerHTML = __h;\n                        }\n                    }\n                }\n            }catch(e){}\n            //console.log('************** selectedElement:', selectedElement);\n            /* istanbul ignore if: */\n            if (!selectedElement){return;}\n            var $selected = angular.element(selectedElement);\n            var tagName = (selectedElement && selectedElement.tagName && selectedElement.tagName.toLowerCase()) ||\n                /* istanbul ignore next: */ \"\";\n            if(command.toLowerCase() === 'insertorderedlist' || command.toLowerCase() === 'insertunorderedlist'){\n                var selfTag = taBrowserTag((command.toLowerCase() === 'insertorderedlist')? 'ol' : 'ul');\n                var selectedElements = taSelection.getOnlySelectedElements();\n                //console.log('PPPPPPPPPPPPP', tagName, selfTag, selectedElements, tagName.match(BLOCKELEMENTS), $selected.hasClass('ta-bind'), $selected.parent()[0].tagName);\n                if (selectedElements.length>1 && (tagName === 'ol' ||  tagName === 'ul' )) {\n                    return listElementsToSelfTag($selected, selectedElements, selfTag, selfTag===tagName, taDefaultWrap);\n                }\n                if(tagName === selfTag){\n                    // if all selected then we should remove the list\n                    // grab all li elements and convert to taDefaultWrap tags\n                    //console.log('tagName===selfTag');\n                    if ($selected[0].childNodes.length !== selectedElements.length && selectedElements.length===1) {\n                        $selected = angular.element(selectedElements[0]);\n                        return listElementToSelfTag($selected.parent(), $selected, selfTag, true, taDefaultWrap);\n                    } else {\n                        return listToDefault($selected, taDefaultWrap);\n                    }\n                }else if(tagName === 'li' &&\n                    $selected.parent()[0].tagName.toLowerCase() === selfTag &&\n                    $selected.parent().children().length === 1){\n                    // catch for the previous statement if only one li exists\n                    return listToDefault($selected.parent(), taDefaultWrap);\n                }else if(tagName === 'li' &&\n                    $selected.parent()[0].tagName.toLowerCase() !== selfTag &&\n                    $selected.parent().children().length === 1){\n                    // catch for the previous statement if only one li exists\n                    return listToList($selected.parent(), selfTag);\n                }else if(tagName.match(BLOCKELEMENTS) && !$selected.hasClass('ta-bind')){\n                    // if it's one of those block elements we have to change the contents\n                    // if it's a ol/ul we are changing from one to the other\n                    if (selectedElements.length) {\n                        if ($selected[0].childNodes.length !== selectedElements.length && selectedElements.length===1) {\n                            //console.log('&&&&&&&&&&&&&&& --------- &&&&&&&&&&&&&&&&', selectedElements[0], $selected[0].childNodes);\n                            $selected = angular.element(selectedElements[0]);\n                            return listElementToSelfTag($selected.parent(), $selected, selfTag, selfTag===tagName, taDefaultWrap);\n                        }\n                    }\n                    if(tagName === 'ol' || tagName === 'ul'){\n                        // now if this is a set of selected elements... behave diferently\n                        return listToList($selected, selfTag);\n                    }else{\n                        var childBlockElements = false;\n                        angular.forEach($selected.children(), function(elem){\n                            if(elem.tagName.match(BLOCKELEMENTS)) {\n                                childBlockElements = true;\n                            }\n                        });\n                        if(childBlockElements){\n                            return childElementsToList($selected.children(), $selected, selfTag);\n                        }else{\n                            return childElementsToList([angular.element('<div>' + selectedElement.innerHTML + '</div>')[0]], $selected, selfTag);\n                        }\n                    }\n                }else if(tagName.match(BLOCKELEMENTS)){\n                    // if we get here then the contents of the ta-bind are selected\n                    _nodes = taSelection.getOnlySelectedElements();\n                    //console.log('_nodes', _nodes, tagName);\n                    if(_nodes.length === 0){\n                        // here is if there is only text in ta-bind ie <div ta-bind>test content</div>\n                        $target = angular.element('<' + selfTag + '><li>' + selectedElement.innerHTML + '</li></' + selfTag + '>');\n                        $selected.html('');\n                        $selected.append($target);\n                    }else if(_nodes.length === 1 && (_nodes[0].tagName.toLowerCase() === 'ol' || _nodes[0].tagName.toLowerCase() === 'ul')){\n                        if(_nodes[0].tagName.toLowerCase() === selfTag){\n                            // remove\n                            return listToDefault(angular.element(_nodes[0]), taDefaultWrap);\n                        }else{\n                            return listToList(angular.element(_nodes[0]), selfTag);\n                        }\n                    }else{\n                        html = '';\n                        var $nodes = [];\n                        for(i = 0; i < _nodes.length; i++){\n                            /* istanbul ignore else: catch for real-world can't make it occur in testing */\n                            if(_nodes[i].nodeType !== 3){\n                                var $n = angular.element(_nodes[i]);\n                                /* istanbul ignore if: browser check only, phantomjs doesn't return children nodes but chrome at least does */\n                                if(_nodes[i].tagName.toLowerCase() === 'li') continue;\n                                else if(_nodes[i].tagName.toLowerCase() === 'ol' || _nodes[i].tagName.toLowerCase() === 'ul'){\n                                    html += $n[0].innerHTML; // if it's a list, add all it's children\n                                }else if(_nodes[i].tagName.toLowerCase() === 'span' && (_nodes[i].childNodes[0].tagName.toLowerCase() === 'ol' || _nodes[i].childNodes[0].tagName.toLowerCase() === 'ul')){\n                                    html += $n[0].childNodes[0].innerHTML; // if it's a list, add all it's children\n                                }else{\n                                    html += '<' + taBrowserTag('li') + '>' + $n[0].innerHTML + '</' + taBrowserTag('li') + '>';\n                                }\n                                $nodes.unshift($n);\n                            }\n                        }\n                        //console.log('$nodes', $nodes);\n                        $target = angular.element('<' + selfTag + '>' + html + '</' + selfTag + '>');\n                        $nodes.pop().replaceWith($target);\n                        angular.forEach($nodes, function($node){ $node.remove(); });\n                    }\n                    taSelection.setSelectionToElementEnd($target[0]);\n                    return;\n                }\n            }else if(command.toLowerCase() === 'formatblock'){\n                optionsTagName = options.toLowerCase().replace(/[<>]/ig, '');\n                if(optionsTagName.trim() === 'default') {\n                    optionsTagName = taDefaultWrap;\n                    options = '<' + taDefaultWrap + '>';\n                }\n                if(tagName === 'li') {\n                    $target = $selected.parent();\n                }\n                else {\n                    $target = $selected;\n                }\n                // find the first blockElement\n                while(!$target[0].tagName || !$target[0].tagName.match(BLOCKELEMENTS) && !$target.parent().attr('contenteditable')){\n                    $target = $target.parent();\n                    /* istanbul ignore next */\n                    tagName = ($target[0].tagName || '').toLowerCase();\n                }\n                if(tagName === optionsTagName){\n                    // $target is wrap element\n                    _nodes = $target.children();\n                    var hasBlock = false;\n                    for(i = 0; i < _nodes.length; i++){\n                        hasBlock = hasBlock || _nodes[i].tagName.match(BLOCKELEMENTS);\n                    }\n                    if(hasBlock){\n                        $target.after(_nodes);\n                        next = $target.next();\n                        $target.remove();\n                        $target = next;\n                    }else{\n                        defaultWrapper.append($target[0].childNodes);\n                        $target.after(defaultWrapper);\n                        $target.remove();\n                        $target = defaultWrapper;\n                    }\n                }else if($target.parent()[0].tagName.toLowerCase() === optionsTagName &&\n                    !$target.parent().hasClass('ta-bind')){\n                    //unwrap logic for parent\n                    var blockElement = $target.parent();\n                    var contents = blockElement.contents();\n                    for(i = 0; i < contents.length; i ++){\n                        /* istanbul ignore next: can't test - some wierd thing with how phantomjs works */\n                        if(blockElement.parent().hasClass('ta-bind') && contents[i].nodeType === 3){\n                            defaultWrapper = angular.element('<' + taDefaultWrap + '>');\n                            defaultWrapper[0].innerHTML = contents[i].outerHTML;\n                            contents[i] = defaultWrapper[0];\n                        }\n                        blockElement.parent()[0].insertBefore(contents[i], blockElement[0]);\n                    }\n                    blockElement.remove();\n                }else if(tagName.match(LISTELEMENTS)){\n                    // wrapping a list element\n                    $target.wrap(options);\n                }else{\n                    // default wrap behaviour\n                    _nodes = taSelection.getOnlySelectedElements();\n                    if(_nodes.length === 0) {\n                        // no nodes at all....\n                        _nodes = [$target[0]];\n                    }\n                    // find the parent block element if any of the nodes are inline or text\n                    for(i = 0; i < _nodes.length; i++){\n                        if(_nodes[i].nodeType === 3 || !_nodes[i].tagName.match(BLOCKELEMENTS)){\n                            while(_nodes[i].nodeType === 3 || !_nodes[i].tagName || !_nodes[i].tagName.match(BLOCKELEMENTS)){\n                                _nodes[i] = _nodes[i].parentNode;\n                            }\n                        }\n                    }\n                    // remove any duplicates from the array of _nodes!\n                    _nodes = _nodes.filter(function(value, index, self) {\n                        return self.indexOf(value) === index;\n                    });\n                    // remove all whole taTextElement if it is here... unless it is the only element!\n                    if (_nodes.length>1) {\n                        _nodes = _nodes.filter(function (value, index, self) {\n                            return !(value.nodeName.toLowerCase() === 'div' && /^taTextElement/.test(value.id));\n                        });\n                    }\n                    if(angular.element(_nodes[0]).hasClass('ta-bind')){\n                        $target = angular.element(options);\n                        $target[0].innerHTML = _nodes[0].innerHTML;\n                        _nodes[0].innerHTML = $target[0].outerHTML;\n                    }else if(optionsTagName === 'blockquote'){\n                        // blockquotes wrap other block elements\n                        html = '';\n                        for(i = 0; i < _nodes.length; i++){\n                            html += _nodes[i].outerHTML;\n                        }\n                        $target = angular.element(options);\n                        $target[0].innerHTML = html;\n                        _nodes[0].parentNode.insertBefore($target[0],_nodes[0]);\n                        for(i = _nodes.length - 1; i >= 0; i--){\n                            /* istanbul ignore else:  */\n                            if (_nodes[i].parentNode) _nodes[i].parentNode.removeChild(_nodes[i]);\n                        }\n                    } else /* istanbul ignore next: not tested since identical to blockquote */\n                    if (optionsTagName === 'pre' && taSelection.getStateShiftKey()) {\n                        //console.log('shift pre', _nodes);\n                        // pre wrap other block elements\n                        html = '';\n                        for (i = 0; i < _nodes.length; i++) {\n                            html += _nodes[i].outerHTML;\n                        }\n                        $target = angular.element(options);\n                        $target[0].innerHTML = html;\n                        _nodes[0].parentNode.insertBefore($target[0], _nodes[0]);\n                        for (i = _nodes.length - 1; i >= 0; i--) {\n                            /* istanbul ignore else:  */\n                            if (_nodes[i].parentNode) _nodes[i].parentNode.removeChild(_nodes[i]);\n                        }\n                    }\n                    else {\n                        //console.log(optionsTagName, _nodes);\n                        // regular block elements replace other block elements\n                        for (i = 0; i < _nodes.length; i++) {\n                            var newBlock = turnBlockIntoBlocks(_nodes[i], options);\n                            if (_nodes[i] === $target[0]) {\n                                $target = angular.element(newBlock);\n                            }\n                        }\n                    }\n                }\n                taSelection.setSelectionToElementEnd($target[0]);\n                // looses focus when we have the whole container selected and no text!\n                // refocus on the shown display element, this fixes a bug when using firefox\n                $target[0].focus();\n                return;\n            }else if(command.toLowerCase() === 'createlink'){\n                /* istanbul ignore next: firefox specific fix */\n                if (tagName === 'a') {\n                    // already a link!!! we are just replacing it...\n                    taSelection.getSelectionElement().href = options;\n                    return;\n                }\n                var tagBegin = '<a href=\"' + options + '\" target=\"' +\n                        (defaultTagAttributes.a.target ? defaultTagAttributes.a.target : '') +\n                        '\">',\n                    tagEnd = '</a>',\n                    _selection = taSelection.getSelection();\n                if(_selection.collapsed){\n                    //console.log('collapsed');\n                    // insert text at selection, then select then just let normal exec-command run\n                    taSelection.insertHtml(tagBegin + options + tagEnd, topNode);\n                }else if(rangy.getSelection().getRangeAt(0).canSurroundContents()){\n                    var node = angular.element(tagBegin + tagEnd)[0];\n                    rangy.getSelection().getRangeAt(0).surroundContents(node);\n                }\n                return;\n            }else if(command.toLowerCase() === 'inserthtml'){\n                //console.log('inserthtml');\n                taSelection.insertHtml(options, topNode);\n                return;\n            }\n            try{\n                $document[0].execCommand(command, showUI, options);\n            }catch(e){}\n        };\n    };\n}]).service('taSelection', ['$document', 'taDOM', '$log',\n/* istanbul ignore next: all browser specifics and PhantomJS dosen't seem to support half of it */\nfunction($document, taDOM, $log){\n    // need to dereference the document else the calls don't work correctly\n    var _document = $document[0];\n    var bShiftState;\n    var brException = function (element, offset) {\n        /* check if selection is a BR element at the beginning of a container. If so, get\n        * the parentNode instead.\n        * offset should be zero in this case. Otherwise, return the original\n        * element.\n        */\n        if (element.tagName && element.tagName.match(/^br$/i) && offset === 0 && !element.previousSibling) {\n            return {\n                element: element.parentNode,\n                offset: 0\n            };\n        } else {\n            return {\n                element: element,\n                offset: offset\n            };\n        }\n    };\n    var api = {\n        getSelection: function(){\n            var range;\n            try {\n                // catch any errors from rangy and ignore the issue\n                range = rangy.getSelection().getRangeAt(0);\n            } catch(e) {\n                //console.info(e);\n                return undefined;\n            }\n            var container = range.commonAncestorContainer;\n            var selection = {\n                start: brException(range.startContainer, range.startOffset),\n                end: brException(range.endContainer, range.endOffset),\n                collapsed: range.collapsed\n            };\n            // This has problems under Firefox.\n            // On Firefox with\n            // <p>Try me !</p>\n            // <ul>\n            // <li>line 1</li>\n            // <li>line 2</li>\n            // </ul>\n            // <p>line 3</p>\n            // <ul>\n            // <li>line 4</li>\n            // <li>line 5</li>\n            // </ul>\n            // <p>Hello textAngular</p>\n            // WITH the cursor after the 3 on line 3, it gets the commonAncestorContainer as:\n            // <TextNode textContent='line 3'>\n            // AND Chrome gets the commonAncestorContainer as:\n            // <p>line 3</p>\n            //\n            // Check if the container is a text node and return its parent if so\n            // unless this is the whole taTextElement.  If so we return the textNode\n            if (container.nodeType === 3) {\n                if (container.parentNode.nodeName.toLowerCase() === 'div' &&\n                    /^taTextElement/.test(container.parentNode.id)) {\n                    // textNode where the parent is the whole <div>!!!\n                    //console.log('textNode ***************** container:', container);\n                } else {\n                    container = container.parentNode;\n                }\n            }\n            if (container.nodeName.toLowerCase() === 'div' &&\n                /^taTextElement/.test(container.id)) {\n                //console.log('*********taTextElement************');\n                //console.log('commonAncestorContainer:', container);\n                selection.start.element = container.childNodes[selection.start.offset];\n                selection.end.element = container.childNodes[selection.end.offset];\n                selection.container = container;\n            } else {\n                if (container.parentNode === selection.start.element ||\n                    container.parentNode === selection.end.element) {\n                    selection.container = container.parentNode;\n                } else {\n                    selection.container = container;\n                }\n            }\n            //console.log('***selection container:', selection.container.nodeName, selection.start.offset, selection.container);\n            return selection;\n        },\n        // if we use the LEFT_ARROW and we are at the special place <span>&#65279;</span> we move the cursor over by one...\n        // Chrome and Firefox behave differently so so fix this for Firefox here.  No adjustment needed for Chrome.\n        updateLeftArrowKey: function(element) {\n            var range = rangy.getSelection().getRangeAt(0);\n            if (range && range.collapsed) {\n                var _nodes = api.getFlattenedDom(range);\n                if (!_nodes.findIndex) return;\n                var _node = range.startContainer;\n                var indexStartContainer = _nodes.findIndex(function(element, index){\n                    if (element.node===_node) return true;\n                    var _indexp = element.parents.indexOf(_node);\n                    return (_indexp !== -1);\n                });\n                var m;\n                var nextNodeToRight;\n                //console.log('indexStartContainer', indexStartContainer, _nodes.length, 'startContainer:', _node, _node === _nodes[indexStartContainer].node);\n                _nodes.forEach(function (n, i) {\n                    //console.log(i, n.node);\n                    n.parents.forEach(function (nn, j){\n                        //console.log(i, j, nn);\n                    });\n                });\n                if (indexStartContainer+1 < _nodes.length) {\n                    // we need the node just after this startContainer\n                    // so we can check and see it this is a special place\n                    nextNodeToRight = _nodes[indexStartContainer+1].node;\n                    //console.log(nextNodeToRight, range.startContainer);\n                }\n                //console.log('updateLeftArrowKey', range.startOffset, range.startContainer.textContent);\n                // this first section handles the case for Chrome browser\n                // if the first character of the nextNode is a \\ufeff we know that we are just before the special span...\n                // and so we most left by one character\n                if (nextNodeToRight && nextNodeToRight.textContent) {\n                    m = /^\\ufeff([^\\ufeff]*)$/.exec(nextNodeToRight.textContent);\n                    if (m) {\n                        // we are before the special node with begins with a \\ufeff character\n                        //console.log('LEFT ...found it...', 'startOffset:', range.startOffset, m[0].length, m[1].length);\n                        // no need to change anything in this case\n                        return;\n                    }\n                }\n                var nextNodeToLeft;\n                if (indexStartContainer > 0) {\n                    // we need the node just after this startContainer\n                    // so we can check and see it this is a special place\n                    nextNodeToLeft = _nodes[indexStartContainer-1].node;\n                    //console.log(nextNodeToLeft, nextNodeToLeft);\n                }\n                if (range.startOffset === 0 && nextNodeToLeft) {\n                    //console.log(nextNodeToLeft, range.startOffset, nextNodeToLeft.textContent);\n                    m = /^\\ufeff([^\\ufeff]*)$/.exec(nextNodeToLeft.textContent);\n                    if (m) {\n                        //console.log('LEFT &&&&&&&&&&&&&&&&&&&...found it...&&&&&&&&&&&', nextNodeToLeft, m[0].length, m[1].length);\n                        // move over to the left my one -- Firefox triggers this case\n                        api.setSelectionToElementEnd(nextNodeToLeft);\n                        return;\n                    }\n                }\n            }\n            return;\n        },\n        // if we use the RIGHT_ARROW and we are at the special place <span>&#65279;</span> we move the cursor over by one...\n        updateRightArrowKey: function(element) {\n            // we do not need to make any adjustments here, so we ignore all this code\n            if (false) {\n                var range = rangy.getSelection().getRangeAt(0);\n                if (range && range.collapsed) {\n                    var _nodes = api.getFlattenedDom(range);\n                    if (!_nodes.findIndex) return;\n                    var _node = range.startContainer;\n                    var indexStartContainer = _nodes.findIndex(function (element, index) {\n                        if (element.node === _node) return true;\n                        var _indexp = element.parents.indexOf(_node);\n                        return (_indexp !== -1);\n                    });\n                    var _sel;\n                    var i;\n                    var m;\n\n                    // if the last character is a \\ufeff we know that we are just before the special span...\n                    // and so we most right by one character\n                    var indexFound = _nodes.findIndex(function (n, index) {\n                        if (n.textContent) {\n                            var m = /^\\ufeff([^\\ufeff]*)$/.exec(n.textContent);\n                            if (m) {\n                                return true;\n                            } else {\n                                return false;\n                            }\n                        } else {\n                            return false;\n                        }\n                    });\n                    if (indexFound === -1) {\n                        return;\n                    }\n                    //console.log(indexFound, range.startContainer, range.startOffset);\n                    _node = _nodes[indexStartContainer];\n                    //console.log('indexStartContainer', indexStartContainer);\n                    if (_node && _node.textContent) {\n                        m = /^\\ufeff([^\\ufeff]*)$/.exec(_node.textContent);\n                        if (m && range.startOffset - 1 === m[1].length) {\n                            //console.log('RIGHT found it...&&&&&&&&&&&', range.startOffset);\n                            // no need to make any adjustment\n                            return;\n                        }\n                    }\n                    //console.log(range.startOffset);\n                    if (_nodes && range.startOffset === 0) {\n                        indexStartContainer = _nodes.indexOf(range.startContainer);\n                        if (indexStartContainer !== -1 && indexStartContainer > 0) {\n                            _node = _nodes[indexStartContainer - 1];\n                            if (_node.textContent) {\n                                m = /\\ufeff([^\\ufeff]*)$/.exec(_node.textContent);\n                                if (m && true || range.startOffset === m[1].length + 1) {\n                                    //console.log('RIGHT &&&&&&&&&&&&&&&&&&&...found it...&&&&&&&&&&&', range.startOffset, m[1].length);\n                                    // no need to make any adjustment\n                                    return;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        getFlattenedDom: function(range) {\n            var parent = range.commonAncestorContainer.parentNode;\n            if (!parent) {\n                return range.commonAncestorContainer.childNodes;\n            }\n            var nodes = Array.prototype.slice.call(parent.childNodes); // converts NodeList to Array\n            var indexStartContainer = nodes.indexOf(range.startContainer);\n            // make sure that we have a big enough set of nodes\n            if (indexStartContainer+1 < nodes.length && indexStartContainer > 0) {\n                // we are good\n                // we can go down one node or up one node\n            } else {\n                if (parent.parentNode) {\n                    parent = parent.parentNode;\n                }\n            }\n            // now walk the parent\n            nodes = [];\n            function addNodes(_set) {\n                if (_set.node.childNodes.length) {\n                    var childNodes = Array.prototype.slice.call(_set.node.childNodes); // converts NodeList to Array\n                    childNodes.forEach(function(n) {\n                        var _t = _set.parents.slice();\n                        if (_t.slice(-1)[0]!==_set.node) {\n                            _t.push(_set.node);\n                        }\n                        addNodes({parents: _t, node: n});\n                    });\n                } else {\n                    nodes.push({parents: _set.parents, node: _set.node});\n                }\n            }\n            addNodes({parents: [parent], node: parent});\n            return nodes;\n        },\n        getOnlySelectedElements: function(){\n            var range = rangy.getSelection().getRangeAt(0);\n            var container = range.commonAncestorContainer;\n            // Node.TEXT_NODE === 3\n            // Node.ELEMENT_NODE === 1\n            // Node.COMMENT_NODE === 8\n            // Check if the container is a text node and return its parent if so\n            container = container.nodeType === 3 ? container.parentNode : container;\n            // get the nodes in the range that are ELEMENT_NODE and are children of the container\n            // in this range...\n            return range.getNodes([1], function(node){\n                return node.parentNode === container;\n            });\n        },\n        // this includes the container element if all children are selected\n        getAllSelectedElements: function(){\n            var range = rangy.getSelection().getRangeAt(0);\n            var container = range.commonAncestorContainer;\n            // Node.TEXT_NODE === 3\n            // Node.ELEMENT_NODE === 1\n            // Node.COMMENT_NODE === 8\n            // Check if the container is a text node and return its parent if so\n            container = container.nodeType === 3 ? container.parentNode : container;\n            // get the nodes in the range that are ELEMENT_NODE and are children of the container\n            // in this range...\n            var selectedNodes = range.getNodes([1], function(node){\n                return node.parentNode === container;\n            });\n            var innerHtml = container.innerHTML;\n            // remove the junk that rangy has put down\n            innerHtml = innerHtml.replace(/<span id=.selectionBoundary[^>]+>\\ufeff?<\\/span>/ig, '');\n            //console.log(innerHtml);\n            //console.log(range.toHtml());\n            //console.log(innerHtml === range.toHtml());\n            if (innerHtml === range.toHtml() &&\n                // not the whole taTextElement\n                (!(container.nodeName.toLowerCase() === 'div' &&  /^taTextElement/.test(container.id)))\n            ) {\n                var arr = [];\n                for(var i = selectedNodes.length; i--; arr.unshift(selectedNodes[i]));\n                selectedNodes = arr;\n                selectedNodes.push(container);\n                //$log.debug(selectedNodes);\n            }\n            return selectedNodes;\n        },\n        // Some basic selection functions\n        getSelectionElement: function () {\n            var s = api.getSelection();\n            if (s) {\n                return api.getSelection().container;\n            } else {\n                return undefined;\n            }\n        },\n        setSelection: function(elStart, elEnd, start, end){\n            var range = rangy.createRange();\n\n            range.setStart(elStart, start);\n            range.setEnd(elEnd, end);\n\n            rangy.getSelection().setSingleRange(range);\n        },\n        setSelectionBeforeElement: function (el){\n            var range = rangy.createRange();\n\n            range.selectNode(el);\n            range.collapse(true);\n\n            rangy.getSelection().setSingleRange(range);\n        },\n        setSelectionAfterElement: function (el){\n            var range = rangy.createRange();\n\n            range.selectNode(el);\n            range.collapse(false);\n\n            rangy.getSelection().setSingleRange(range);\n        },\n        setSelectionToElementStart: function (el){\n            var range = rangy.createRange();\n\n            range.selectNodeContents(el);\n            range.collapse(true);\n\n            rangy.getSelection().setSingleRange(range);\n        },\n        setSelectionToElementEnd: function (el){\n            var range = rangy.createRange();\n\n            range.selectNodeContents(el);\n            range.collapse(false);\n            if(el.childNodes && el.childNodes[el.childNodes.length - 1] && el.childNodes[el.childNodes.length - 1].nodeName === 'br'){\n                range.startOffset = range.endOffset = range.startOffset - 1;\n            }\n            rangy.getSelection().setSingleRange(range);\n        },\n        setStateShiftKey: function(bS) {\n            bShiftState = bS;\n        },\n        getStateShiftKey: function() {\n            return bShiftState;\n        },\n        // from http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div\n        // topNode is the contenteditable normally, all manipulation MUST be inside this.\n        insertHtml: function(html, topNode){\n            var parent, secondParent, _childI, nodes, i, lastNode, _tempFrag;\n            var element = angular.element(\"<div>\" + html + \"</div>\");\n            var range = rangy.getSelection().getRangeAt(0);\n            var frag = _document.createDocumentFragment();\n            var children = element[0].childNodes;\n            var isInline = true;\n\n            if(children.length > 0){\n                // NOTE!! We need to do the following:\n                // check for blockelements - if they exist then we have to split the current element in half (and all others up to the closest block element) and insert all children in-between.\n                // If there are no block elements, or there is a mixture we need to create textNodes for the non wrapped text (we don't want them spans messing up the picture).\n                nodes = [];\n                for(_childI = 0; _childI < children.length; _childI++){\n                    var _cnode = children[_childI];\n                    if (_cnode.nodeName.toLowerCase() === 'p' &&\n                        _cnode.innerHTML.trim() === '') { // empty p element\n                        continue;\n                    }\n                    /****************\n                     *  allow any text to be inserted...\n                    if((   _cnode.nodeType === 3 &&\n                           _cnode.nodeValue === '\\ufeff'[0] &&\n                           _cnode.nodeValue.trim() === '') // empty no-space space element\n                        ) {\n                        // no change to isInline\n                        nodes.push(_cnode);\n                        continue;\n                    }\n                    if(_cnode.nodeType === 3 &&\n                         _cnode.nodeValue.trim() === '') { // empty text node\n                        continue;\n                    }\n                    *****************/\n                    isInline = isInline && !BLOCKELEMENTS.test(_cnode.nodeName);\n                    nodes.push(_cnode);\n                }\n                for(var _n = 0; _n < nodes.length; _n++) {\n                    lastNode = frag.appendChild(nodes[_n]);\n                }\n                if( !isInline &&\n                    range.collapsed &&\n                    /^(|<br(|\\/)>)$/i.test(range.startContainer.innerHTML) ) {\n                    range.selectNode(range.startContainer);\n                }\n            }else{\n                isInline = true;\n                // paste text of some sort\n                lastNode = frag = _document.createTextNode(html);\n            }\n\n            // Other Edge case - selected data spans multiple blocks.\n            if(isInline){\n                range.deleteContents();\n            }else{ // not inline insert\n                if(range.collapsed && range.startContainer !== topNode){\n                    if(range.startContainer.innerHTML && range.startContainer.innerHTML.match(/^<[^>]*>$/i)){\n                        // this log is to catch when innerHTML is something like `<img ...>`\n                        parent = range.startContainer;\n                        if(range.startOffset === 1){\n                            // before single tag\n                            range.setStartAfter(parent);\n                            range.setEndAfter(parent);\n                        }else{\n                            // after single tag\n                            range.setStartBefore(parent);\n                            range.setEndBefore(parent);\n                        }\n                    }else{\n                        // split element into 2 and insert block element in middle\n                        if(range.startContainer.nodeType === 3 && range.startContainer.parentNode !== topNode){ // if text node\n                            parent = range.startContainer.parentNode;\n                            secondParent = parent.cloneNode();\n                            // split the nodes into two lists - before and after, splitting the node with the selection into 2 text nodes.\n                            taDOM.splitNodes(parent.childNodes, parent, secondParent, range.startContainer, range.startOffset);\n\n                            // Escape out of the inline tags like b\n                            while(!VALIDELEMENTS.test(parent.nodeName)){\n                                angular.element(parent).after(secondParent);\n                                parent = parent.parentNode;\n                                var _lastSecondParent = secondParent;\n                                secondParent = parent.cloneNode();\n                                // split the nodes into two lists - before and after, splitting the node with the selection into 2 text nodes.\n                                taDOM.splitNodes(parent.childNodes, parent, secondParent, _lastSecondParent);\n                            }\n                        }else{\n                            parent = range.startContainer;\n                            secondParent = parent.cloneNode();\n                            taDOM.splitNodes(parent.childNodes, parent, secondParent, undefined, undefined, range.startOffset);\n                        }\n\n                        angular.element(parent).after(secondParent);\n                        // put cursor to end of inserted content\n                        //console.log('setStartAfter', parent);\n                        range.setStartAfter(parent);\n                        range.setEndAfter(parent);\n\n                        if(/^(|<br(|\\/)>)$/i.test(parent.innerHTML.trim())){\n                            range.setStartBefore(parent);\n                            range.setEndBefore(parent);\n                            angular.element(parent).remove();\n                        }\n                        if(/^(|<br(|\\/)>)$/i.test(secondParent.innerHTML.trim())) angular.element(secondParent).remove();\n                        if(parent.nodeName.toLowerCase() === 'li'){\n                            _tempFrag = _document.createDocumentFragment();\n                            for(i = 0; i < frag.childNodes.length; i++){\n                                element = angular.element('<li>');\n                                taDOM.transferChildNodes(frag.childNodes[i], element[0]);\n                                taDOM.transferNodeAttributes(frag.childNodes[i], element[0]);\n                                _tempFrag.appendChild(element[0]);\n                            }\n                            frag = _tempFrag;\n                            if(lastNode){\n                                lastNode = frag.childNodes[frag.childNodes.length - 1];\n                                lastNode = lastNode.childNodes[lastNode.childNodes.length - 1];\n                            }\n                        }\n                    }\n                }else{\n                    range.deleteContents();\n                }\n            }\n\n            range.insertNode(frag);\n            if(lastNode){\n                api.setSelectionToElementEnd(lastNode);\n            }\n        }\n\n        /* NOT FUNCTIONAL YET\n         // under Firefox, we may have a selection that needs to be normalized\n         isSelectionContainerWhole_taTextElement: function (){\n         var range = rangy.getSelection().getRangeAt(0);\n         var container = range.commonAncestorContainer;\n         if (container.nodeName.toLowerCase() === 'div' &&\n         /^taTextElement/.test(container.id)) {\n         // container is the whole taTextElement\n         return true;\n         }\n         return false;\n         },\n         setNormalizedSelection: function (){\n         var range = rangy.getSelection().getRangeAt(0);\n         var container = range.commonAncestorContainer;\n         console.log(range);\n         console.log(container.childNodes);\n         if (range.collapsed) {\n         // we know what to do...\n         console.log(container.childNodes[range.startOffset]);\n         api.setSelectionToElementStart(container.childNodes[range.startOffset]);\n         }\n         },\n         */\n    };\n    return api;\n}]).service('taDOM', function(){\n    var taDOM = {\n        // recursive function that returns an array of angular.elements that have the passed attribute set on them\n        getByAttribute: function(element, attribute){\n            var resultingElements = [];\n            var childNodes = element.children();\n            if(childNodes.length){\n                angular.forEach(childNodes, function(child){\n                    resultingElements = resultingElements.concat(taDOM.getByAttribute(angular.element(child), attribute));\n                });\n            }\n            if(element.attr(attribute) !== undefined) resultingElements.push(element);\n            return resultingElements;\n        },\n\n        transferChildNodes: function(source, target){\n            // clear out target\n            target.innerHTML = '';\n            while(source.childNodes.length > 0) target.appendChild(source.childNodes[0]);\n            return target;\n        },\n\n        splitNodes: function(nodes, target1, target2, splitNode, subSplitIndex, splitIndex){\n            if(!splitNode && isNaN(splitIndex)) throw new Error('taDOM.splitNodes requires a splitNode or splitIndex');\n            var startNodes = document.createDocumentFragment();\n            var endNodes = document.createDocumentFragment();\n            var index = 0;\n\n            while(nodes.length > 0 && (isNaN(splitIndex) || splitIndex !== index) && nodes[0] !== splitNode){\n                startNodes.appendChild(nodes[0]); // this removes from the nodes array (if proper childNodes object.\n                index++;\n            }\n\n            if(!isNaN(subSplitIndex) && subSplitIndex >= 0 && nodes[0]){\n                startNodes.appendChild(document.createTextNode(nodes[0].nodeValue.substring(0, subSplitIndex)));\n                nodes[0].nodeValue = nodes[0].nodeValue.substring(subSplitIndex);\n            }\n            while(nodes.length > 0) endNodes.appendChild(nodes[0]);\n\n            taDOM.transferChildNodes(startNodes, target1);\n            taDOM.transferChildNodes(endNodes, target2);\n        },\n\n        transferNodeAttributes: function(source, target){\n            for(var i = 0; i < source.attributes.length; i++) target.setAttribute(source.attributes[i].name, source.attributes[i].value);\n            return target;\n        }\n    };\n    return taDOM;\n});\n\nangular.module('textAngular.validators', [])\n.directive('taMaxText', function(){\n    return {\n        restrict: 'A',\n        require: 'ngModel',\n        link: function(scope, elem, attrs, ctrl){\n            var max = parseInt(scope.$eval(attrs.taMaxText));\n            if (isNaN(max)){\n                throw('Max text must be an integer');\n            }\n            attrs.$observe('taMaxText', function(value){\n                max = parseInt(value);\n                if (isNaN(max)){\n                    throw('Max text must be an integer');\n                }\n                if (ctrl.$dirty){\n                    ctrl.$validate();\n                }\n            });\n            ctrl.$validators.taMaxText = function(viewValue){\n                var source = angular.element('<div/>');\n                source.html(viewValue);\n                return source.text().length <= max;\n            };\n        }\n    };\n}).directive('taMinText', function(){\n    return {\n        restrict: 'A',\n        require: 'ngModel',\n        link: function(scope, elem, attrs, ctrl){\n            var min = parseInt(scope.$eval(attrs.taMinText));\n            if (isNaN(min)){\n                throw('Min text must be an integer');\n            }\n            attrs.$observe('taMinText', function(value){\n                min = parseInt(value);\n                if (isNaN(min)){\n                    throw('Min text must be an integer');\n                }\n                if (ctrl.$dirty){\n                    ctrl.$validate();\n                }\n            });\n            ctrl.$validators.taMinText = function(viewValue){\n                var source = angular.element('<div/>');\n                source.html(viewValue);\n                return !source.text().length || source.text().length >= min;\n            };\n        }\n    };\n});\nangular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'])\n.service('_taBlankTest', [function(){\n    return function(_blankVal){\n        // we radically restructure this code.\n        // what was here before was incredibly fragile.\n        // What we do now is to check that the html is non-blank visually\n        // which we check by looking at html->text\n        if(!_blankVal) return true;\n        // find first non-tag match - ie start of string or after tag that is not whitespace\n        // var t0 = performance.now();\n        // Takes a small fraction of a mSec to do this...\n        var _text_ = stripHtmlToText(_blankVal);\n        // var t1 = performance.now();\n        // console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:');\n        if (_text_=== '') {\n            // img generates a visible item so it is not blank!\n            if (/<img[^>]+>/.test(_blankVal)) {\n                return false;\n            }\n            return true;\n        } else {\n            return false;\n        }\n    };\n}])\n.directive('taButton', [function(){\n    return {\n        link: function(scope, element, attrs){\n            element.attr('unselectable', 'on');\n            element.on('mousedown', function(e, eventData){\n                /* istanbul ignore else: this is for catching the jqLite testing*/\n                if(eventData) angular.extend(e, eventData);\n                // this prevents focusout from firing on the editor when clicking toolbar buttons\n                e.preventDefault();\n                return false;\n            });\n        }\n    };\n}])\n.directive('taBind', [\n        'taSanitize', '$timeout', '$document', 'taFixChrome', 'taBrowserTag',\n        'taSelection', 'taSelectableElements', 'taApplyCustomRenderers', 'taOptions',\n        '_taBlankTest', '$parse', 'taDOM', 'textAngularManager',\n        function(\n            taSanitize, $timeout, $document, taFixChrome, taBrowserTag,\n            taSelection, taSelectableElements, taApplyCustomRenderers, taOptions,\n            _taBlankTest, $parse, taDOM, textAngularManager){\n    // Uses for this are textarea or input with ng-model and ta-bind='text'\n    // OR any non-form element with contenteditable=\"contenteditable\" ta-bind=\"html|text\" ng-model\n    return {\n        priority: 2, // So we override validators correctly\n        require: ['ngModel','?ngModelOptions'],\n        link: function(scope, element, attrs, controller){\n            var ngModel = controller[0];\n            var ngModelOptions = controller[1] || {};\n            // the option to use taBind on an input or textarea is required as it will sanitize all input into it correctly.\n            var _isContentEditable = element.attr('contenteditable') !== undefined && element.attr('contenteditable');\n            var _isInputFriendly = _isContentEditable || element[0].tagName.toLowerCase() === 'textarea' || element[0].tagName.toLowerCase() === 'input';\n            var _isReadonly = false;\n            var _focussed = false;\n            var _skipRender = false;\n            var _disableSanitizer = attrs.taUnsafeSanitizer || taOptions.disableSanitizer;\n            var _keepStyles = attrs.taKeepStyles || taOptions.keepStyles;\n            var _lastKey;\n            // see http://www.javascripter.net/faq/keycodes.htm for good information\n            // NOTE Mute On|Off 173 (Opera MSIE Safari Chrome) 181 (Firefox)\n            // BLOCKED_KEYS are special keys...\n            // Tab, pause/break, CapsLock, Esc, Page Up, End, Home,\n            // Left arrow, Up arrow, Right arrow, Down arrow, Insert, Delete,\n            // f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12\n            // NumLock, ScrollLock\n            var BLOCKED_KEYS = /^(9|19|20|27|33|34|35|36|37|38|39|40|45|112|113|114|115|116|117|118|119|120|121|122|123|144|145)$/i;\n            // UNDO_TRIGGER_KEYS - spaces, enter, delete, backspace, all punctuation\n            // Backspace, Enter, Space, Delete, (; :) (Firefox), (= +) (Firefox),\n            // Numpad +, Numpad -, (; :), (= +),\n            // (, <), (- _), (. >), (/ ?), (` ~), ([ {), (\\ |), (] }), (' \")\n            // NOTE - Firefox: 173 = (- _) -- adding this to UNDO_TRIGGER_KEYS\n            var UNDO_TRIGGER_KEYS = /^(8|13|32|46|59|61|107|109|173|186|187|188|189|190|191|192|219|220|221|222)$/i;\n            var _pasteHandler;\n\n            // defaults to the paragraph element, but we need the line-break or it doesn't allow you to type into the empty element\n            // non IE is '<p><br/></p>', ie is '<p></p>' as for once IE gets it correct...\n            var _defaultVal, _defaultTest;\n\n            var _CTRL_KEY = 0x0001;\n            var _META_KEY = 0x0002;\n            var _ALT_KEY = 0x0004;\n            var _SHIFT_KEY = 0x0008;\n            // KEYCODEs we use\n            var _ENTER_KEYCODE = 13;\n            var _SHIFT_KEYCODE = 16;\n            var _TAB_KEYCODE = 9;\n            var _LEFT_ARROW_KEYCODE = 37;\n            var _RIGHT_ARROW_KEYCODE = 39;\n            // map events to special keys...\n            // mappings is an array of maps from events to specialKeys as declared in textAngularSetup\n            var _keyMappings = [\n                //\t\tctrl/command + z\n                {\n                    specialKey: 'UndoKey',\n                    forbiddenModifiers: _ALT_KEY + _SHIFT_KEY,\n                    mustHaveModifiers: [_META_KEY + _CTRL_KEY],\n                    keyCode: 90\n                },\n                //\t\tctrl/command + shift + z\n                {\n                    specialKey: 'RedoKey',\n                    forbiddenModifiers: _ALT_KEY,\n                    mustHaveModifiers: [_META_KEY + _CTRL_KEY, _SHIFT_KEY],\n                    keyCode: 90\n                },\n                //\t\tctrl/command + y\n                {\n                    specialKey: 'RedoKey',\n                    forbiddenModifiers: _ALT_KEY + _SHIFT_KEY,\n                    mustHaveModifiers: [_META_KEY + _CTRL_KEY],\n                    keyCode: 89\n                },\n                //\t\tTabKey\n                {\n                    specialKey: 'TabKey',\n                    forbiddenModifiers: _META_KEY + _SHIFT_KEY + _ALT_KEY + _CTRL_KEY,\n                    mustHaveModifiers: [],\n                    keyCode: _TAB_KEYCODE\n                },\n                //\t\tshift + TabKey\n                {\n                    specialKey: 'ShiftTabKey',\n                    forbiddenModifiers: _META_KEY + _ALT_KEY + _CTRL_KEY,\n                    mustHaveModifiers: [_SHIFT_KEY],\n                    keyCode: _TAB_KEYCODE\n                }\n            ];\n            function _mapKeys(event) {\n                var specialKey;\n                _keyMappings.forEach(function (map){\n                    if (map.keyCode === event.keyCode) {\n                        var netModifiers = (event.metaKey ? _META_KEY: 0) +\n                            (event.ctrlKey ? _CTRL_KEY: 0) +\n                            (event.shiftKey ? _SHIFT_KEY: 0) +\n                            (event.altKey ? _ALT_KEY: 0);\n                        if (map.forbiddenModifiers & netModifiers) return;\n                        if (map.mustHaveModifiers.every(function (modifier) { return netModifiers & modifier; })){\n                            specialKey = map.specialKey;\n                        }\n                    }\n                });\n                return specialKey;\n            }\n\n            // set the default to be a paragraph value\n            if(attrs.taDefaultWrap === undefined) attrs.taDefaultWrap = 'p';\n            /* istanbul ignore next: ie specific test */\n            if(attrs.taDefaultWrap === ''){\n                _defaultVal = '';\n                _defaultTest = (_browserDetect.ie === undefined)? '<div><br></div>' : (_browserDetect.ie >= 11)? '<p><br></p>' : (_browserDetect.ie <= 8)? '<P>&nbsp;</P>' : '<p>&nbsp;</p>';\n            }else{\n                _defaultVal = (_browserDetect.ie === undefined || _browserDetect.ie >= 11)?\n                    (attrs.taDefaultWrap.toLowerCase() === 'br' ? '<BR><BR>' : '<' + attrs.taDefaultWrap + '><br></' + attrs.taDefaultWrap + '>') :\n                    (_browserDetect.ie <= 8)?\n                        '<' + attrs.taDefaultWrap.toUpperCase() + '></' + attrs.taDefaultWrap.toUpperCase() + '>' :\n                        '<' + attrs.taDefaultWrap + '></' + attrs.taDefaultWrap + '>';\n                _defaultTest = (_browserDetect.ie === undefined || _browserDetect.ie >= 11)?\n                    (attrs.taDefaultWrap.toLowerCase() === 'br' ? '<br><br>' : '<' + attrs.taDefaultWrap + '><br></' + attrs.taDefaultWrap + '>') :\n                    (_browserDetect.ie <= 8)?\n                        '<' + attrs.taDefaultWrap.toUpperCase() + '>&nbsp;</' + attrs.taDefaultWrap.toUpperCase() + '>' :\n                        '<' + attrs.taDefaultWrap + '>&nbsp;</' + attrs.taDefaultWrap + '>';\n            }\n\n            /* istanbul ignore else */\n            if(!ngModelOptions.$options) ngModelOptions.$options = {}; // ng-model-options support\n\n            var _ensureContentWrapped = function(value) {\n                if (_taBlankTest(value)) return value;\n                var domTest = angular.element(\"<div>\" + value + \"</div>\");\n                //console.log('domTest.children().length():', domTest.children().length);\n                //console.log('_ensureContentWrapped', domTest.children());\n                //console.log(value, attrs.taDefaultWrap);\n                if (domTest.children().length === 0) {\n                    // if we have a <br> and the attrs.taDefaultWrap is a <p> we need to remove the <br>\n                    //value = value.replace(/<br>/i, '');\n                    value = \"<\" + attrs.taDefaultWrap + \">\" + value + \"</\" + attrs.taDefaultWrap + \">\";\n                } else {\n                    var _children = domTest[0].childNodes;\n                    var i;\n                    var _foundBlockElement = false;\n                    for (i = 0; i < _children.length; i++) {\n                        if (_foundBlockElement = _children[i].nodeName.toLowerCase().match(BLOCKELEMENTS)) break;\n                    }\n                    if (!_foundBlockElement) {\n                        value = \"<\" + attrs.taDefaultWrap + \">\" + value + \"</\" + attrs.taDefaultWrap + \">\";\n                    }\n                    else{\n                        value = \"\";\n                        for(i = 0; i < _children.length; i++){\n                            var node = _children[i];\n                            var nodeName = node.nodeName.toLowerCase();\n                            //console.log('node#:', i, 'name:', nodeName);\n                            if(nodeName === '#comment') {\n                                value += '<!--' + node.nodeValue + '-->';\n                            } else if(nodeName === '#text') {\n                                // determine if this is all whitespace, if so, we will leave it as it is.\n                                // otherwise, we will wrap it as it is\n                                var text = node.textContent;\n                                if (!text.trim()) {\n                                    // just whitespace\n                                    value += text;\n                                } else {\n                                    // not pure white space so wrap in <p>...</p> or whatever attrs.taDefaultWrap is set to.\n                                    value += \"<\" + attrs.taDefaultWrap + \">\" + text + \"</\" + attrs.taDefaultWrap + \">\";\n                                }\n                            } else if(!nodeName.match(BLOCKELEMENTS)){\n                                /* istanbul ignore  next: Doesn't seem to trigger on tests */\n                                var _subVal = (node.outerHTML || node.nodeValue);\n                                /* istanbul ignore else: Doesn't seem to trigger on tests, is tested though */\n                                if(_subVal.trim() !== '')\n                                    value += \"<\" + attrs.taDefaultWrap + \">\" + _subVal + \"</\" + attrs.taDefaultWrap + \">\";\n                                else value += _subVal;\n                            } else {\n                                value += node.outerHTML;\n                            }\n                            //console.log(value);\n                        }\n                    }\n                }\n                //console.log(value);\n                return value;\n            };\n\n            if(attrs.taPaste) {\n                _pasteHandler = $parse(attrs.taPaste);\n            }\n\n            element.addClass('ta-bind');\n\n            var _undoKeyupTimeout;\n\n            scope['$undoManager' + (attrs.id || '')] = ngModel.$undoManager = {\n                _stack: [],\n                _index: 0,\n                _max: 1000,\n                push: function(value){\n                    if((typeof value === \"undefined\" || value === null) ||\n                        ((typeof this.current() !== \"undefined\" && this.current() !== null) && value === this.current())) return value;\n                    if(this._index < this._stack.length - 1){\n                        this._stack = this._stack.slice(0,this._index+1);\n                    }\n                    this._stack.push(value);\n                    if(_undoKeyupTimeout) $timeout.cancel(_undoKeyupTimeout);\n                    if(this._stack.length > this._max) this._stack.shift();\n                    this._index = this._stack.length - 1;\n                    return value;\n                },\n                undo: function(){\n                    return this.setToIndex(this._index-1);\n                },\n                redo: function(){\n                    return this.setToIndex(this._index+1);\n                },\n                setToIndex: function(index){\n                    if(index < 0 || index > this._stack.length - 1){\n                        return undefined;\n                    }\n                    this._index = index;\n                    return this.current();\n                },\n                current: function(){\n                    return this._stack[this._index];\n                }\n            };\n\n            // in here we are undoing the converts used elsewhere to prevent the < > and & being displayed when they shouldn't in the code.\n            var _compileHtml = function(){\n                if(_isContentEditable) {\n                    return element[0].innerHTML;\n                }\n                if(_isInputFriendly) {\n                    return element.val();\n                }\n                throw ('textAngular Error: attempting to update non-editable taBind');\n            };\n\n            var selectorClickHandler = function(event){\n                // emit the element-select event, pass the element\n                scope.$emit('ta-element-select', this);\n                event.preventDefault();\n                return false;\n            };\n\n            //used for updating when inserting wrapped elements\n            var _reApplyOnSelectorHandlers = scope['reApplyOnSelectorHandlers' + (attrs.id || '')] = function(){\n                /* istanbul ignore else */\n                if(!_isReadonly) angular.forEach(taSelectableElements, function(selector){\n                    // check we don't apply the handler twice\n                    element.find(selector)\n                        .off('click', selectorClickHandler)\n                        .on('click', selectorClickHandler);\n                });\n            };\n\n            var _setViewValue = function(_val, triggerUndo, skipRender){\n                _skipRender = skipRender || false;\n                if(typeof triggerUndo === \"undefined\" || triggerUndo === null) triggerUndo = true && _isContentEditable; // if not contentEditable then the native undo/redo is fine\n                if(typeof _val === \"undefined\" || _val === null) _val = _compileHtml();\n                if(_taBlankTest(_val)){\n                    // this avoids us from tripping the ng-pristine flag if we click in and out with out typing\n                    if(ngModel.$viewValue !== '') ngModel.$setViewValue('');\n                    if(triggerUndo && ngModel.$undoManager.current() !== '') ngModel.$undoManager.push('');\n                }else{\n                    _reApplyOnSelectorHandlers();\n                    if(ngModel.$viewValue !== _val){\n                        ngModel.$setViewValue(_val);\n                        if(triggerUndo) ngModel.$undoManager.push(_val);\n                    }\n                }\n                ngModel.$render();\n            };\n\n            var _setInnerHTML = function(newval){\n                element[0].innerHTML = newval;\n            };\n\n            var _redoUndoTimeout;\n            var _undo = scope['$undoTaBind' + (attrs.id || '')] = function(){\n                /* istanbul ignore else: can't really test it due to all changes being ignored as well in readonly */\n                if(!_isReadonly && _isContentEditable){\n                    var content = ngModel.$undoManager.undo();\n                    if(typeof content !== \"undefined\" && content !== null){\n                        _setInnerHTML(content);\n                        _setViewValue(content, false);\n                        if(_redoUndoTimeout) $timeout.cancel(_redoUndoTimeout);\n                        _redoUndoTimeout = $timeout(function(){\n                            element[0].focus();\n                            taSelection.setSelectionToElementEnd(element[0]);\n                        }, 1);\n                    }\n                }\n            };\n\n            var _redo = scope['$redoTaBind' + (attrs.id || '')] = function(){\n                /* istanbul ignore else: can't really test it due to all changes being ignored as well in readonly */\n                if(!_isReadonly && _isContentEditable){\n                    var content = ngModel.$undoManager.redo();\n                    if(typeof content !== \"undefined\" && content !== null){\n                        _setInnerHTML(content);\n                        _setViewValue(content, false);\n                        /* istanbul ignore next */\n                        if(_redoUndoTimeout) $timeout.cancel(_redoUndoTimeout);\n                        _redoUndoTimeout = $timeout(function(){\n                            element[0].focus();\n                            taSelection.setSelectionToElementEnd(element[0]);\n                        }, 1);\n                    }\n                }\n            };\n\n            //used for updating when inserting wrapped elements\n            scope['updateTaBind' + (attrs.id || '')] = function(){\n                if(!_isReadonly) _setViewValue(undefined, undefined, true);\n            };\n\n            // catch DOM XSS via taSanitize\n            // Sanitizing both ways is identical\n            var _sanitize = function(unsafe){\n                return (ngModel.$oldViewValue = taSanitize(taFixChrome(unsafe, _keepStyles), ngModel.$oldViewValue, _disableSanitizer));\n            };\n\n            // trigger the validation calls\n            if(element.attr('required')) ngModel.$validators.required = function(modelValue, viewValue) {\n                return !_taBlankTest(modelValue || viewValue);\n            };\n            // parsers trigger from the above keyup function or any other time that the viewValue is updated and parses it for storage in the ngModel\n            ngModel.$parsers.push(_sanitize);\n            ngModel.$parsers.unshift(_ensureContentWrapped);\n            // because textAngular is bi-directional (which is awesome) we need to also sanitize values going in from the server\n            ngModel.$formatters.push(_sanitize);\n            ngModel.$formatters.unshift(_ensureContentWrapped);\n            ngModel.$formatters.unshift(function(value){\n                return ngModel.$undoManager.push(value || '');\n            });\n\n            //this code is used to update the models when data is entered/deleted\n            if(_isInputFriendly){\n                scope.events = {};\n                if(!_isContentEditable){\n                    // if a textarea or input just add in change and blur handlers, everything else is done by angulars input directive\n                    element.on('change blur', scope.events.change = scope.events.blur = function(){\n                        if(!_isReadonly) ngModel.$setViewValue(_compileHtml());\n                    });\n\n                    element.on('keydown', scope.events.keydown = function(event, eventData){\n                        /* istanbul ignore else: this is for catching the jqLite testing*/\n                        if(eventData) angular.extend(event, eventData);\n                        // Reference to http://stackoverflow.com/questions/6140632/how-to-handle-tab-in-textarea\n                        /* istanbul ignore else: otherwise normal functionality */\n                        if(event.keyCode === _TAB_KEYCODE){ // tab was pressed\n                            // get caret position/selection\n                            var start = this.selectionStart;\n                            var end = this.selectionEnd;\n\n                            var value = element.val();\n                            if(event.shiftKey){\n                                // find \\t\n                                var _linebreak = value.lastIndexOf('\\n', start), _tab = value.lastIndexOf('\\t', start);\n                                if(_tab !== -1 && _tab >= _linebreak){\n                                    // set textarea value to: text before caret + tab + text after caret\n                                    element.val(value.substring(0, _tab) + value.substring(_tab + 1));\n\n                                    // put caret at right position again (add one for the tab)\n                                    this.selectionStart = this.selectionEnd = start - 1;\n                                }\n                            }else{\n                                // set textarea value to: text before caret + tab + text after caret\n                                element.val(value.substring(0, start) + \"\\t\" + value.substring(end));\n\n                                // put caret at right position again (add one for the tab)\n                                this.selectionStart = this.selectionEnd = start + 1;\n                            }\n                            // prevent the focus lose\n                            event.preventDefault();\n                        }\n                    });\n\n                    var _repeat = function(string, n){\n                        var result = '';\n                        for(var _n = 0; _n < n; _n++) result += string;\n                        return result;\n                    };\n\n                    // add a forEach function that will work on a NodeList, etc..\n                    var forEach = function (array, callback, scope) {\n                        for (var i= 0; i<array.length; i++) {\n                            callback.call(scope, i, array[i]);\n                        }\n                    };\n\n                    // handle <ul> or <ol> nodes\n                    var recursiveListFormat = function(listNode, tablevel){\n                        var _html = '';\n                        var _subnodes = listNode.childNodes;\n                        tablevel++;\n                        // tab out and add the <ul> or <ol> html piece\n                        _html += _repeat('\\t', tablevel-1) + listNode.outerHTML.substring(0, 4);\n                        forEach(_subnodes, function (index, node) {\n                            /* istanbul ignore next: browser catch */\n                            var nodeName = node.nodeName.toLowerCase();\n                            if (nodeName === '#comment') {\n                                _html += '<!--' + node.nodeValue + '-->';\n                                return;\n                            }\n                            if (nodeName === '#text') {\n                                _html += node.textContent;\n                                return;\n                            }\n                            /* istanbul ignore next: not tested, and this was original code -- so not wanting to possibly cause an issue, leaving it... */\n                            if(!node.outerHTML) {\n                                // no html to add\n                                return;\n                            }\n                            if(nodeName === 'ul' || nodeName === 'ol') {\n                                _html += '\\n' + recursiveListFormat(node, tablevel);\n                            }\n                            else {\n                                // no reformatting within this subnode, so just do the tabing...\n                                _html += '\\n' + _repeat('\\t', tablevel) + node.outerHTML;\n                            }\n                        });\n                        // now add on the </ol> or </ul> piece\n                        _html += '\\n' + _repeat('\\t', tablevel-1) + listNode.outerHTML.substring(listNode.outerHTML.lastIndexOf('<'));\n                        return _html;\n                    };\n                    // handle formating of something like:\n                    // <ol><!--First comment-->\n                    //  <li>Test Line 1<!--comment test list 1--></li>\n                    //    <ul><!--comment ul-->\n                    //      <li>Nested Line 1</li>\n                    //        <!--comment between nested lines--><li>Nested Line 2</li>\n                    //    </ul>\n                    //  <li>Test Line 3</li>\n                    // </ol>\n                    ngModel.$formatters.unshift(function(htmlValue){\n                        // tabulate the HTML so it looks nicer\n                        //\n                        // first get a list of the nodes...\n                        // we do this by using the element parser...\n                        //\n                        // doing this -- which is simpiler -- breaks our tests...\n                        //var _nodes=angular.element(htmlValue);\n                        var _nodes = angular.element('<div>' + htmlValue + '</div>')[0].childNodes;\n                        if(_nodes.length > 0){\n                            // do the reformatting of the layout...\n                            htmlValue = '';\n                            forEach(_nodes, function (index, node) {\n                                var nodeName = node.nodeName.toLowerCase();\n                                if (nodeName === '#comment') {\n                                    htmlValue += '<!--' + node.nodeValue + '-->';\n                                    return;\n                                }\n                                if (nodeName === '#text') {\n                                    htmlValue += node.textContent;\n                                    return;\n                                }\n                                /* istanbul ignore next: not tested, and this was original code -- so not wanting to possibly cause an issue, leaving it... */\n                                if(!node.outerHTML)\n                                {\n                                    // nothing to format!\n                                    return;\n                                }\n                                if(htmlValue.length > 0) {\n                                    // we aready have some content, so drop to a new line\n                                    htmlValue += '\\n';\n                                }\n                                if(nodeName === 'ul' || nodeName === 'ol') {\n                                    // okay a set of list stuff we want to reformat in a nested way\n                                    htmlValue += '' + recursiveListFormat(node, 0);\n                                }\n                                else {\n                                    // just use the original without any additional formating\n                                    htmlValue += '' + node.outerHTML;\n                                }\n                            });\n                        }\n                        return htmlValue;\n                    });\n                }else{\n                    // all the code specific to contenteditable divs\n                    var _processingPaste = false;\n                    /* istanbul ignore next: phantom js cannot test this for some reason */\n                    var processpaste = function(text) {\n                       var _isOneNote = text!==undefined? text.match(/content=[\"']*OneNote.File/i): false;\n                        /* istanbul ignore else: don't care if nothing pasted */\n                        //console.log(text);\n                        if(text && text.trim().length){\n                            // test paste from word/microsoft product\n                            if(text.match(/class=[\"']*Mso(Normal|List)/i) || text.match(/content=[\"']*Word.Document/i) || text.match(/content=[\"']*OneNote.File/i)){\n                                var textFragment = text.match(/<!--StartFragment-->([\\s\\S]*?)<!--EndFragment-->/i);\n                                if(!textFragment) textFragment = text;\n                                else textFragment = textFragment[1];\n                                textFragment = textFragment.replace(/<o:p>[\\s\\S]*?<\\/o:p>/ig, '').replace(/class=([\"']|)MsoNormal([\"']|)/ig, '');\n                                var dom = angular.element(\"<div>\" + textFragment + \"</div>\");\n                                var targetDom = angular.element(\"<div></div>\");\n                                var _list = {\n                                    element: null,\n                                    lastIndent: [],\n                                    lastLi: null,\n                                    isUl: false\n                                };\n                                _list.lastIndent.peek = function(){\n                                    var n = this.length;\n                                    if (n>0) return this[n-1];\n                                };\n                                var _resetList = function(isUl){\n                                    _list.isUl = isUl;\n                                    _list.element = angular.element(isUl ? \"<ul>\" : \"<ol>\");\n                                    _list.lastIndent = [];\n                                    _list.lastIndent.peek = function(){\n                                        var n = this.length;\n                                        if (n>0) return this[n-1];\n                                    };\n                                    _list.lastLevelMatch = null;\n                                };\n                                for(var i = 0; i <= dom[0].childNodes.length; i++){\n                                    if(!dom[0].childNodes[i] || dom[0].childNodes[i].nodeName === \"#text\"){\n                                        continue;\n                                    } else {\n                                        var tagName = dom[0].childNodes[i].tagName.toLowerCase();\n                                        if(tagName !== 'p' &&\n                                            tagName !== 'ul' &&\n                                            tagName !== 'h1' &&\n                                            tagName !== 'h2' &&\n                                            tagName !== 'h3' &&\n                                            tagName !== 'h4' &&\n                                            tagName !== 'h5' &&\n                                            tagName !== 'h6' &&\n                                            tagName !== 'table'){\n                                            continue;\n                                        }\n                                    }\n                                    var el = angular.element(dom[0].childNodes[i]);\n                                    var _listMatch = (el.attr('class') || '').match(/MsoList(Bullet|Number|Paragraph)(CxSp(First|Middle|Last)|)/i);\n\n                                    if(_listMatch){\n                                        if(el[0].childNodes.length < 2 || el[0].childNodes[1].childNodes.length < 1){\n                                            continue;\n                                        }\n                                        var isUl = _listMatch[1].toLowerCase() === 'bullet' || (_listMatch[1].toLowerCase() !== 'number' && !(/^[^0-9a-z<]*[0-9a-z]+[^0-9a-z<>]</i.test(el[0].childNodes[1].innerHTML) || /^[^0-9a-z<]*[0-9a-z]+[^0-9a-z<>]</i.test(el[0].childNodes[1].childNodes[0].innerHTML)));\n                                        var _indentMatch = (el.attr('style') || '').match(/margin-left:([\\-\\.0-9]*)/i);\n                                        var indent = parseFloat((_indentMatch)?_indentMatch[1]:0);\n                                        var _levelMatch = (el.attr('style') || '').match(/mso-list:l([0-9]+) level([0-9]+) lfo[0-9+]($|;)/i);\n                                        // prefers the mso-list syntax\n\n                                        if(_levelMatch && _levelMatch[2]) indent = parseInt(_levelMatch[2]);\n\n                                        if ((_levelMatch && (!_list.lastLevelMatch || _levelMatch[1] !== _list.lastLevelMatch[1])) || !_listMatch[3] || _listMatch[3].toLowerCase() === 'first' || (_list.lastIndent.peek() === null) || (_list.isUl !== isUl && _list.lastIndent.peek() === indent)) {\n                                            _resetList(isUl);\n                                            targetDom.append(_list.element);\n                                        } else if (_list.lastIndent.peek() != null && _list.lastIndent.peek() < indent){\n                                            _list.element = angular.element(isUl ? '<ul>' : '<ol>');\n                                            _list.lastLi.append(_list.element);\n                                        } else if (_list.lastIndent.peek() != null && _list.lastIndent.peek() > indent){\n                                            while(_list.lastIndent.peek() != null && _list.lastIndent.peek() > indent){\n                                                if(_list.element.parent()[0].tagName.toLowerCase() === 'li'){\n                                                    _list.element = _list.element.parent();\n                                                    continue;\n                                                }else if(/[uo]l/i.test(_list.element.parent()[0].tagName.toLowerCase())){\n                                                    _list.element = _list.element.parent();\n                                                }else{ // else it's it should be a sibling\n                                                    break;\n                                                }\n                                                _list.lastIndent.pop();\n                                            }\n                                            _list.isUl = _list.element[0].tagName.toLowerCase() === 'ul';\n                                            if (isUl !== _list.isUl) {\n                                                _resetList(isUl);\n                                                targetDom.append(_list.element);\n                                            }\n                                        }\n\n                                        _list.lastLevelMatch = _levelMatch;\n                                        if(indent !== _list.lastIndent.peek()) _list.lastIndent.push(indent);\n                                        _list.lastLi = angular.element('<li>');\n                                        _list.element.append(_list.lastLi);\n                                        _list.lastLi.html(el.html().replace(/<!(--|)\\[if !supportLists\\](--|)>[\\s\\S]*?<!(--|)\\[endif\\](--|)>/ig, ''));\n                                        el.remove();\n                                    }else{\n                                        _resetList(false);\n                                        targetDom.append(el);\n                                    }\n                                }\n                                var _unwrapElement = function(node){\n                                    node = angular.element(node);\n                                    for(var _n = node[0].childNodes.length - 1; _n >= 0; _n--) node.after(node[0].childNodes[_n]);\n                                    node.remove();\n                                };\n\n                                angular.forEach(targetDom.find('span'), function(node){\n                                    node.removeAttribute('lang');\n                                    if(node.attributes.length <= 0) _unwrapElement(node);\n                                });\n                                angular.forEach(targetDom.find('font'), _unwrapElement);\n\n                                text = targetDom.html();\n                                if(_isOneNote){\n                                    text = targetDom.html() || dom.html();\n                                }\n                                // LF characters instead of spaces in some spots and they are replaced by '/n', so we need to just swap them to spaces\n                                text = text.replace(/\\n/g, ' ');\n                            }else{\n                                // remove unnecessary chrome insert\n                                text = text.replace(/<(|\\/)meta[^>]*?>/ig, '');\n                                if(text.match(/<[^>]*?(ta-bind)[^>]*?>/)){\n                                    // entire text-angular or ta-bind has been pasted, REMOVE AT ONCE!!\n                                    if(text.match(/<[^>]*?(text-angular)[^>]*?>/)){\n                                        var _el = angular.element('<div>' + text + '</div>');\n                                        _el.find('textarea').remove();\n                                        for(var _b = 0; _b < binds.length; _b++){\n                                            var _target = binds[_b][0].parentNode.parentNode;\n                                            for(var _c = 0; _c < binds[_b][0].childNodes.length; _c++){\n                                                _target.parentNode.insertBefore(binds[_b][0].childNodes[_c], _target);\n                                            }\n                                            _target.parentNode.removeChild(_target);\n                                        }\n                                        text = _el.html().replace('<br class=\"Apple-interchange-newline\">', '');\n                                    }\n                                }else if(text.match(/^<span/)){\n                                    // in case of pasting only a span - chrome paste, remove them. THis is just some wierd formatting\n                                    // if we remove the '<span class=\"Apple-converted-space\"> </span>' here we destroy the spacing\n                                    // on paste from even ourselves!\n                                    if (!text.match(/<span class=(\\\"Apple-converted-space\\\"|\\'Apple-converted-space\\')>.<\\/span>/ig)) {\n                                        text = text.replace(/<(|\\/)span[^>]*?>/ig, '');\n                                    }\n                                }\n                                // Webkit on Apple tags\n                                text = text.replace(/<br class=\"Apple-interchange-newline\"[^>]*?>/ig, '').replace(/<span class=\"Apple-converted-space\">( |&nbsp;)<\\/span>/ig, '&nbsp;');\n                            }\n\n                            if (/<li(\\s.*)?>/i.test(text) && /(<ul(\\s.*)?>|<ol(\\s.*)?>).*<li(\\s.*)?>/i.test(text) === false) {\n                                // insert missing parent of li element\n                                text = text.replace(/<li(\\s.*)?>.*<\\/li(\\s.*)?>/i, '<ul>$&</ul>');\n                            }\n\n                            // parse whitespace from plaintext input, starting with preceding spaces that get stripped on paste\n                            text = text.replace(/^[ |\\u00A0]+/gm, function (match) {\n                                var result = '';\n                                for (var i = 0; i < match.length; i++) {\n                                    result += '&nbsp;';\n                                }\n                                return result;\n                            }).replace(/\\n|\\r\\n|\\r/g, '<br />').replace(/\\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;');\n\n                            if(_pasteHandler) text = _pasteHandler(scope, {$html: text}) || text;\n\n                            // turn span vertical-align:super into <sup></sup>\n                            text = text.replace(/<span style=(\"|')([^<]*?)vertical-align\\s*:\\s*super;?([^>]*?)(\"|')>([^<]+?)<\\/span>/g, \"<sup style='$2$3'>$5</sup>\");\n\n                            text = taSanitize(text, '', _disableSanitizer);\n                            //console.log('DONE\\n', text);\n\n                            taSelection.insertHtml(text, element[0]);\n                            $timeout(function(){\n                                ngModel.$setViewValue(_compileHtml());\n                                _processingPaste = false;\n                                element.removeClass('processing-paste');\n                            }, 0);\n                        }else{\n                            _processingPaste = false;\n                            element.removeClass('processing-paste');\n                        }\n                    };\n\n                    element.on('paste', scope.events.paste = function(e, eventData){\n                        /* istanbul ignore else: this is for catching the jqLite testing*/\n                        if(eventData) angular.extend(e, eventData);\n                        if(_isReadonly || _processingPaste){\n                            e.stopPropagation();\n                            e.preventDefault();\n                            return false;\n                        }\n\n                        // Code adapted from http://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser/6804718#6804718\n                        _processingPaste = true;\n                        element.addClass('processing-paste');\n                        var pastedContent;\n                        var clipboardData = (e.originalEvent || e).clipboardData;\n                        /* istanbul ignore next: Handle legacy IE paste */\n                        if ( !clipboardData && window.clipboardData && window.clipboardData.getData ){\n                            pastedContent = window.clipboardData.getData(\"Text\");\n                            processpaste(pastedContent);\n                            e.stopPropagation();\n                            e.preventDefault();\n                            return false;\n                        }\n                        if (clipboardData && clipboardData.getData && clipboardData.types.length > 0) {// Webkit - get data from clipboard, put into editdiv, cleanup, then cancel event\n                            var _types = \"\";\n                            for(var _t = 0; _t < clipboardData.types.length; _t++){\n                                _types += \" \" + clipboardData.types[_t];\n                            }\n                            /* istanbul ignore next: browser tests */\n                            if (/text\\/html/i.test(_types)) {\n                                pastedContent = clipboardData.getData('text/html');\n                            } else if (/text\\/plain/i.test(_types)) {\n                                pastedContent = clipboardData.getData('text/plain');\n                            }\n                            processpaste(pastedContent);\n                            e.stopPropagation();\n                            e.preventDefault();\n                            return false;\n                        } else {// Everything else - empty editdiv and allow browser to paste content into it, then cleanup\n                            var _savedSelection = rangy.saveSelection(),\n                                _tempDiv = angular.element('<div class=\"ta-hidden-input\" contenteditable=\"true\"></div>');\n                            $document.find('body').append(_tempDiv);\n                            _tempDiv[0].focus();\n                            $timeout(function(){\n                                // restore selection\n                                rangy.restoreSelection(_savedSelection);\n                                processpaste(_tempDiv[0].innerHTML);\n                                element[0].focus();\n                                _tempDiv.remove();\n                            }, 0);\n                        }\n                    });\n                    element.on('cut', scope.events.cut = function(e){\n                        // timeout to next is needed as otherwise the paste/cut event has not finished actually changing the display\n                        if(!_isReadonly) $timeout(function(){\n                            ngModel.$setViewValue(_compileHtml());\n                        }, 0);\n                        else e.preventDefault();\n                    });\n\n                    element.on('keydown', scope.events.keydown = function(event, eventData){\n                        /* istanbul ignore else: this is for catching the jqLite testing*/\n                        if(eventData) angular.extend(event, eventData);\n                        if (event.keyCode === _SHIFT_KEYCODE) {\n                            taSelection.setStateShiftKey(true);\n                        } else {\n                            taSelection.setStateShiftKey(false);\n                        }\n                        event.specialKey = _mapKeys(event);\n                        var userSpecialKey;\n                        /* istanbul ignore next: difficult to test */\n                        taOptions.keyMappings.forEach(function (mapping) {\n                            if (event.specialKey === mapping.commandKeyCode) {\n                                // taOptions has remapped this binding... so\n                                // we disable our own\n                                event.specialKey = undefined;\n                            }\n                            if (mapping.testForKey(event)) {\n                                userSpecialKey = mapping.commandKeyCode;\n                            }\n                            if ((mapping.commandKeyCode === 'UndoKey') || (mapping.commandKeyCode === 'RedoKey')) {\n                                // this is necessary to fully stop the propagation.\n                                if (!mapping.enablePropagation) {\n                                    event.preventDefault();\n                                }\n                            }\n                        });\n                        /* istanbul ignore next: difficult to test */\n                        if (typeof userSpecialKey !== 'undefined') {\n                            event.specialKey = userSpecialKey;\n                        }\n                        /* istanbul ignore next: difficult to test as can't seem to select */\n                        if ((typeof event.specialKey !== 'undefined') && (\n                                event.specialKey !== 'UndoKey' || event.specialKey !== 'RedoKey'\n                            )) {\n                            event.preventDefault();\n                            textAngularManager.sendKeyCommand(scope, event);\n                        }\n                        /* istanbul ignore else: readonly check */\n                        if(!_isReadonly){\n                            if (event.specialKey==='UndoKey') {\n                                _undo();\n                                event.preventDefault();\n                            }\n                            if (event.specialKey==='RedoKey') {\n                                _redo();\n                                event.preventDefault();\n                            }\n                            /* istanbul ignore next: difficult to test as can't seem to select */\n                            if(event.keyCode === _ENTER_KEYCODE && !event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey)\n                            {\n                                var contains = function(a, obj) {\n                                    for (var i = 0; i < a.length; i++) {\n                                        if (a[i] === obj) {\n                                            return true;\n                                        }\n                                    }\n                                    return false;\n                                };\n                                var $selection;\n                                var selection = taSelection.getSelectionElement();\n                                // shifted to nodeName here from tagName since it is more widely supported see: http://stackoverflow.com/questions/4878484/difference-between-tagname-and-nodename\n                                if(!selection.nodeName.match(VALIDELEMENTS)) return;\n                                var _new = angular.element(_defaultVal);\n                                // if we are in the last element of a blockquote, or ul or ol and the element is blank\n                                // we need to pull the element outside of the said type\n                                var moveOutsideElements = ['blockquote', 'ul', 'ol'];\n                                if (contains(moveOutsideElements, selection.parentNode.tagName.toLowerCase())) {\n                                    if (/^<br(|\\/)>$/i.test(selection.innerHTML.trim()) && !selection.nextSibling) {\n                                        // if last element is blank, pull element outside.\n                                        $selection = angular.element(selection);\n                                        var _parent = $selection.parent();\n                                        _parent.after(_new);\n                                        $selection.remove();\n                                        if (_parent.children().length === 0) _parent.remove();\n                                        taSelection.setSelectionToElementStart(_new[0]);\n                                        event.preventDefault();\n                                    }\n                                    if (/^<[^>]+><br(|\\/)><\\/[^>]+>$/i.test(selection.innerHTML.trim())) {\n                                        $selection = angular.element(selection);\n                                        $selection.after(_new);\n                                        $selection.remove();\n                                        taSelection.setSelectionToElementStart(_new[0]);\n                                        event.preventDefault();\n                                    }\n                                }\n                            }\n                        }\n                    });\n                    var _keyupTimeout;\n                    element.on('keyup', scope.events.keyup = function(event, eventData){\n                        /* istanbul ignore else: this is for catching the jqLite testing*/\n                        if(eventData) angular.extend(event, eventData);\n                        taSelection.setStateShiftKey(false);\t// clear the ShiftKey state\n                        /* istanbul ignore next: FF specific bug fix */\n                        if (event.keyCode === _TAB_KEYCODE) {\n                            var _selection = taSelection.getSelection();\n                            if(_selection.start.element === element[0] && element.children().length) taSelection.setSelectionToElementStart(element.children()[0]);\n                            return;\n                        }\n                        // we do this here during the 'keyup' so that the browser has already moved the slection by one character...\n                        if (event.keyCode === _LEFT_ARROW_KEYCODE && !event.shiftKey) {\n                            taSelection.updateLeftArrowKey(element);\n                        }\n                        // we do this here during the 'keyup' so that the browser has already moved the slection by one character...\n                        if (event.keyCode === _RIGHT_ARROW_KEYCODE && !event.shiftKey) {\n                            taSelection.updateRightArrowKey(element);\n                        }\n                        if(_undoKeyupTimeout) $timeout.cancel(_undoKeyupTimeout);\n                        if(!_isReadonly && !BLOCKED_KEYS.test(event.keyCode)){\n                            /* istanbul ignore next: Ignore any _ENTER_KEYCODE that has ctrlKey, metaKey or alKey */\n                            if (event.keyCode === _ENTER_KEYCODE && (event.ctrlKey || event.metaKey || event.altKey)) {\n                                // we ignore any ENTER_\tKEYCODE that is anything but plain or a shift one...\n                            } else {\n                                // if enter - insert new taDefaultWrap, if shift+enter insert <br/>\n                                if(_defaultVal !== '' && _defaultVal !== '<BR><BR>' && event.keyCode === _ENTER_KEYCODE && !event.ctrlKey && !event.metaKey && !event.altKey){\n                                    var selection = taSelection.getSelectionElement();\n                                    while(!selection.nodeName.match(VALIDELEMENTS) && selection !== element[0]){\n                                        selection = selection.parentNode;\n                                    }\n                                    if(!event.shiftKey){\n                                        // new paragraph, br should be caught correctly\n                                        // shifted to nodeName here from tagName since it is more widely supported see: http://stackoverflow.com/questions/4878484/difference-between-tagname-and-nodename\n                                        //console.log('Enter', selection.nodeName, attrs.taDefaultWrap, selection.innerHTML.trim());\n                                        if(selection.tagName.toLowerCase() !==\n                                            attrs.taDefaultWrap &&\n                                            selection.nodeName.toLowerCase() !== 'li' &&\n                                            (selection.innerHTML.trim() === '' || selection.innerHTML.trim() === '<br>')\n                                        ) {\n                                            // Chrome starts with a <div><br></div> after an EnterKey\n                                            // so we replace this with the _defaultVal\n                                            var _new = angular.element(_defaultVal);\n                                            angular.element(selection).replaceWith(_new);\n                                            taSelection.setSelectionToElementStart(_new[0]);\n                                        }\n                                    } else {\n                                        // shift + Enter\n                                        var tagName = selection.tagName.toLowerCase();\n                                        //console.log('Shift+Enter', selection.tagName, attrs.taDefaultWrap, selection.innerHTML.trim());\n                                        // For an LI: We see: LI p ....<br><br>\n                                        // For a P: We see: P p ....<br><br>\n                                        // on Safari, the browser ignores the Shift+Enter and acts just as an Enter Key\n                                        // For an LI: We see: LI p <br>\n                                        // For a P: We see: P p <br>\n                                        if((tagName === attrs.taDefaultWrap ||\n                                            tagName === 'li' ||\n                                            tagName === 'pre' ||\n                                            tagName === 'div') &&\n                                            !/.+<br><br>/.test(selection.innerHTML.trim())) {\n                                            var ps = selection.previousSibling;\n                                            //console.log('wrong....', ps);\n                                            // we need to remove this selection and fix the previousSibling up...\n                                            if (ps) {\n                                                ps.innerHTML = ps.innerHTML + '<br><br>';\n                                                angular.element(selection).remove();\n                                                taSelection.setSelectionToElementEnd(ps);\n                                            }\n                                        }\n                                    }\n                                }\n                                var val = _compileHtml();\n                                if(_defaultVal !== '' && (val.trim() === '' || val.trim() === '<br>')){\n                                    _setInnerHTML(_defaultVal);\n                                    taSelection.setSelectionToElementStart(element.children()[0]);\n                                }else if(val.substring(0, 1) !== '<' && attrs.taDefaultWrap !== ''){\n                                    /* we no longer do this, since there can be comments here and white space\n                                     var _savedSelection = rangy.saveSelection();\n                                     val = _compileHtml();\n                                     val = \"<\" + attrs.taDefaultWrap + \">\" + val + \"</\" + attrs.taDefaultWrap + \">\";\n                                     _setInnerHTML(val);\n                                     rangy.restoreSelection(_savedSelection);\n                                     */\n                                }\n                                var triggerUndo = _lastKey !== event.keyCode && UNDO_TRIGGER_KEYS.test(event.keyCode);\n                                if(_keyupTimeout) $timeout.cancel(_keyupTimeout);\n                                _keyupTimeout = $timeout(function() {\n                                    _setViewValue(val, triggerUndo, true);\n                                }, ngModelOptions.$options.debounce || 400);\n                                if(!triggerUndo) _undoKeyupTimeout = $timeout(function(){ ngModel.$undoManager.push(val); }, 250);\n                                _lastKey = event.keyCode;\n                            }\n                        }\n                    });\n\n                    // when there is a change from a spelling correction in the browser, the only\n                    // change that is seen is a 'input' and the $watch('html') sees nothing... So\n                    // we added this element.on('input') to catch this change and call the _setViewValue()\n                    // so the ngModel is updated and all works as it should.\n                    var _inputTimeout;\n                    element.on('input', function() {\n                        if (_compileHtml() !== ngModel.$viewValue) {\n                            // we wait a time now to allow the natural $watch('html') to handle this change\n                            // and then after a 1 second delay, if there is still a difference we will do the\n                            // _setViewValue() call.\n                            /* istanbul ignore if: can't test */\n                            if(_inputTimeout) $timeout.cancel(_inputTimeout);\n                            /* istanbul ignore next: cant' test? */\n                            _inputTimeout = $timeout(function() {\n                                var _savedSelection = rangy.saveSelection();\n                                var _val = _compileHtml();\n                                if (_val !== ngModel.$viewValue) {\n                                    //console.log('_setViewValue');\n                                    //console.log('old:', ngModel.$viewValue);\n                                    //console.log('new:', _val);\n                                    _setViewValue(_val, true);\n                                }\n                                // if the savedSelection marker is gone at this point, we cannot restore the selection!!!\n                                //console.log('rangy.restoreSelection', ngModel.$viewValue.length, _savedSelection);\n                                if (ngModel.$viewValue.length !== 0) {\n                                    rangy.restoreSelection(_savedSelection);\n                                }\n                            }, 1000);\n                        }\n                    });\n\n                    element.on('blur', scope.events.blur = function(){\n                        _focussed = false;\n                        /* istanbul ignore else: if readonly don't update model */\n                        if(!_isReadonly){\n                            _setViewValue(undefined, undefined, true);\n                        }else{\n                            _skipRender = true; // don't redo the whole thing, just check the placeholder logic\n                            ngModel.$render();\n                        }\n                    });\n\n                    // Placeholders not supported on ie 8 and below\n                    if(attrs.placeholder && (_browserDetect.ie > 8 || _browserDetect.ie === undefined)){\n                        var rule;\n                        if(attrs.id) rule = addCSSRule('#' + attrs.id + '.placeholder-text:before', 'content: \"' + attrs.placeholder + '\"');\n                        else throw('textAngular Error: An unique ID is required for placeholders to work');\n\n                        scope.$on('$destroy', function(){\n                            removeCSSRule(rule);\n                        });\n                    }\n\n                    element.on('focus', scope.events.focus = function(){\n                        _focussed = true;\n                        element.removeClass('placeholder-text');\n                        _reApplyOnSelectorHandlers();\n                    });\n\n                    element.on('mouseup', scope.events.mouseup = function(){\n                        var _selection = taSelection.getSelection();\n                        if(_selection && _selection.start.element === element[0] && element.children().length) taSelection.setSelectionToElementStart(element.children()[0]);\n                    });\n\n                    // prevent propagation on mousedown in editor, see #206\n                    element.on('mousedown', scope.events.mousedown = function(event, eventData){\n                        /* istanbul ignore else: this is for catching the jqLite testing*/\n                        if(eventData) angular.extend(event, eventData);\n                        event.stopPropagation();\n                    });\n                }\n            }\n\n            var fileDropHandler = function(event, eventData){\n                /* istanbul ignore else: this is for catching the jqLite testing*/\n                if(eventData) angular.extend(event, eventData);\n                // emit the drop event, pass the element, preventing should be done elsewhere\n                if(!dropFired && !_isReadonly){\n                    dropFired = true;\n                    var dataTransfer;\n                    if(event.originalEvent) dataTransfer = event.originalEvent.dataTransfer;\n                    else dataTransfer = event.dataTransfer;\n                    scope.$emit('ta-drop-event', this, event, dataTransfer);\n                    $timeout(function(){\n                        dropFired = false;\n                        _setViewValue(undefined, undefined, true);\n                    }, 100);\n                }\n            };\n\n            var _renderTimeout;\n            var _renderInProgress = false;\n            // changes to the model variable from outside the html/text inputs\n            ngModel.$render = function(){\n                /* istanbul ignore if: Catches rogue renders, hard to replicate in tests */\n                if(_renderInProgress) return;\n                else _renderInProgress = true;\n                // catch model being null or undefined\n                var val = ngModel.$viewValue || '';\n                // if the editor isn't focused it needs to be updated, otherwise it's receiving user input\n                if(!_skipRender){\n                    /* istanbul ignore else: in other cases we don't care */\n                    if(_isContentEditable && _focussed){\n                        // update while focussed\n                        element.removeClass('placeholder-text');\n                        /* istanbul ignore next: don't know how to test this */\n                        if(_renderTimeout) $timeout.cancel(_renderTimeout);\n                        _renderTimeout = $timeout(function(){\n                            /* istanbul ignore if: Can't be bothered testing this... */\n                            if(!_focussed){\n                                element[0].focus();\n                                taSelection.setSelectionToElementEnd(element.children()[element.children().length - 1]);\n                            }\n                            _renderTimeout = undefined;\n                        }, 1);\n                    }\n                    if(_isContentEditable){\n                        // WYSIWYG Mode\n                        if(attrs.placeholder){\n                            if(val === ''){\n                                // blank\n                                _setInnerHTML(_defaultVal);\n                            }else{\n                                // not-blank\n                                _setInnerHTML(val);\n                            }\n                        }else{\n                            _setInnerHTML((val === '') ? _defaultVal : val);\n                        }\n                        // if in WYSIWYG and readOnly we kill the use of links by clicking\n                        if(!_isReadonly){\n                            _reApplyOnSelectorHandlers();\n                            element.on('drop', fileDropHandler);\n                        }else{\n                            element.off('drop', fileDropHandler);\n                        }\n                    }else if(element[0].tagName.toLowerCase() !== 'textarea' && element[0].tagName.toLowerCase() !== 'input'){\n                        // make sure the end user can SEE the html code as a display. This is a read-only display element\n                        _setInnerHTML(taApplyCustomRenderers(val));\n                    }else{\n                        // only for input and textarea inputs\n                        element.val(val);\n                    }\n                }\n                if(_isContentEditable && attrs.placeholder){\n                    if(val === ''){\n                        if(_focussed) element.removeClass('placeholder-text');\n                        else element.addClass('placeholder-text');\n                    }else{\n                        element.removeClass('placeholder-text');\n                    }\n                }\n                _renderInProgress = _skipRender = false;\n            };\n\n            if(attrs.taReadonly){\n                //set initial value\n                _isReadonly = scope.$eval(attrs.taReadonly);\n                if(_isReadonly){\n                    element.addClass('ta-readonly');\n                    // we changed to readOnly mode (taReadonly='true')\n                    if(element[0].tagName.toLowerCase() === 'textarea' || element[0].tagName.toLowerCase() === 'input'){\n                        element.attr('disabled', 'disabled');\n                    }\n                    if(element.attr('contenteditable') !== undefined && element.attr('contenteditable')){\n                        element.removeAttr('contenteditable');\n                    }\n                }else{\n                    element.removeClass('ta-readonly');\n                    // we changed to NOT readOnly mode (taReadonly='false')\n                    if(element[0].tagName.toLowerCase() === 'textarea' || element[0].tagName.toLowerCase() === 'input'){\n                        element.removeAttr('disabled');\n                    }else if(_isContentEditable){\n                        element.attr('contenteditable', 'true');\n                    }\n                }\n                // taReadonly only has an effect if the taBind element is an input or textarea or has contenteditable='true' on it.\n                // Otherwise it is readonly by default\n                scope.$watch(attrs.taReadonly, function(newVal, oldVal){\n                    if(oldVal === newVal) return;\n                    if(newVal){\n                        element.addClass('ta-readonly');\n                        // we changed to readOnly mode (taReadonly='true')\n                        if(element[0].tagName.toLowerCase() === 'textarea' || element[0].tagName.toLowerCase() === 'input'){\n                            element.attr('disabled', 'disabled');\n                        }\n                        if(element.attr('contenteditable') !== undefined && element.attr('contenteditable')){\n                            element.removeAttr('contenteditable');\n                        }\n                        // turn ON selector click handlers\n                        angular.forEach(taSelectableElements, function(selector){\n                            element.find(selector).on('click', selectorClickHandler);\n                        });\n                        element.off('drop', fileDropHandler);\n                    }else{\n                        element.removeClass('ta-readonly');\n                        // we changed to NOT readOnly mode (taReadonly='false')\n                        if(element[0].tagName.toLowerCase() === 'textarea' || element[0].tagName.toLowerCase() === 'input'){\n                            element.removeAttr('disabled');\n                        }else if(_isContentEditable){\n                            element.attr('contenteditable', 'true');\n                        }\n                        // remove the selector click handlers\n                        angular.forEach(taSelectableElements, function(selector){\n                            element.find(selector).off('click', selectorClickHandler);\n                        });\n                        element.on('drop', fileDropHandler);\n                    }\n                    _isReadonly = newVal;\n                });\n            }\n\n            // Initialise the selectableElements\n            // if in WYSIWYG and readOnly we kill the use of links by clicking\n            if(_isContentEditable && !_isReadonly){\n                angular.forEach(taSelectableElements, function(selector){\n                    element.find(selector).on('click', selectorClickHandler);\n                });\n                element.on('drop', fileDropHandler);\n            }\n        }\n    };\n}]);\n\n// this global var is used to prevent multiple fires of the drop event. Needs to be global to the textAngular file.\nvar dropFired = false;\nvar textAngular = angular.module(\"textAngular\", ['ngSanitize', 'textAngularSetup', 'textAngular.factories', 'textAngular.DOM', 'textAngular.validators', 'textAngular.taBind']); //This makes ngSanitize required\n\ntextAngular.config([function(){\n    // clear taTools variable. Just catches testing and any other time that this config may run multiple times...\n    angular.forEach(taTools, function(value, key){ delete taTools[key];\t});\n}]);\n\ntextAngular.directive(\"textAngular\", [\n    '$compile', '$timeout', 'taOptions', 'taSelection', 'taExecCommand',\n    'textAngularManager', '$document', '$animate', '$log', '$q', '$parse',\n    function($compile, $timeout, taOptions, taSelection, taExecCommand,\n        textAngularManager, $document, $animate, $log, $q, $parse){\n        return {\n            require: '?ngModel',\n            scope: {},\n            restrict: \"EA\",\n            priority: 2, // So we override validators correctly\n            link: function(scope, element, attrs, ngModel){\n                // all these vars should not be accessable outside this directive\n                var _keydown, _keyup, _keypress, _mouseup, _focusin, _focusout,\n                    _originalContents, _editorFunctions,\n                    _serial = (attrs.serial) ? attrs.serial : Math.floor(Math.random() * 10000000000000000),\n                    _taExecCommand, _resizeMouseDown, _updateSelectedStylesTimeout;\n                var _resizeTimeout;\n\n                scope._name = (attrs.name) ? attrs.name : 'textAngularEditor' + _serial;\n\n                var oneEvent = function(_element, event, action){\n                    $timeout(function(){\n                        _element.one(event, action);\n                    }, 100);\n                };\n                _taExecCommand = taExecCommand(attrs.taDefaultWrap);\n                // get the settings from the defaults and add our specific functions that need to be on the scope\n                angular.extend(scope, angular.copy(taOptions), {\n                    // wraps the selection in the provided tag / execCommand function. Should only be called in WYSIWYG mode.\n                    wrapSelection: function(command, opt, isSelectableElementTool){\n                        // we restore the saved selection that was saved when focus was lost\n                        /* NOT FUNCTIONAL YET */\n                        /* textAngularManager.restoreFocusSelection(scope._name, scope); */\n                        if(command.toLowerCase() === \"undo\"){\n                            scope['$undoTaBindtaTextElement' + _serial]();\n                        }else if(command.toLowerCase() === \"redo\"){\n                            scope['$redoTaBindtaTextElement' + _serial]();\n                        }else{\n                            // catch errors like FF erroring when you try to force an undo with nothing done\n                            _taExecCommand(command, false, opt, scope.defaultTagAttributes);\n                            if(isSelectableElementTool){\n                                // re-apply the selectable tool events\n                                scope['reApplyOnSelectorHandlerstaTextElement' + _serial]();\n                            }\n                            // refocus on the shown display element, this fixes a display bug when using :focus styles to outline the box.\n                            // You still have focus on the text/html input it just doesn't show up\n                            scope.displayElements.text[0].focus();\n                        }\n                    },\n                    showHtml: scope.$eval(attrs.taShowHtml) || false\n                });\n                // setup the options from the optional attributes\n                if(attrs.taFocussedClass)\t\t\tscope.classes.focussed = attrs.taFocussedClass;\n                if(attrs.taTextEditorClass)\t\t\tscope.classes.textEditor = attrs.taTextEditorClass;\n                if(attrs.taHtmlEditorClass)\t\t\tscope.classes.htmlEditor = attrs.taHtmlEditorClass;\n                if(attrs.taDefaultTagAttributes){\n                    try\t{\n                        //\tTODO: This should use angular.merge to enhance functionality once angular 1.4 is required\n                        angular.extend(scope.defaultTagAttributes, angular.fromJson(attrs.taDefaultTagAttributes));\n                    } catch (error) {\n                        $log.error(error);\n                    }\n                }\n                // optional setup functions\n                if(attrs.taTextEditorSetup)\t\t\tscope.setup.textEditorSetup = scope.$parent.$eval(attrs.taTextEditorSetup);\n                if(attrs.taHtmlEditorSetup)\t\t\tscope.setup.htmlEditorSetup = scope.$parent.$eval(attrs.taHtmlEditorSetup);\n                // optional fileDropHandler function\n                if(attrs.taFileDrop)\t\t\t\tscope.fileDropHandler = scope.$parent.$eval(attrs.taFileDrop);\n                else\t\t\t\t\t\t\t\tscope.fileDropHandler = scope.defaultFileDropHandler;\n\n                _originalContents = element[0].innerHTML;\n                // clear the original content\n                element[0].innerHTML = '';\n\n                // Setup the HTML elements as variable references for use later\n                scope.displayElements = {\n                    // we still need the hidden input even with a textarea as the textarea may have invalid/old input in it,\n                    // wheras the input will ALLWAYS have the correct value.\n                    forminput: angular.element(\"<input type='hidden' tabindex='-1' style='display: none;'>\"),\n                    html: angular.element(\"<textarea></textarea>\"),\n                    text: angular.element(\"<div></div>\"),\n                    // other toolbased elements\n                    scrollWindow: angular.element(\"<div class='ta-scroll-window'></div>\"),\n                    popover: angular.element('<div class=\"popover fade bottom\" style=\"max-width: none; width: 305px;\"></div>'),\n                    popoverArrow: angular.element('<div class=\"arrow\"></div>'),\n                    popoverContainer: angular.element('<div class=\"popover-content\"></div>'),\n                    resize: {\n                        overlay: angular.element('<div class=\"ta-resizer-handle-overlay\"></div>'),\n                        background: angular.element('<div class=\"ta-resizer-handle-background\"></div>'),\n                        anchors: [\n                            angular.element('<div class=\"ta-resizer-handle-corner ta-resizer-handle-corner-tl\"></div>'),\n                            angular.element('<div class=\"ta-resizer-handle-corner ta-resizer-handle-corner-tr\"></div>'),\n                            angular.element('<div class=\"ta-resizer-handle-corner ta-resizer-handle-corner-bl\"></div>'),\n                            angular.element('<div class=\"ta-resizer-handle-corner ta-resizer-handle-corner-br\"></div>')\n                        ],\n                        info: angular.element('<div class=\"ta-resizer-handle-info\"></div>')\n                    }\n                };\n\n                // Setup the popover\n                scope.displayElements.popover.append(scope.displayElements.popoverArrow);\n                scope.displayElements.popover.append(scope.displayElements.popoverContainer);\n                scope.displayElements.scrollWindow.append(scope.displayElements.popover);\n\n                scope.displayElements.popover.on('mousedown', function(e, eventData){\n                    /* istanbul ignore else: this is for catching the jqLite testing*/\n                    if(eventData) angular.extend(e, eventData);\n                    // this prevents focusout from firing on the editor when clicking anything in the popover\n                    e.preventDefault();\n                    return false;\n                });\n\n                /* istanbul ignore next: popover resize and scroll events handled */\n                scope.handlePopoverEvents = function() {\n                    if (scope.displayElements.popover.css('display')==='block') {\n                        if(_resizeTimeout) $timeout.cancel(_resizeTimeout);\n                        _resizeTimeout = $timeout(function() {\n                            //console.log('resize', scope.displayElements.popover.css('display'));\n                            scope.reflowPopover(scope.resizeElement);\n                            scope.reflowResizeOverlay(scope.resizeElement);\n                        }, 100);\n                    }\n                };\n\n                /* istanbul ignore next: browser resize check */\n                angular.element(window).on('resize', scope.handlePopoverEvents);\n\n                /* istanbul ignore next: browser scroll check */\n                angular.element(window).on('scroll', scope.handlePopoverEvents);\n\n                // we want to know if a given node has a scrollbar!\n                // credit to lotif on http://stackoverflow.com/questions/4880381/check-whether-html-element-has-scrollbars\n                var isScrollable = function(node) {\n                    var cs;\n                    var _notScrollable = {\n                        vertical: false,\n                        horizontal: false,\n                    };\n                    try {\n                        cs = window.getComputedStyle(node);\n                        if (cs === null) {\n                            return _notScrollable;\n                        }\n                    } catch (e) {\n                        /* istanbul ignore next: error handler */\n                        return _notScrollable;\n                    }\n                    var overflowY = cs['overflow-y'];\n                    var overflowX = cs['overflow-x'];\n                    return {\n                        vertical: (overflowY === 'scroll' || overflowY === 'auto') &&\n                                    /* istanbul ignore next: not tested */\n                                    node.scrollHeight > node.clientHeight,\n                        horizontal: (overflowX === 'scroll' || overflowX === 'auto') &&\n                                    /* istanbul ignore next: not tested */\n                                    node.scrollWidth > node.clientWidth,\n                    };\n                };\n\n                // getScrollTop\n                //\n                // we structure this so that it can climb the parents of the _el and when it finds\n                // one with scrollbars, it adds an EventListener, so that no matter how the\n                // DOM is structured in the user APP, if there is a scrollbar not as part of the\n                // ta-scroll-window, we will still capture the 'scroll' events...\n                // and handle the scroll event properly and do the resize, etc.\n                //\n                scope.getScrollTop = function (_el, bAddListener) {\n                    var scrollTop = _el.scrollTop;\n                    if (typeof scrollTop === 'undefined') {\n                        scrollTop = 0;\n                    }\n                    /* istanbul ignore next: triggered only if has scrollbar */\n                    if (bAddListener && isScrollable(_el).vertical) {\n                        // remove element eventListener\n                        _el.removeEventListener('scroll', scope._scrollListener, false);\n                        _el.addEventListener('scroll', scope._scrollListener, false);\n                    }\n                    /* istanbul ignore next: triggered only if has scrollbar and scrolled */\n                    if (scrollTop !== 0) {\n                        return { node:_el.nodeName, top:scrollTop };\n                    }\n                    /* istanbul ignore else: catches only if no scroll */\n                    if (_el.parentNode) {\n                        return scope.getScrollTop(_el.parentNode, bAddListener);\n                    } else {\n                        return { node:'<none>', top:0 };\n                    }\n                };\n\n                // define the popover show and hide functions\n                scope.showPopover = function(_el){\n                    scope.getScrollTop(scope.displayElements.scrollWindow[0], true);\n                    scope.displayElements.popover.css('display', 'block');\n                    // we must use a $timeout here, or the css change to the\n                    // displayElements.resize.overlay will not take!!!\n                    // WHY???\n                    $timeout(function() {\n                        scope.displayElements.resize.overlay.css('display', 'block');\n                    });\n                    scope.resizeElement = _el;\n                    scope.reflowPopover(_el);\n                    $animate.addClass(scope.displayElements.popover, 'in');\n                    oneEvent($document.find('body'), 'click keyup', function(){scope.hidePopover();});\n                };\n\n                /* istanbul ignore next: browser scroll event handler */\n                scope._scrollListener = function (e, eventData){\n                    scope.handlePopoverEvents();\n                };\n\n                scope.reflowPopover = function(_el){\n                    var scrollTop = scope.getScrollTop(scope.displayElements.scrollWindow[0], false);\n                    var spaceAboveImage = _el[0].offsetTop-scrollTop.top;\n                    //var spaceBelowImage = scope.displayElements.text[0].offsetHeight - _el[0].offsetHeight - spaceAboveImage;\n                    //console.log(spaceAboveImage, spaceBelowImage);\n\n                    /* istanbul ignore if: catches only if near bottom of editor */\n                    if(spaceAboveImage < 51) {\n                        scope.displayElements.popover.css('top', _el[0].offsetTop + _el[0].offsetHeight + scope.displayElements.scrollWindow[0].scrollTop + 'px');\n                        scope.displayElements.popover.removeClass('top').addClass('bottom');\n                    } else {\n                        scope.displayElements.popover.css('top', _el[0].offsetTop - 54 + scope.displayElements.scrollWindow[0].scrollTop + 'px');\n                        scope.displayElements.popover.removeClass('bottom').addClass('top');\n                    }\n                    var _maxLeft = scope.displayElements.text[0].offsetWidth - scope.displayElements.popover[0].offsetWidth;\n                    var _targetLeft = _el[0].offsetLeft + (_el[0].offsetWidth / 2.0) - (scope.displayElements.popover[0].offsetWidth / 2.0);\n                    var _rleft = Math.max(0, Math.min(_maxLeft, _targetLeft));\n                    var _marginLeft = (Math.min(_targetLeft, (Math.max(0, _targetLeft - _maxLeft))) - 11);\n                    _rleft += window.scrollX;\n                    _marginLeft -= window.scrollX;\n                    scope.displayElements.popover.css('left', _rleft + 'px');\n                    scope.displayElements.popoverArrow.css('margin-left', _marginLeft + 'px');\n                };\n                scope.hidePopover = function(){\n                    scope.displayElements.popover.css('display', 'none');\n                    scope.displayElements.popoverContainer.attr('style', '');\n                    scope.displayElements.popoverContainer.attr('class', 'popover-content');\n                    scope.displayElements.popover.removeClass('in');\n                    scope.displayElements.resize.overlay.css('display', 'none');\n                };\n\n                // setup the resize overlay\n                scope.displayElements.resize.overlay.append(scope.displayElements.resize.background);\n                angular.forEach(scope.displayElements.resize.anchors, function(anchor){ scope.displayElements.resize.overlay.append(anchor);});\n                scope.displayElements.resize.overlay.append(scope.displayElements.resize.info);\n                scope.displayElements.scrollWindow.append(scope.displayElements.resize.overlay);\n\n                // A click event on the resize.background will now shift the focus to the editor\n                /* istanbul ignore next: click on the resize.background to focus back to editor */\n                scope.displayElements.resize.background.on('click', function(e) {\n                    scope.displayElements.text[0].focus();\n                });\n\n                // define the show and hide events\n                scope.reflowResizeOverlay = function(_el){\n                    _el = angular.element(_el)[0];\n                    scope.displayElements.resize.overlay.css({\n                        'display': 'block',\n                        'left': _el.offsetLeft - 5 + 'px',\n                        'top': _el.offsetTop - 5 + 'px',\n                        'width': _el.offsetWidth + 10 + 'px',\n                        'height': _el.offsetHeight + 10 + 'px'\n                    });\n                    scope.displayElements.resize.info.text(_el.offsetWidth + ' x ' + _el.offsetHeight);\n                };\n                /* istanbul ignore next: pretty sure phantomjs won't test this */\n                scope.showResizeOverlay = function(_el){\n                    var _body = $document.find('body');\n                    _resizeMouseDown = function(event){\n                        var startPosition = {\n                            width: parseInt(_el.attr('width')),\n                            height: parseInt(_el.attr('height')),\n                            x: event.clientX,\n                            y: event.clientY\n                        };\n                        if(startPosition.width === undefined || isNaN(startPosition.width)) startPosition.width = _el[0].offsetWidth;\n                        if(startPosition.height === undefined || isNaN(startPosition.height)) startPosition.height = _el[0].offsetHeight;\n                        scope.hidePopover();\n                        var ratio = startPosition.height / startPosition.width;\n                        var mousemove = function(event){\n                            // calculate new size\n                            var pos = {\n                                x: Math.max(0, startPosition.width + (event.clientX - startPosition.x)),\n                                y: Math.max(0, startPosition.height + (event.clientY - startPosition.y))\n                            };\n\n                            // DEFAULT: the aspect ratio is not locked unless the Shift key is pressed.\n                            //\n                            // attribute: ta-resize-force-aspect-ratio -- locks resize into maintaing the aspect ratio\n                            var bForceAspectRatio = (attrs.taResizeForceAspectRatio !== undefined);\n                            // attribute: ta-resize-maintain-aspect-ratio=true causes the space ratio to remain locked\n                            // unless the Shift key is pressed\n                            var bFlipKeyBinding = attrs.taResizeMaintainAspectRatio;\n                            var bKeepRatio =  bForceAspectRatio || (bFlipKeyBinding && !event.shiftKey);\n                            if(bKeepRatio) {\n                                var newRatio = pos.y / pos.x;\n                                pos.x = ratio > newRatio ? pos.x : pos.y / ratio;\n                                pos.y = ratio > newRatio ? pos.x * ratio : pos.y;\n                            }\n                            var el = angular.element(_el);\n                            function roundedMaxVal(val) {\n                                return Math.round(Math.max(0, val));\n                            }\n                            el.css('height', roundedMaxVal(pos.y) + 'px');\n                            el.css('width', roundedMaxVal(pos.x) + 'px');\n\n                            // reflow the popover tooltip\n                            scope.reflowResizeOverlay(_el);\n                        };\n                        _body.on('mousemove', mousemove);\n                        oneEvent(_body, 'mouseup', function(event){\n                            event.preventDefault();\n                            event.stopPropagation();\n                            _body.off('mousemove', mousemove);\n                            // at this point, we need to force the model to update! since the css has changed!\n                            // this fixes bug: #862 - we now hide the popover -- as this seems more consitent.\n                            // there are still issues under firefox, the window does not repaint. -- not sure\n                            // how best to resolve this, but clicking anywhere works.\n                            scope.$apply(function (){\n                                scope.hidePopover();\n                                scope.updateTaBindtaTextElement();\n                            }, 100);\n                        });\n                        event.stopPropagation();\n                        event.preventDefault();\n                    };\n\n                    scope.displayElements.resize.anchors[3].off('mousedown');\n                    scope.displayElements.resize.anchors[3].on('mousedown', _resizeMouseDown);\n\n                    scope.reflowResizeOverlay(_el);\n                    oneEvent(_body, 'click', function(){scope.hideResizeOverlay();});\n                };\n                /* istanbul ignore next: pretty sure phantomjs won't test this */\n                scope.hideResizeOverlay = function(){\n                    scope.displayElements.resize.anchors[3].off('mousedown', _resizeMouseDown);\n                    scope.displayElements.resize.overlay.css('display', 'none');\n                };\n\n                // allow for insertion of custom directives on the textarea and div\n                scope.setup.htmlEditorSetup(scope.displayElements.html);\n                scope.setup.textEditorSetup(scope.displayElements.text);\n                scope.displayElements.html.attr({\n                    'id': 'taHtmlElement' + _serial,\n                    'ng-show': 'showHtml',\n                    'ta-bind': 'ta-bind',\n                    'ng-model': 'html',\n                    'ng-model-options': element.attr('ng-model-options')\n                });\n                scope.displayElements.text.attr({\n                    'id': 'taTextElement' + _serial,\n                    'contentEditable': 'true',\n                    'ta-bind': 'ta-bind',\n                    'ng-model': 'html',\n                    'ng-model-options': element.attr('ng-model-options')\n                });\n                scope.displayElements.scrollWindow.attr({'ng-hide': 'showHtml'});\n                if(attrs.taDefaultWrap) {\n                    // taDefaultWrap is only applied to the text and not the html view\n                    scope.displayElements.text.attr('ta-default-wrap', attrs.taDefaultWrap);\n                }\n\n                if(attrs.taUnsafeSanitizer){\n                    scope.displayElements.text.attr('ta-unsafe-sanitizer', attrs.taUnsafeSanitizer);\n                    scope.displayElements.html.attr('ta-unsafe-sanitizer', attrs.taUnsafeSanitizer);\n                }\n\n                if(attrs.taKeepStyles){\n                    scope.displayElements.text.attr('ta-keep-styles', attrs.taKeepStyles);\n                    scope.displayElements.html.attr('ta-keep-styles', attrs.taKeepStyles);\n                }\n\n                // add the main elements to the origional element\n                scope.displayElements.scrollWindow.append(scope.displayElements.text);\n                element.append(scope.displayElements.scrollWindow);\n                element.append(scope.displayElements.html);\n\n                scope.displayElements.forminput.attr('name', scope._name);\n                element.append(scope.displayElements.forminput);\n\n                if(attrs.tabindex){\n                    element.removeAttr('tabindex');\n                    scope.displayElements.text.attr('tabindex', attrs.tabindex);\n                    scope.displayElements.html.attr('tabindex', attrs.tabindex);\n                }\n\n                if (attrs.placeholder) {\n                    scope.displayElements.text.attr('placeholder', attrs.placeholder);\n                    scope.displayElements.html.attr('placeholder', attrs.placeholder);\n                }\n\n                if(attrs.taDisabled){\n                    scope.displayElements.text.attr('ta-readonly', 'disabled');\n                    scope.displayElements.html.attr('ta-readonly', 'disabled');\n                    scope.disabled = scope.$parent.$eval(attrs.taDisabled);\n                    scope.$parent.$watch(attrs.taDisabled, function(newVal){\n                        scope.disabled = newVal;\n                        if(scope.disabled){\n                            element.addClass(scope.classes.disabled);\n                        }else{\n                            element.removeClass(scope.classes.disabled);\n                        }\n                    });\n                }\n\n                if(attrs.taPaste){\n                    scope._pasteHandler = function(_html){\n                        return $parse(attrs.taPaste)(scope.$parent, {$html: _html});\n                    };\n                    scope.displayElements.text.attr('ta-paste', '_pasteHandler($html)');\n                }\n\n                // compile the scope with the text and html elements only - if we do this with the main element it causes a compile loop\n                $compile(scope.displayElements.scrollWindow)(scope);\n                $compile(scope.displayElements.html)(scope);\n\n                scope.updateTaBindtaTextElement = scope['updateTaBindtaTextElement' + _serial];\n                scope.updateTaBindtaHtmlElement = scope['updateTaBindtaHtmlElement' + _serial];\n\n                // add the classes manually last\n                element.addClass(\"ta-root\");\n                scope.displayElements.scrollWindow.addClass(\"ta-text ta-editor \" + scope.classes.textEditor);\n                scope.displayElements.html.addClass(\"ta-html ta-editor \" + scope.classes.htmlEditor);\n\n                var testAndSet = function(choice, beforeState) {\n                    /* istanbul ignore next: this is only here because of a bug in rangy where rangy.saveSelection() has cleared the state */\n                    if (beforeState !== $document[0].queryCommandState(choice)) {\n                        $document[0].execCommand(choice, false, null);\n                    }\n                };\n                // used in the toolbar actions\n                scope._actionRunning = false;\n                var _savedSelection = false;\n                scope.startAction = function(){\n                    var _beforeStateBold = false;\n                    var _beforeStateItalic = false;\n                    var _beforeStateUnderline = false;\n                    var _beforeStateStrikethough = false;\n                    scope._actionRunning = true;\n                    _beforeStateBold = $document[0].queryCommandState('bold');\n                    _beforeStateItalic = $document[0].queryCommandState('italic');\n                    _beforeStateUnderline = $document[0].queryCommandState('underline');\n                    _beforeStateStrikethough = $document[0].queryCommandState('strikeThrough');\n                    //console.log('B', _beforeStateBold, 'I', _beforeStateItalic, '_', _beforeStateUnderline, 'S', _beforeStateStrikethough);\n                    // if rangy library is loaded return a function to reload the current selection\n                    _savedSelection = rangy.saveSelection();\n                    // rangy.saveSelection() clear the state of bold, italic, underline, strikethrough\n                    // so we reset them here....!!!\n                    // this fixes bugs #423, #1129, #1105, #693 which are actually rangy bugs!\n                    testAndSet('bold', _beforeStateBold);\n                    testAndSet('italic', _beforeStateItalic);\n                    testAndSet('underline', _beforeStateUnderline);\n                    testAndSet('strikeThrough', _beforeStateStrikethough);\n                    //console.log('B', $document[0].queryCommandState('bold'), 'I', $document[0].queryCommandState('italic'), '_', $document[0].queryCommandState('underline'), 'S', $document[0].queryCommandState('strikeThrough') );\n                    return function(){\n                        if(_savedSelection) rangy.restoreSelection(_savedSelection);\n                        // perhaps if we restore the selections here, we would do better overall???\n                        // BUT what we do above does well in 90% of the cases...\n                    };\n                };\n                scope.endAction = function(){\n                    scope._actionRunning = false;\n                    if(_savedSelection){\n                        if(scope.showHtml){\n                            scope.displayElements.html[0].focus();\n                        }else{\n                            scope.displayElements.text[0].focus();\n                        }\n                        // rangy.restoreSelection(_savedSelection);\n                        rangy.removeMarkers(_savedSelection);\n                    }\n                    _savedSelection = false;\n                    scope.updateSelectedStyles();\n                    // only update if in text or WYSIWYG mode\n                    if(!scope.showHtml) scope['updateTaBindtaTextElement' + _serial]();\n                };\n\n                // note that focusout > focusin is called everytime we click a button - except bad support: http://www.quirksmode.org/dom/events/blurfocus.html\n                // cascades to displayElements.text and displayElements.html automatically.\n                _focusin = function(e){\n                    scope.focussed = true;\n                    element.addClass(scope.classes.focussed);\n/*******  NOT FUNCTIONAL YET\n                    if (e.target.id === 'taTextElement' + _serial) {\n                        console.log('_focusin taTextElement');\n                        // we only do this if NOT focussed\n                        textAngularManager.restoreFocusSelection(scope._name);\n                    }\n*******/\n                    _editorFunctions.focus();\n                    element.triggerHandler('focus');\n                    // we call editorScope.updateSelectedStyles() here because we want the toolbar to be focussed\n                    // as soon as we have focus.  Otherwise this only happens on mousedown or keydown etc...\n                    /* istanbul ignore else: don't run if already running */\n                    if(scope.updateSelectedStyles && !scope._bUpdateSelectedStyles){\n                        // we don't set editorScope._bUpdateSelectedStyles here, because we do not want the\n                        // updateSelectedStyles() to run twice which it will do after 200 msec if we have\n                        // set editorScope._bUpdateSelectedStyles\n                        //\n                        // WOW, normally I would do a scope.$apply here, but this causes ERRORs when doing tests!\n                        $timeout(function () {\n                            scope.updateSelectedStyles();\n                        }, 0);\n                    }\n                };\n                scope.displayElements.html.on('focus', _focusin);\n                scope.displayElements.text.on('focus', _focusin);\n                _focusout = function(e){\n                    /****************** NOT FUNCTIONAL YET\n                    try {\n                        var _s = rangy.getSelection();\n                        if (_s) {\n                            // we save the selection when we loose focus so that if do a wrapSelection, the\n                            // apropriate selection in the editor is restored before action.\n                            var _savedFocusRange = rangy.saveRange(_s.getRangeAt(0));\n                            textAngularManager.saveFocusSelection(scope._name, _savedFocusRange);\n                        }\n                    } catch(error) { }\n                    *****************/\n                    // if we are NOT runnig an action and have NOT focussed again on the text etc then fire the blur events\n                    if(!scope._actionRunning &&\n                        $document[0].activeElement !== scope.displayElements.html[0] &&\n                        $document[0].activeElement !== scope.displayElements.text[0])\n                    {\n                        element.removeClass(scope.classes.focussed);\n                        _editorFunctions.unfocus();\n                        // to prevent multiple apply error defer to next seems to work.\n                        $timeout(function(){\n                            scope._bUpdateSelectedStyles = false;\n                            element.triggerHandler('blur');\n                            scope.focussed = false;\n                        }, 0);\n                    }\n                    e.preventDefault();\n                    return false;\n                };\n                scope.displayElements.html.on('blur', _focusout);\n                scope.displayElements.text.on('blur', _focusout);\n\n                scope.displayElements.text.on('paste', function(event){\n                    element.triggerHandler('paste', event);\n                });\n\n                // Setup the default toolbar tools, this way allows the user to add new tools like plugins.\n                // This is on the editor for future proofing if we find a better way to do this.\n                scope.queryFormatBlockState = function(command){\n                    // $document[0].queryCommandValue('formatBlock') errors in Firefox if we call this when focussed on the textarea\n                    return !scope.showHtml && command.toLowerCase() === $document[0].queryCommandValue('formatBlock').toLowerCase();\n                };\n                scope.queryCommandState = function(command){\n                    // $document[0].queryCommandValue('formatBlock') errors in Firefox if we call this when focussed on the textarea\n                    return (!scope.showHtml) ? $document[0].queryCommandState(command) : '';\n                };\n                scope.switchView = function(){\n                    scope.showHtml = !scope.showHtml;\n                    $animate.enabled(false, scope.displayElements.html);\n                    $animate.enabled(false, scope.displayElements.text);\n                    //Show the HTML view\n                    /* istanbul ignore next: ngModel exists check */\n/* THIS is not the correct thing to do, here....\n   The ngModel is correct, but it is not formatted as the user as done it...\n                    var _model;\n                    if (ngModel) {\n                        _model = ngModel.$viewValue;\n                    } else {\n                        _model = scope.html;\n                    }\n                    var _html = scope.displayElements.html[0].value;\n                    if (getDomFromHtml(_html).childElementCount !== getDomFromHtml(_model).childElementCount) {\n                        // the model and the html do not agree\n                        // they can get out of sync and when they do, we correct that here...\n                        scope.displayElements.html.val(_model);\n                    }\n*/\n                    if(scope.showHtml){\n                        //defer until the element is visible\n                        $timeout(function(){\n                            $animate.enabled(true, scope.displayElements.html);\n                            $animate.enabled(true, scope.displayElements.text);\n                            // [0] dereferences the DOM object from the angular.element\n                            return scope.displayElements.html[0].focus();\n                        }, 100);\n                    }else{\n                        //Show the WYSIWYG view\n                        //defer until the element is visible\n                        $timeout(function(){\n                            $animate.enabled(true, scope.displayElements.html);\n                            $animate.enabled(true, scope.displayElements.text);\n                            // [0] dereferences the DOM object from the angular.element\n                            return scope.displayElements.text[0].focus();\n                        }, 100);\n                    }\n                };\n\n                // changes to the model variable from outside the html/text inputs\n                // if no ngModel, then the only input is from inside text-angular\n                if(attrs.ngModel){\n                    var _firstRun = true;\n                    ngModel.$render = function(){\n                        if(_firstRun){\n                            // we need this firstRun to set the originalContents otherwise it gets overrided by the setting of ngModel to undefined from NaN\n                            _firstRun = false;\n                            // if view value is null or undefined initially and there was original content, set to the original content\n                            var _initialValue = scope.$parent.$eval(attrs.ngModel);\n                            if((_initialValue === undefined || _initialValue === null) && (_originalContents && _originalContents !== '')){\n                                // on passing through to taBind it will be sanitised\n                                ngModel.$setViewValue(_originalContents);\n                            }\n                        }\n                        scope.displayElements.forminput.val(ngModel.$viewValue);\n                        // if the editors aren't focused they need to be updated, otherwise they are doing the updating\n                        scope.html = ngModel.$viewValue || '';\n                    };\n                    // trigger the validation calls\n                    if(element.attr('required')) ngModel.$validators.required = function(modelValue, viewValue) {\n                        var value = modelValue || viewValue;\n                        return !(!value || value.trim() === '');\n                    };\n                }else{\n                    // if no ngModel then update from the contents of the origional html.\n                    scope.displayElements.forminput.val(_originalContents);\n                    scope.html = _originalContents;\n                }\n\n                // changes from taBind back up to here\n                scope.$watch('html', function(newValue, oldValue){\n                    if(newValue !== oldValue){\n                        if(attrs.ngModel && ngModel.$viewValue !== newValue) {\n                            ngModel.$setViewValue(newValue);\n                        }\n                        scope.displayElements.forminput.val(newValue);\n                    }\n                });\n\n                if(attrs.taTargetToolbars) {\n                    _editorFunctions = textAngularManager.registerEditor(scope._name, scope, attrs.taTargetToolbars.split(','));\n                }\n                else{\n                    var _toolbar = angular.element('<div text-angular-toolbar name=\"textAngularToolbar' + _serial + '\">');\n                    // passthrough init of toolbar options\n                    if(attrs.taToolbar)\t\t\t\t\t\t_toolbar.attr('ta-toolbar', attrs.taToolbar);\n                    if(attrs.taToolbarClass)\t\t\t\t_toolbar.attr('ta-toolbar-class', attrs.taToolbarClass);\n                    if(attrs.taToolbarGroupClass)\t\t\t_toolbar.attr('ta-toolbar-group-class', attrs.taToolbarGroupClass);\n                    if(attrs.taToolbarButtonClass)\t\t\t_toolbar.attr('ta-toolbar-button-class', attrs.taToolbarButtonClass);\n                    if(attrs.taToolbarActiveButtonClass)\t_toolbar.attr('ta-toolbar-active-button-class', attrs.taToolbarActiveButtonClass);\n                    if(attrs.taFocussedClass)\t\t\t\t_toolbar.attr('ta-focussed-class', attrs.taFocussedClass);\n\n                    element.prepend(_toolbar);\n                    $compile(_toolbar)(scope.$parent);\n                    _editorFunctions = textAngularManager.registerEditor(scope._name, scope, ['textAngularToolbar' + _serial]);\n                }\n\n                scope.$on('$destroy', function(){\n                    textAngularManager.unregisterEditor(scope._name);\n                    angular.element(window).off('blur');\n                    angular.element(window).off('resize', scope.handlePopoverEvents);\n                    angular.element(window).off('scroll', scope.handlePopoverEvents);\n                });\n\n                // catch element select event and pass to toolbar tools\n                scope.$on('ta-element-select', function(event, element){\n                    if(_editorFunctions.triggerElementSelect(event, element)){\n                        scope['reApplyOnSelectorHandlerstaTextElement' + _serial]();\n                    }\n                });\n\n/******************* no working fully\n                var distanceFromPoint = function (px, py, x, y) {\n                    return Math.sqrt((px-x)*(px-x)+(py-y)*(py-y));\n                };\n                // because each object is a rectangle and we have a single point,\n                // we need to give priority if the point is inside the rectangle\n                var getPositionDistance = function(el, x, y) {\n                    var range = document.createRange();\n                    range.selectNode(el);\n                    var rect = range.getBoundingClientRect();\n                    console.log(el, rect);\n                    range.detach();\n                    var bcr = rect;\n                    // top left\n                    var d1 = distanceFromPoint(bcr.left, bcr.top, x, y);\n                    // bottom left\n                    var d2 = distanceFromPoint(bcr.left, bcr.bottom, x, y);\n                    // top right\n                    var d3 = distanceFromPoint(bcr.right, bcr.top, x, y);\n                    // bottom right\n                    var d4 = distanceFromPoint(bcr.right, bcr.bottom, x, y);\n                    return Math.min(d1, d2, d3, d4);\n                };\n                var findClosest = function(el, minElement, maxDistance, x, y) {\n                    var _d=0;\n                    for (var i = 0; i < el.childNodes.length; i++) {\n                        var _n = el.childNodes[i];\n                        if (!_n.childNodes.length) {\n                            _d = getPositionDistance(_n, x, y);\n                            //console.log(_n, _n.childNodes, _d);\n                            if (_d < maxDistance) {\n                                maxDistance = _d;\n                                minElement = _n;\n                            }\n                        }\n                        var res = findClosest(_n, minElement, maxDistance, x, y);\n                        if (res.max < maxDistance) {\n                            maxDistance = res.max;\n                            minElement = res.min;\n                        }\n                    }\n                    return { max: maxDistance, min: minElement };\n                };\n                var getClosestElement = function (el, x, y) {\n                    return findClosest(el, null, 12341234124, x, y);\n                };\n****************/\n\n                scope.$on('ta-drop-event', function(event, element, dropEvent, dataTransfer){\n                    if(dataTransfer && dataTransfer.files && dataTransfer.files.length > 0){\n                        scope.displayElements.text[0].focus();\n                        // we must set the location of the drop!\n                        //console.log(dropEvent.clientX, dropEvent.clientY, dropEvent.target);\n                        taSelection.setSelectionToElementEnd(dropEvent.target);\n                        angular.forEach(dataTransfer.files, function(file){\n                            // taking advantage of boolean execution, if the fileDropHandler returns true, nothing else after it is executed\n                            // If it is false then execute the defaultFileDropHandler if the fileDropHandler is NOT the default one\n                            // Once one of these has been executed wrap the result as a promise, if undefined or variable update the taBind, else we should wait for the promise\n                            try{\n                                $q.when(scope.fileDropHandler(file, scope.wrapSelection) ||\n                                    (scope.fileDropHandler !== scope.defaultFileDropHandler &&\n                                    $q.when(scope.defaultFileDropHandler(file, scope.wrapSelection)))).then(function(){\n                                        scope['updateTaBindtaTextElement' + _serial]();\n                                    });\n                            }catch(error){\n                                $log.error(error);\n                            }\n                        });\n                        dropEvent.preventDefault();\n                        dropEvent.stopPropagation();\n                    /* istanbul ignore else, the updates if moved text */\n                    }else{\n                        $timeout(function(){\n                            scope['updateTaBindtaTextElement' + _serial]();\n                        }, 0);\n                    }\n                });\n\n                // the following is for applying the active states to the tools that support it\n                scope._bUpdateSelectedStyles = false;\n                /* istanbul ignore next: browser window/tab leave check */\n                angular.element(window).on('blur', function(){\n                    scope._bUpdateSelectedStyles = false;\n                    scope.focussed = false;\n                });\n                // loop through all the tools polling their activeState function if it exists\n                scope.updateSelectedStyles = function(){\n                    var _selection;\n                    /* istanbul ignore next: This check is to ensure multiple timeouts don't exist */\n                    if(_updateSelectedStylesTimeout) $timeout.cancel(_updateSelectedStylesTimeout);\n                    // test if the common element ISN'T the root ta-text node\n                    if((_selection = taSelection.getSelectionElement()) !== undefined && _selection.parentNode !== scope.displayElements.text[0]){\n                        _editorFunctions.updateSelectedStyles(angular.element(_selection));\n                    }else _editorFunctions.updateSelectedStyles();\n                    // used to update the active state when a key is held down, ie the left arrow\n                    /* istanbul ignore else: browser only check */\n                    if(scope._bUpdateSelectedStyles) _updateSelectedStylesTimeout = $timeout(scope.updateSelectedStyles, 200);\n                };\n                // start updating on keydown\n                _keydown = function(){\n                    /* istanbul ignore next: ie catch */\n                    if(!scope.focussed){\n                        scope._bUpdateSelectedStyles = false;\n                        return;\n                    }\n                    /* istanbul ignore else: don't run if already running */\n                    if(!scope._bUpdateSelectedStyles){\n                        scope._bUpdateSelectedStyles = true;\n                        scope.$apply(function(){\n                            scope.updateSelectedStyles();\n                        });\n                    }\n                };\n                scope.displayElements.html.on('keydown', _keydown);\n                scope.displayElements.text.on('keydown', _keydown);\n                // stop updating on key up and update the display/model\n                _keyup = function(){\n                    scope._bUpdateSelectedStyles = false;\n                };\n                scope.displayElements.html.on('keyup', _keyup);\n                scope.displayElements.text.on('keyup', _keyup);\n                // stop updating on key up and update the display/model\n                _keypress = function(event, eventData){\n                    // bug fix for Firefox.  If we are selecting a <a> already, any characters will\n                    // be added within the <a> which is bad!\n                    /* istanbul ignore next: don't see how to test this... */\n                    if (taSelection.getSelection) {\n                        var _selection = taSelection.getSelection();\n                        // in a weird case (can't reproduce) taSelection.getSelectionElement() can be undefined!!\n                        // this comes from range.commonAncestorContainer;\n                        // so I check for this here which fixes the error case\n                        if (taSelection.getSelectionElement() && taSelection.getSelectionElement().nodeName.toLowerCase() === 'a') {\n                            // check and see if we are at the edge of the <a>\n                            if (_selection.start.element.nodeType === 3 &&\n                                _selection.start.element.textContent.length === _selection.end.offset) {\n                                // we are at the end of the <a>!!!\n                                // so move the selection to after the <a>!!\n                                taSelection.setSelectionAfterElement(taSelection.getSelectionElement());\n                            }\n                            if (_selection.start.element.nodeType === 3 &&\n                                _selection.start.offset === 0) {\n                                // we are at the start of the <a>!!!\n                                // so move the selection before the <a>!!\n                                taSelection.setSelectionBeforeElement(taSelection.getSelectionElement());\n                            }\n                        }\n                    }\n                    /* istanbul ignore else: this is for catching the jqLite testing*/\n                    if(eventData) angular.extend(event, eventData);\n                    scope.$apply(function(){\n                        if(_editorFunctions.sendKeyCommand(event)){\n                            /* istanbul ignore else: don't run if already running */\n                            if(!scope._bUpdateSelectedStyles){\n                                scope.updateSelectedStyles();\n                            }\n                            event.preventDefault();\n                            return false;\n                        }\n                    });\n                };\n                scope.displayElements.html.on('keypress', _keypress);\n                scope.displayElements.text.on('keypress', _keypress);\n                // update the toolbar active states when we click somewhere in the text/html boxed\n                _mouseup = function(){\n                    // ensure only one execution of updateSelectedStyles()\n                    scope._bUpdateSelectedStyles = false;\n                    // for some reason, unless we do a $timeout here, after a _mouseup when the line is\n                    // highlighted, and instead use a scope.$apply(function(){ scope.updateSelectedStyles(); });\n                    // doesn't work properly, so we replaced this with:\n                    /* istanbul ignore next: not tested  */\n                    $timeout(function() { scope.updateSelectedStyles(); }, 0);\n                };\n                scope.displayElements.html.on('mouseup', _mouseup);\n                scope.displayElements.text.on('mouseup', _mouseup);\n            }\n        };\n    }\n]);\ntextAngular.service('textAngularManager', ['taToolExecuteAction', 'taTools', 'taRegisterTool', '$interval', '$rootScope', '$log',\n    function(taToolExecuteAction, taTools, taRegisterTool, $interval, $rootScope, $log){\n    // this service is used to manage all textAngular editors and toolbars.\n    // All publicly published functions that modify/need to access the toolbar or editor scopes should be in here\n    // these contain references to all the editors and toolbars that have been initialised in this app\n    var toolbars = {}, editors = {};\n    // we touch the time any change occurs through register of an editor or tool so that we\n    // in the future will fire and event to trigger an updateSelection\n    var timeRecentModification = 0;\n    var updateStyles = function(selectedElement){\n        angular.forEach(editors, function(editor) {\n            editor.editorFunctions.updateSelectedStyles(selectedElement);\n        });\n    };\n    var triggerInterval = 50;\n    var triggerIntervalTimer;\n    var setupTriggerUpdateStyles = function() {\n        timeRecentModification = Date.now();\n        /* istanbul ignore next: setup a one time updateStyles() */\n        triggerIntervalTimer = $interval(function() {\n            updateStyles();\n            triggerIntervalTimer = undefined;\n        }, triggerInterval, 1); // only trigger once\n    };\n    /* istanbul ignore next: make sure clean up on destroy */\n    $rootScope.$on('destroy', function() {\n        if (triggerIntervalTimer) {\n            $interval.cancel(triggerIntervalTimer);\n            triggerIntervalTimer = undefined;\n        }\n    });\n    var touchModification = function() {\n        if (Math.abs(Date.now() - timeRecentModification) > triggerInterval) {\n            // we have already triggered the updateStyles a long time back... so setup it again...\n            setupTriggerUpdateStyles();\n        }\n    };\n    // when we focus into a toolbar, we need to set the TOOLBAR's $parent to be the toolbars it's linked to.\n    // We also need to set the tools to be updated to be the toolbars...\n    return {\n        // register an editor and the toolbars that it is affected by\n        registerEditor: function(editorName, editorScope, targetToolbars){\n            // NOTE: name === editorScope._name\n            // targetToolbars is an [] of 'toolbar name's\n            // targetToolbars are optional, we don't require a toolbar to function\n            if(!editorName || editorName === '') throw('textAngular Error: An editor requires a name');\n            if(!editorScope) throw('textAngular Error: An editor requires a scope');\n            if(editors[editorName]) throw('textAngular Error: An Editor with name \"' + editorName + '\" already exists');\n            editors[editorName] = {\n                scope: editorScope,\n                toolbars: targetToolbars,\n                // toolbarScopes used by this editor\n                toolbarScopes: [],\n                _registerToolbarScope: function(toolbarScope){\n                    // add to the list late\n                    if(this.toolbars.indexOf(toolbarScope.name) >= 0) {\n                        // if this toolbarScope is being used by this editor we add it as one of the scopes\n                        this.toolbarScopes.push(toolbarScope);\n                    }\n                },\n                // this is a suite of functions the editor should use to update all it's linked toolbars\n                editorFunctions: {\n                    disable: function(){\n                        // disable all linked toolbars\n                        angular.forEach(editors[editorName].toolbarScopes, function(toolbarScope){\n                            toolbarScope.disabled = true;\n                        });\n                    },\n                    enable: function(){\n                        // enable all linked toolbars\n                        angular.forEach(editors[editorName].toolbarScopes, function(toolbarScope){\n                            toolbarScope.disabled = false;\n                        });\n                    },\n                    focus: function(){\n                        // this should be called when the editor is focussed\n                        angular.forEach(editors[editorName].toolbarScopes, function(toolbarScope){\n                            toolbarScope._parent = editorScope;\n                            toolbarScope.disabled = false;\n                            toolbarScope.focussed = true;\n                        });\n                        editorScope.focussed = true;\n                    },\n                    unfocus: function(){\n                        // this should be called when the editor becomes unfocussed\n                        angular.forEach(editors[editorName].toolbarScopes, function(toolbarScope){\n                            toolbarScope.disabled = true;\n                            toolbarScope.focussed = false;\n                        });\n                        editorScope.focussed = false;\n                    },\n                    updateSelectedStyles: function(selectedElement){\n                        // update the active state of all buttons on liked toolbars\n                        angular.forEach(editors[editorName].toolbarScopes, function(toolbarScope){\n                            angular.forEach(toolbarScope.tools, function(toolScope){\n                                if(toolScope.activeState){\n                                    toolbarScope._parent = editorScope;\n                                    // selectedElement may be undefined if nothing selected\n                                    toolScope.active = toolScope.activeState(selectedElement);\n                                }\n                            });\n                        });\n                    },\n                    sendKeyCommand: function(event){\n                        // we return true if we applied an action, false otherwise\n                        var result = false;\n                        if(event.ctrlKey || event.metaKey || event.specialKey) angular.forEach(taTools, function(tool, name){\n                            if(tool.commandKeyCode && (tool.commandKeyCode === event.which || tool.commandKeyCode === event.specialKey)){\n                                for(var _t = 0; _t < editors[editorName].toolbarScopes.length; _t++){\n                                    if(editors[editorName].toolbarScopes[_t].tools[name] !== undefined){\n                                        taToolExecuteAction.call(editors[editorName].toolbarScopes[_t].tools[name], editorScope);\n                                        result = true;\n                                        break;\n                                    }\n                                }\n                            }\n                        });\n                        return result;\n                    },\n                    triggerElementSelect: function(event, element){\n                        // search through the taTools to see if a match for the tag is made.\n                        // if there is, see if the tool is on a registered toolbar and not disabled.\n                        // NOTE: This can trigger on MULTIPLE tools simultaneously.\n                        var elementHasAttrs = function(_element, attrs){\n                            var result = true;\n                            for(var i = 0; i < attrs.length; i++) result = result && _element.attr(attrs[i]);\n                            return result;\n                        };\n                        var workerTools = [];\n                        var unfilteredTools = {};\n                        var result = false;\n                        element = angular.element(element);\n                        // get all valid tools by element name, keep track if one matches the\n                        var onlyWithAttrsFilter = false;\n                        angular.forEach(taTools, function(tool, name){\n                            if(\n                                tool.onElementSelect &&\n                                tool.onElementSelect.element &&\n                                tool.onElementSelect.element.toLowerCase() === element[0].tagName.toLowerCase() &&\n                                (!tool.onElementSelect.filter || tool.onElementSelect.filter(element))\n                            ){\n                                // this should only end up true if the element matches the only attributes\n                                onlyWithAttrsFilter = onlyWithAttrsFilter ||\n                                    (angular.isArray(tool.onElementSelect.onlyWithAttrs) && elementHasAttrs(element, tool.onElementSelect.onlyWithAttrs));\n                                if(!tool.onElementSelect.onlyWithAttrs || elementHasAttrs(element, tool.onElementSelect.onlyWithAttrs)) unfilteredTools[name] = tool;\n                            }\n                        });\n                        // if we matched attributes to filter on, then filter, else continue\n                        if(onlyWithAttrsFilter){\n                            angular.forEach(unfilteredTools, function(tool, name){\n                                if(tool.onElementSelect.onlyWithAttrs && elementHasAttrs(element, tool.onElementSelect.onlyWithAttrs)) workerTools.push({'name': name, 'tool': tool});\n                            });\n                            // sort most specific (most attrs to find) first\n                            workerTools.sort(function(a,b){\n                                return b.tool.onElementSelect.onlyWithAttrs.length - a.tool.onElementSelect.onlyWithAttrs.length;\n                            });\n                        }else{\n                            angular.forEach(unfilteredTools, function(tool, name){\n                                workerTools.push({'name': name, 'tool': tool});\n                            });\n                        }\n                        // Run the actions on the first visible filtered tool only\n                        if(workerTools.length > 0){\n                            for(var _i = 0; _i < workerTools.length; _i++){\n                                var tool = workerTools[_i].tool;\n                                var name = workerTools[_i].name;\n                                for(var _t = 0; _t < editors[editorName].toolbarScopes.length; _t++){\n                                    if(editors[editorName].toolbarScopes[_t].tools[name] !== undefined){\n                                        tool.onElementSelect.action.call(editors[editorName].toolbarScopes[_t].tools[name], event, element, editorScope);\n                                        result = true;\n                                        break;\n                                    }\n                                }\n                                if(result) break;\n                            }\n                        }\n                        return result;\n                    }\n                }\n            };\n            angular.forEach(targetToolbars, function(_name){\n                if(toolbars[_name]) {\n                    editors[editorName].toolbarScopes.push(toolbars[_name]);\n                }\n                // if it doesn't exist it may not have been compiled yet and it will be added later\n            });\n            touchModification();\n            return editors[editorName].editorFunctions;\n        },\n        // retrieve editor by name, largely used by testing suites only\n        retrieveEditor: function(name){\n            return editors[name];\n        },\n        unregisterEditor: function(name){\n            delete editors[name];\n            touchModification();\n        },\n        // registers a toolbar such that it can be linked to editors\n        registerToolbar: function(toolbarScope){\n            if(!toolbarScope) throw('textAngular Error: A toolbar requires a scope');\n            if(!toolbarScope.name || toolbarScope.name === '') throw('textAngular Error: A toolbar requires a name');\n            if(toolbars[toolbarScope.name]) throw('textAngular Error: A toolbar with name \"' + toolbarScope.name + '\" already exists');\n            toolbars[toolbarScope.name] = toolbarScope;\n            // walk all the editors and connect this toolbarScope to the editors.... if we need to.  This way, it does\n            // not matter if we register the editors after the toolbars or not\n            // Note the editor will ignore this toolbarScope if it is not connected to it...\n            angular.forEach(editors, function(_editor){\n                _editor._registerToolbarScope(toolbarScope);\n            });\n            touchModification();\n        },\n        // retrieve toolbar by name, largely used by testing suites only\n        retrieveToolbar: function(name){\n            return toolbars[name];\n        },\n        // retrieve toolbars by editor name, largely used by testing suites only\n        retrieveToolbarsViaEditor: function(name){\n            var result = [], _this = this;\n            angular.forEach(this.retrieveEditor(name).toolbars, function(name){\n                result.push(_this.retrieveToolbar(name));\n            });\n            return result;\n        },\n        unregisterToolbar: function(name){\n            delete toolbars[name];\n            touchModification();\n        },\n        // functions for updating the toolbar buttons display\n        updateToolsDisplay: function(newTaTools){\n            // pass a partial struct of the taTools, this allows us to update the tools on the fly, will not change the defaults.\n            var _this = this;\n            angular.forEach(newTaTools, function(_newTool, key){\n                _this.updateToolDisplay(key, _newTool);\n            });\n        },\n        // this function resets all toolbars to their default tool definitions\n        resetToolsDisplay: function(){\n            var _this = this;\n            angular.forEach(taTools, function(_newTool, key){\n                _this.resetToolDisplay(key);\n            });\n            touchModification();\n        },\n        // update a tool on all toolbars\n        updateToolDisplay: function(toolKey, _newTool){\n            var _this = this;\n            angular.forEach(toolbars, function(toolbarScope, toolbarKey){\n                _this.updateToolbarToolDisplay(toolbarKey, toolKey, _newTool);\n            });\n            touchModification();\n        },\n        // resets a tool to the default/starting state on all toolbars\n        resetToolDisplay: function(toolKey){\n            var _this = this;\n            angular.forEach(toolbars, function(toolbarScope, toolbarKey){\n                _this.resetToolbarToolDisplay(toolbarKey, toolKey);\n            });\n            touchModification();\n        },\n        // update a tool on a specific toolbar\n        updateToolbarToolDisplay: function(toolbarKey, toolKey, _newTool){\n            if(toolbars[toolbarKey]) toolbars[toolbarKey].updateToolDisplay(toolKey, _newTool);\n            else throw('textAngular Error: No Toolbar with name \"' + toolbarKey + '\" exists');\n        },\n        // reset a tool on a specific toolbar to it's default starting value\n        resetToolbarToolDisplay: function(toolbarKey, toolKey){\n            if(toolbars[toolbarKey]) toolbars[toolbarKey].updateToolDisplay(toolKey, taTools[toolKey], true);\n            else throw('textAngular Error: No Toolbar with name \"' + toolbarKey + '\" exists');\n        },\n        // removes a tool from all toolbars and it's definition\n        removeTool: function(toolKey){\n            delete taTools[toolKey];\n            angular.forEach(toolbars, function(toolbarScope){\n                delete toolbarScope.tools[toolKey];\n                for(var i = 0; i < toolbarScope.toolbar.length; i++){\n                    var toolbarIndex;\n                    for(var j = 0; j < toolbarScope.toolbar[i].length; j++){\n                        if(toolbarScope.toolbar[i][j] === toolKey){\n                            toolbarIndex = {\n                                group: i,\n                                index: j\n                            };\n                            break;\n                        }\n                        if(toolbarIndex !== undefined) break;\n                    }\n                    if(toolbarIndex !== undefined){\n                        toolbarScope.toolbar[toolbarIndex.group].slice(toolbarIndex.index, 1);\n                        toolbarScope._$element.children().eq(toolbarIndex.group).children().eq(toolbarIndex.index).remove();\n                    }\n                }\n            });\n            touchModification();\n        },\n        // toolkey, toolDefinition are required. If group is not specified will pick the last group, if index isnt defined will append to group\n        addTool: function(toolKey, toolDefinition, group, index){\n            taRegisterTool(toolKey, toolDefinition);\n            angular.forEach(toolbars, function(toolbarScope){\n                toolbarScope.addTool(toolKey, toolDefinition, group, index);\n            });\n            touchModification();\n        },\n        // adds a Tool but only to one toolbar not all\n        addToolToToolbar: function(toolKey, toolDefinition, toolbarKey, group, index){\n            taRegisterTool(toolKey, toolDefinition);\n            toolbars[toolbarKey].addTool(toolKey, toolDefinition, group, index);\n            touchModification();\n        },\n        // this is used when externally the html of an editor has been changed and textAngular needs to be notified to update the model.\n        // this will call a $digest if not already happening\n        refreshEditor: function(name){\n            if(editors[name]){\n                editors[name].scope.updateTaBindtaTextElement();\n                /* istanbul ignore else: phase catch */\n                if(!editors[name].scope.$$phase) editors[name].scope.$digest();\n            }else throw('textAngular Error: No Editor with name \"' + name + '\" exists');\n            touchModification();\n        },\n        // this is used by taBind to send a key command in response to a special key event\n        sendKeyCommand: function(scope, event){\n            var _editor = editors[scope._name];\n            /* istanbul ignore else: if nothing to do, do nothing */\n            if (_editor && _editor.editorFunctions.sendKeyCommand(event)) {\n                /* istanbul ignore else: don't run if already running */\n                if(!scope._bUpdateSelectedStyles){\n                    scope.updateSelectedStyles();\n                }\n                event.preventDefault();\n                return false;\n            }\n        },\n        //\n        // When a toolbar and tools are created, it isn't until there is a key event or mouse event\n        // that the updateSelectedStyles() is called behind the scenes.\n        // This function forces an update through the existing editors to help the application make sure\n        // the inital state is correct.\n        //\n        updateStyles: updateStyles,\n        // return the current version of textAngular in use to the user\n        getVersion: function () { return textAngularVersion; },\n        // for testing\n        getToolbarScopes: function () {\n            var tmp=[];\n            angular.forEach(editors, function (_editor) {\n                tmp = tmp.concat(_editor.toolbarScopes);\n            });\n            return tmp;\n        }\n/********************** not functional yet\n        // save the selection ('range') for the given editor\n        saveFocusSelection: function (name, range) {\n            editors[name].savedFocusRange = range;\n        },\n        // restore the saved selection from when the focus was lost\n        restoreFocusSelection: function(name, scope) {\n            // we only do this if NOT focussed and saved...\n            if (editors[name].savedFocusRange && !scope.focussed) {\n                try {\n                    var _r = rangy.restoreRange(editors[name].savedFocusRange);\n                    var _sel = rangy.getSelection();\n                    _sel.addRange(_r);\n                } catch(e) {}\n            }\n        }\n*************/\n    };\n}]);\ntextAngular.directive('textAngularToolbar', [\n    '$compile', 'textAngularManager', 'taOptions', 'taTools', 'taToolExecuteAction', '$window',\n    function($compile, textAngularManager, taOptions, taTools, taToolExecuteAction, $window){\n        return {\n            scope: {\n                name: '@' // a name IS required\n            },\n            restrict: \"EA\",\n            link: function(scope, element, attrs){\n                if(!scope.name || scope.name === '') throw('textAngular Error: A toolbar requires a name');\n                angular.extend(scope, angular.copy(taOptions));\n                if(attrs.taToolbar)\t\t\t\t\t\tscope.toolbar = scope.$parent.$eval(attrs.taToolbar);\n                if(attrs.taToolbarClass)\t\t\t\tscope.classes.toolbar = attrs.taToolbarClass;\n                if(attrs.taToolbarGroupClass)\t\t\tscope.classes.toolbarGroup = attrs.taToolbarGroupClass;\n                if(attrs.taToolbarButtonClass)\t\t\tscope.classes.toolbarButton = attrs.taToolbarButtonClass;\n                if(attrs.taToolbarActiveButtonClass)\tscope.classes.toolbarButtonActive = attrs.taToolbarActiveButtonClass;\n                if(attrs.taFocussedClass)\t\t\t\tscope.classes.focussed = attrs.taFocussedClass;\n\n                scope.disabled = true;\n                scope.focussed = false;\n                scope._$element = element;\n                element[0].innerHTML = '';\n                element.addClass(\"ta-toolbar \" + scope.classes.toolbar);\n\n                scope.$watch('focussed', function(){\n                    if(scope.focussed) element.addClass(scope.classes.focussed);\n                    else element.removeClass(scope.classes.focussed);\n                });\n\n                var setupToolElement = function(toolDefinition, toolScope){\n                    var toolElement;\n                    if(toolDefinition && toolDefinition.display){\n                        toolElement = angular.element(toolDefinition.display);\n                    }\n                    else toolElement = angular.element(\"<button type='button'>\");\n\n                    if(toolDefinition && toolDefinition[\"class\"]) toolElement.addClass(toolDefinition[\"class\"]);\n                    else toolElement.addClass(scope.classes.toolbarButton);\n\n                    toolElement.attr('name', toolScope.name);\n                    // important to not take focus from the main text/html entry\n                    toolElement.attr('ta-button', 'ta-button');\n                    toolElement.attr('ng-disabled', 'isDisabled()');\n                    toolElement.attr('tabindex', '-1');\n                    toolElement.attr('ng-click', 'executeAction()');\n                    toolElement.attr('ng-class', 'displayActiveToolClass(active)');\n\n                    if (toolDefinition && toolDefinition.tooltiptext) {\n                        toolElement.attr('title', toolDefinition.tooltiptext);\n                    }\n                    if(toolDefinition && !toolDefinition.display && !toolScope._display){\n                        // first clear out the current contents if any\n                        toolElement[0].innerHTML = '';\n                        // add the buttonText\n                        if(toolDefinition.buttontext) toolElement[0].innerHTML = toolDefinition.buttontext;\n                        // add the icon to the front of the button if there is content\n                        if(toolDefinition.iconclass){\n                            var icon = angular.element('<i>'), content = toolElement[0].innerHTML;\n                            icon.addClass(toolDefinition.iconclass);\n                            toolElement[0].innerHTML = '';\n                            toolElement.append(icon);\n                            if(content && content !== '') toolElement.append('&nbsp;' + content);\n                        }\n                    }\n\n                    toolScope._lastToolDefinition = angular.copy(toolDefinition);\n\n                    return $compile(toolElement)(toolScope);\n                };\n\n                // Keep a reference for updating the active states later\n                scope.tools = {};\n                // create the tools in the toolbar\n                // default functions and values to prevent errors in testing and on init\n                scope._parent = {\n                    disabled: true,\n                    showHtml: false,\n                    queryFormatBlockState: function(){ return false; },\n                    queryCommandState: function(){ return false; }\n                };\n                var defaultChildScope = {\n                    $window: $window,\n                    $editor: function(){\n                        // dynamically gets the editor as it is set\n                        return scope._parent;\n                    },\n                    isDisabled: function(){\n                        // view selection button is always enabled since it doesn not depend on a selction!\n                        if (this.name === 'html' && scope._parent.startAction) {\n                            return false;\n                        }\n                        // to set your own disabled logic set a function or boolean on the tool called 'disabled'\n                        return ( // this bracket is important as without it it just returns the first bracket and ignores the rest\n                            // when the button's disabled function/value evaluates to true\n                            (typeof this.$eval('disabled') !== 'function' && this.$eval('disabled')) || this.$eval('disabled()') ||\n                            // all buttons except the HTML Switch button should be disabled in the showHtml (RAW html) mode\n                            (this.name !== 'html' && this.$editor().showHtml) ||\n                            // if the toolbar is disabled\n                            this.$parent.disabled ||\n                            // if the current editor is disabled\n                            this.$editor().disabled\n                        );\n                    },\n                    displayActiveToolClass: function(active){\n                        return (active)? scope.classes.toolbarButtonActive : '';\n                    },\n                    executeAction: taToolExecuteAction\n                };\n\n                angular.forEach(scope.toolbar, function(group){\n                    // setup the toolbar group\n                    var groupElement = angular.element(\"<div>\");\n                    groupElement.addClass(scope.classes.toolbarGroup);\n                    angular.forEach(group, function(tool){\n                        // init and add the tools to the group\n                        // a tool name (key name from taTools struct)\n                        //creates a child scope of the main angularText scope and then extends the childScope with the functions of this particular tool\n                        // reference to the scope and element kept\n                        scope.tools[tool] = angular.extend(scope.$new(true), taTools[tool], defaultChildScope, {name: tool});\n                        scope.tools[tool].$element = setupToolElement(taTools[tool], scope.tools[tool]);\n                        // append the tool compiled with the childScope to the group element\n                        groupElement.append(scope.tools[tool].$element);\n                    });\n                    // append the group to the toolbar\n                    element.append(groupElement);\n                });\n\n                // update a tool\n                // if a value is set to null, remove from the display\n                // when forceNew is set to true it will ignore all previous settings, used to reset to taTools definition\n                // to reset to defaults pass in taTools[key] as _newTool and forceNew as true, ie `updateToolDisplay(key, taTools[key], true);`\n                scope.updateToolDisplay = function(key, _newTool, forceNew){\n                    var toolInstance = scope.tools[key];\n                    if(toolInstance){\n                        // get the last toolDefinition, then override with the new definition\n                        if(toolInstance._lastToolDefinition && !forceNew) _newTool = angular.extend({}, toolInstance._lastToolDefinition, _newTool);\n                        if(_newTool.buttontext === null && _newTool.iconclass === null && _newTool.display === null)\n                            throw('textAngular Error: Tool Definition for updating \"' + key + '\" does not have a valid display/iconclass/buttontext value');\n\n                        // if tool is defined on this toolbar, update/redo the tool\n                        if(_newTool.buttontext === null){\n                            delete _newTool.buttontext;\n                        }\n                        if(_newTool.iconclass === null){\n                            delete _newTool.iconclass;\n                        }\n                        if(_newTool.display === null){\n                            delete _newTool.display;\n                        }\n\n                        var toolElement = setupToolElement(_newTool, toolInstance);\n                        toolInstance.$element.replaceWith(toolElement);\n                        toolInstance.$element = toolElement;\n                    }\n                };\n\n                // we assume here that all values passed are valid and correct\n                scope.addTool = function(key, _newTool, groupIndex, index){\n                    scope.tools[key] = angular.extend(scope.$new(true), taTools[key], defaultChildScope, {name: key});\n                    scope.tools[key].$element = setupToolElement(taTools[key], scope.tools[key]);\n                    var group;\n                    if(groupIndex === undefined) groupIndex = scope.toolbar.length - 1;\n                    group = angular.element(element.children()[groupIndex]);\n\n                    if(index === undefined){\n                        group.append(scope.tools[key].$element);\n                        scope.toolbar[groupIndex][scope.toolbar[groupIndex].length - 1] = key;\n                    }else{\n                        group.children().eq(index).after(scope.tools[key].$element);\n                        scope.toolbar[groupIndex][index] = key;\n                    }\n                };\n\n                textAngularManager.registerToolbar(scope);\n\n                scope.$on('$destroy', function(){\n                    textAngularManager.unregisterToolbar(scope.name);\n                });\n            }\n        };\n    }\n]);\ntextAngular.directive('textAngularVersion', ['textAngularManager',\n    function(textAngularManager) {\n        var version = textAngularManager.getVersion();\n        return {\n            restrict: \"EA\",\n            link: function (scope, element, attrs) {\n                element.html(version);\n            }\n        };\n    }\n]);\n\nreturn textAngular.name;\n\n}));\n"]}