tinymce.js 1.2 MB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742227432274422745227462274722748227492275022751227522275322754227552275622757227582275922760227612276222763227642276522766227672276822769227702277122772227732277422775227762277722778227792278022781227822278322784227852278622787227882278922790227912279222793227942279522796227972279822799228002280122802228032280422805228062280722808228092281022811228122281322814228152281622817228182281922820228212282222823228242282522826228272282822829228302283122832228332283422835228362283722838228392284022841228422284322844228452284622847228482284922850228512285222853228542285522856228572285822859228602286122862228632286422865228662286722868228692287022871228722287322874228752287622877228782287922880228812288222883228842288522886228872288822889228902289122892228932289422895228962289722898228992290022901229022290322904229052290622907229082290922910229112291222913229142291522916229172291822919229202292122922229232292422925229262292722928229292293022931229322293322934229352293622937229382293922940229412294222943229442294522946229472294822949229502295122952229532295422955229562295722958229592296022961229622296322964229652296622967229682296922970229712297222973229742297522976229772297822979229802298122982229832298422985229862298722988229892299022991229922299322994229952299622997229982299923000230012300223003230042300523006230072300823009230102301123012230132301423015230162301723018230192302023021230222302323024230252302623027230282302923030230312303223033230342303523036230372303823039230402304123042230432304423045230462304723048230492305023051230522305323054230552305623057230582305923060230612306223063230642306523066230672306823069230702307123072230732307423075230762307723078230792308023081230822308323084230852308623087230882308923090230912309223093230942309523096230972309823099231002310123102231032310423105231062310723108231092311023111231122311323114231152311623117231182311923120231212312223123231242312523126231272312823129231302313123132231332313423135231362313723138231392314023141231422314323144231452314623147231482314923150231512315223153231542315523156231572315823159231602316123162231632316423165231662316723168231692317023171231722317323174231752317623177231782317923180231812318223183231842318523186231872318823189231902319123192231932319423195231962319723198231992320023201232022320323204232052320623207232082320923210232112321223213232142321523216232172321823219232202322123222232232322423225232262322723228232292323023231232322323323234232352323623237232382323923240232412324223243232442324523246232472324823249232502325123252232532325423255232562325723258232592326023261232622326323264232652326623267232682326923270232712327223273232742327523276232772327823279232802328123282232832328423285232862328723288232892329023291232922329323294232952329623297232982329923300233012330223303233042330523306233072330823309233102331123312233132331423315233162331723318233192332023321233222332323324233252332623327233282332923330233312333223333233342333523336233372333823339233402334123342233432334423345233462334723348233492335023351233522335323354233552335623357233582335923360233612336223363233642336523366233672336823369233702337123372233732337423375233762337723378233792338023381233822338323384233852338623387233882338923390233912339223393233942339523396233972339823399234002340123402234032340423405234062340723408234092341023411234122341323414234152341623417234182341923420234212342223423234242342523426234272342823429234302343123432234332343423435234362343723438234392344023441234422344323444234452344623447234482344923450234512345223453234542345523456234572345823459234602346123462234632346423465234662346723468234692347023471234722347323474234752347623477234782347923480234812348223483234842348523486234872348823489234902349123492234932349423495234962349723498234992350023501235022350323504235052350623507235082350923510235112351223513235142351523516235172351823519235202352123522235232352423525235262352723528235292353023531235322353323534235352353623537235382353923540235412354223543235442354523546235472354823549235502355123552235532355423555235562355723558235592356023561235622356323564235652356623567235682356923570235712357223573235742357523576235772357823579235802358123582235832358423585235862358723588235892359023591235922359323594235952359623597235982359923600236012360223603236042360523606236072360823609236102361123612236132361423615236162361723618236192362023621236222362323624236252362623627236282362923630236312363223633236342363523636236372363823639236402364123642236432364423645236462364723648236492365023651236522365323654236552365623657236582365923660236612366223663236642366523666236672366823669236702367123672236732367423675236762367723678236792368023681236822368323684236852368623687236882368923690236912369223693236942369523696236972369823699237002370123702237032370423705237062370723708237092371023711237122371323714237152371623717237182371923720237212372223723237242372523726237272372823729237302373123732237332373423735237362373723738237392374023741237422374323744237452374623747237482374923750237512375223753237542375523756237572375823759237602376123762237632376423765237662376723768237692377023771237722377323774237752377623777237782377923780237812378223783237842378523786237872378823789237902379123792237932379423795237962379723798237992380023801238022380323804238052380623807238082380923810238112381223813238142381523816238172381823819238202382123822238232382423825238262382723828238292383023831238322383323834238352383623837238382383923840238412384223843238442384523846238472384823849238502385123852238532385423855238562385723858238592386023861238622386323864238652386623867238682386923870238712387223873238742387523876238772387823879238802388123882238832388423885238862388723888238892389023891238922389323894238952389623897238982389923900239012390223903239042390523906239072390823909239102391123912239132391423915239162391723918239192392023921239222392323924239252392623927239282392923930239312393223933239342393523936239372393823939239402394123942239432394423945239462394723948239492395023951239522395323954239552395623957239582395923960239612396223963239642396523966239672396823969239702397123972239732397423975239762397723978239792398023981239822398323984239852398623987239882398923990239912399223993239942399523996239972399823999240002400124002240032400424005240062400724008240092401024011240122401324014240152401624017240182401924020240212402224023240242402524026240272402824029240302403124032240332403424035240362403724038240392404024041240422404324044240452404624047240482404924050240512405224053240542405524056240572405824059240602406124062240632406424065240662406724068240692407024071240722407324074240752407624077240782407924080240812408224083240842408524086240872408824089240902409124092240932409424095240962409724098240992410024101241022410324104241052410624107241082410924110241112411224113241142411524116241172411824119241202412124122241232412424125241262412724128241292413024131241322413324134241352413624137241382413924140241412414224143241442414524146241472414824149241502415124152241532415424155241562415724158241592416024161241622416324164241652416624167241682416924170241712417224173241742417524176241772417824179241802418124182241832418424185241862418724188241892419024191241922419324194241952419624197241982419924200242012420224203242042420524206242072420824209242102421124212242132421424215242162421724218242192422024221242222422324224242252422624227242282422924230242312423224233242342423524236242372423824239242402424124242242432424424245242462424724248242492425024251242522425324254242552425624257242582425924260242612426224263242642426524266242672426824269242702427124272242732427424275242762427724278242792428024281242822428324284242852428624287242882428924290242912429224293242942429524296242972429824299243002430124302243032430424305243062430724308243092431024311243122431324314243152431624317243182431924320243212432224323243242432524326243272432824329243302433124332243332433424335243362433724338243392434024341243422434324344243452434624347243482434924350243512435224353243542435524356243572435824359243602436124362243632436424365243662436724368243692437024371243722437324374243752437624377243782437924380243812438224383243842438524386243872438824389243902439124392243932439424395243962439724398243992440024401244022440324404244052440624407244082440924410244112441224413244142441524416244172441824419244202442124422244232442424425244262442724428244292443024431244322443324434244352443624437244382443924440244412444224443244442444524446244472444824449244502445124452244532445424455244562445724458244592446024461244622446324464244652446624467244682446924470244712447224473244742447524476244772447824479244802448124482244832448424485244862448724488244892449024491244922449324494244952449624497244982449924500245012450224503245042450524506245072450824509245102451124512245132451424515245162451724518245192452024521245222452324524245252452624527245282452924530245312453224533245342453524536245372453824539245402454124542245432454424545245462454724548245492455024551245522455324554245552455624557245582455924560245612456224563245642456524566245672456824569245702457124572245732457424575245762457724578245792458024581245822458324584245852458624587245882458924590245912459224593245942459524596245972459824599246002460124602246032460424605246062460724608246092461024611246122461324614246152461624617246182461924620246212462224623246242462524626246272462824629246302463124632246332463424635246362463724638246392464024641246422464324644246452464624647246482464924650246512465224653246542465524656246572465824659246602466124662246632466424665246662466724668246692467024671246722467324674246752467624677246782467924680246812468224683246842468524686246872468824689246902469124692246932469424695246962469724698246992470024701247022470324704247052470624707247082470924710247112471224713247142471524716247172471824719247202472124722247232472424725247262472724728247292473024731247322473324734247352473624737247382473924740247412474224743247442474524746247472474824749247502475124752247532475424755247562475724758247592476024761247622476324764247652476624767247682476924770247712477224773247742477524776247772477824779247802478124782247832478424785247862478724788247892479024791247922479324794247952479624797247982479924800248012480224803248042480524806248072480824809248102481124812248132481424815248162481724818248192482024821248222482324824248252482624827248282482924830248312483224833248342483524836248372483824839248402484124842248432484424845248462484724848248492485024851248522485324854248552485624857248582485924860248612486224863248642486524866248672486824869248702487124872248732487424875248762487724878248792488024881248822488324884248852488624887248882488924890248912489224893248942489524896248972489824899249002490124902249032490424905249062490724908249092491024911249122491324914249152491624917249182491924920249212492224923249242492524926249272492824929249302493124932249332493424935249362493724938249392494024941249422494324944249452494624947249482494924950249512495224953249542495524956249572495824959249602496124962249632496424965249662496724968249692497024971249722497324974249752497624977249782497924980249812498224983249842498524986249872498824989249902499124992249932499424995249962499724998249992500025001250022500325004250052500625007250082500925010250112501225013250142501525016250172501825019250202502125022250232502425025250262502725028250292503025031250322503325034250352503625037250382503925040250412504225043250442504525046250472504825049250502505125052250532505425055250562505725058250592506025061250622506325064250652506625067250682506925070250712507225073250742507525076250772507825079250802508125082250832508425085250862508725088250892509025091250922509325094250952509625097250982509925100251012510225103251042510525106251072510825109251102511125112251132511425115251162511725118251192512025121251222512325124251252512625127251282512925130251312513225133251342513525136251372513825139251402514125142251432514425145251462514725148251492515025151251522515325154251552515625157251582515925160251612516225163251642516525166251672516825169251702517125172251732517425175251762517725178251792518025181251822518325184251852518625187251882518925190251912519225193251942519525196251972519825199252002520125202252032520425205252062520725208252092521025211252122521325214252152521625217252182521925220252212522225223252242522525226252272522825229252302523125232252332523425235252362523725238252392524025241252422524325244252452524625247252482524925250252512525225253252542525525256252572525825259252602526125262252632526425265252662526725268252692527025271252722527325274252752527625277252782527925280252812528225283252842528525286252872528825289252902529125292252932529425295252962529725298252992530025301253022530325304253052530625307253082530925310253112531225313253142531525316253172531825319253202532125322253232532425325253262532725328253292533025331253322533325334253352533625337253382533925340253412534225343253442534525346253472534825349253502535125352253532535425355253562535725358253592536025361253622536325364253652536625367253682536925370253712537225373253742537525376253772537825379253802538125382253832538425385253862538725388253892539025391253922539325394253952539625397253982539925400254012540225403254042540525406254072540825409254102541125412254132541425415254162541725418254192542025421254222542325424254252542625427254282542925430254312543225433254342543525436254372543825439254402544125442254432544425445254462544725448254492545025451254522545325454254552545625457254582545925460254612546225463254642546525466254672546825469254702547125472254732547425475254762547725478254792548025481254822548325484254852548625487254882548925490254912549225493254942549525496254972549825499255002550125502255032550425505255062550725508255092551025511255122551325514255152551625517255182551925520255212552225523255242552525526255272552825529255302553125532255332553425535255362553725538255392554025541255422554325544255452554625547255482554925550255512555225553255542555525556255572555825559255602556125562255632556425565255662556725568255692557025571255722557325574255752557625577255782557925580255812558225583255842558525586255872558825589255902559125592255932559425595255962559725598255992560025601256022560325604256052560625607256082560925610256112561225613256142561525616256172561825619256202562125622256232562425625256262562725628256292563025631256322563325634256352563625637256382563925640256412564225643256442564525646256472564825649256502565125652256532565425655256562565725658256592566025661256622566325664256652566625667256682566925670256712567225673256742567525676256772567825679256802568125682256832568425685256862568725688256892569025691256922569325694256952569625697256982569925700257012570225703257042570525706257072570825709257102571125712257132571425715257162571725718257192572025721257222572325724257252572625727257282572925730257312573225733257342573525736257372573825739257402574125742257432574425745257462574725748257492575025751257522575325754257552575625757257582575925760257612576225763257642576525766257672576825769257702577125772257732577425775257762577725778257792578025781257822578325784257852578625787257882578925790257912579225793257942579525796257972579825799258002580125802258032580425805258062580725808258092581025811258122581325814258152581625817258182581925820258212582225823258242582525826258272582825829258302583125832258332583425835258362583725838258392584025841258422584325844258452584625847258482584925850258512585225853258542585525856258572585825859258602586125862258632586425865258662586725868258692587025871258722587325874258752587625877258782587925880258812588225883258842588525886258872588825889258902589125892258932589425895258962589725898258992590025901259022590325904259052590625907259082590925910259112591225913259142591525916259172591825919259202592125922259232592425925259262592725928259292593025931259322593325934259352593625937259382593925940259412594225943259442594525946259472594825949259502595125952259532595425955259562595725958259592596025961259622596325964259652596625967259682596925970259712597225973259742597525976259772597825979259802598125982259832598425985259862598725988259892599025991259922599325994259952599625997259982599926000260012600226003260042600526006260072600826009260102601126012260132601426015260162601726018260192602026021260222602326024260252602626027260282602926030260312603226033260342603526036260372603826039260402604126042260432604426045260462604726048260492605026051260522605326054260552605626057260582605926060260612606226063260642606526066260672606826069260702607126072260732607426075260762607726078260792608026081260822608326084260852608626087260882608926090260912609226093260942609526096260972609826099261002610126102261032610426105261062610726108261092611026111261122611326114261152611626117261182611926120261212612226123261242612526126261272612826129261302613126132261332613426135261362613726138261392614026141261422614326144261452614626147261482614926150261512615226153261542615526156261572615826159261602616126162261632616426165261662616726168261692617026171261722617326174261752617626177261782617926180261812618226183261842618526186261872618826189261902619126192261932619426195261962619726198261992620026201262022620326204262052620626207262082620926210262112621226213262142621526216262172621826219262202622126222262232622426225262262622726228262292623026231262322623326234262352623626237262382623926240262412624226243262442624526246262472624826249262502625126252262532625426255262562625726258262592626026261262622626326264262652626626267262682626926270262712627226273262742627526276262772627826279262802628126282262832628426285262862628726288262892629026291262922629326294262952629626297262982629926300263012630226303263042630526306263072630826309263102631126312263132631426315263162631726318263192632026321263222632326324263252632626327263282632926330263312633226333263342633526336263372633826339263402634126342263432634426345263462634726348263492635026351263522635326354263552635626357263582635926360263612636226363263642636526366263672636826369263702637126372263732637426375263762637726378263792638026381263822638326384263852638626387263882638926390263912639226393263942639526396263972639826399264002640126402264032640426405264062640726408264092641026411264122641326414264152641626417264182641926420264212642226423264242642526426264272642826429264302643126432264332643426435264362643726438264392644026441264422644326444264452644626447264482644926450264512645226453264542645526456264572645826459264602646126462264632646426465264662646726468264692647026471264722647326474264752647626477264782647926480264812648226483264842648526486264872648826489264902649126492264932649426495264962649726498264992650026501265022650326504265052650626507265082650926510265112651226513265142651526516265172651826519265202652126522265232652426525265262652726528265292653026531265322653326534265352653626537265382653926540265412654226543265442654526546265472654826549265502655126552265532655426555265562655726558265592656026561265622656326564265652656626567265682656926570265712657226573265742657526576265772657826579265802658126582265832658426585265862658726588265892659026591265922659326594265952659626597265982659926600266012660226603266042660526606266072660826609266102661126612266132661426615266162661726618266192662026621266222662326624266252662626627266282662926630266312663226633266342663526636266372663826639266402664126642266432664426645266462664726648266492665026651266522665326654266552665626657266582665926660266612666226663266642666526666266672666826669266702667126672266732667426675266762667726678266792668026681266822668326684266852668626687266882668926690266912669226693266942669526696266972669826699267002670126702267032670426705267062670726708267092671026711267122671326714267152671626717267182671926720267212672226723267242672526726267272672826729267302673126732267332673426735267362673726738267392674026741267422674326744267452674626747267482674926750267512675226753267542675526756267572675826759267602676126762267632676426765267662676726768267692677026771267722677326774267752677626777267782677926780267812678226783267842678526786267872678826789267902679126792267932679426795267962679726798267992680026801268022680326804268052680626807268082680926810268112681226813268142681526816268172681826819268202682126822268232682426825268262682726828268292683026831268322683326834268352683626837268382683926840268412684226843268442684526846268472684826849268502685126852268532685426855268562685726858268592686026861268622686326864268652686626867268682686926870268712687226873268742687526876268772687826879268802688126882268832688426885268862688726888268892689026891268922689326894268952689626897268982689926900269012690226903269042690526906269072690826909269102691126912269132691426915269162691726918269192692026921269222692326924269252692626927269282692926930269312693226933269342693526936269372693826939269402694126942269432694426945269462694726948269492695026951269522695326954269552695626957269582695926960269612696226963269642696526966269672696826969269702697126972269732697426975269762697726978269792698026981269822698326984269852698626987269882698926990269912699226993269942699526996269972699826999270002700127002270032700427005270062700727008270092701027011270122701327014270152701627017270182701927020270212702227023270242702527026270272702827029270302703127032270332703427035270362703727038270392704027041270422704327044270452704627047270482704927050270512705227053270542705527056270572705827059270602706127062270632706427065270662706727068270692707027071270722707327074270752707627077270782707927080270812708227083270842708527086270872708827089270902709127092270932709427095270962709727098270992710027101271022710327104271052710627107271082710927110271112711227113271142711527116271172711827119271202712127122271232712427125271262712727128271292713027131271322713327134271352713627137271382713927140271412714227143271442714527146271472714827149271502715127152271532715427155271562715727158271592716027161271622716327164271652716627167271682716927170271712717227173271742717527176271772717827179271802718127182271832718427185271862718727188271892719027191271922719327194271952719627197271982719927200272012720227203272042720527206272072720827209272102721127212272132721427215272162721727218272192722027221272222722327224272252722627227272282722927230272312723227233272342723527236272372723827239272402724127242272432724427245272462724727248272492725027251272522725327254272552725627257272582725927260272612726227263272642726527266272672726827269272702727127272272732727427275272762727727278272792728027281272822728327284272852728627287272882728927290272912729227293272942729527296272972729827299273002730127302273032730427305273062730727308273092731027311273122731327314273152731627317273182731927320273212732227323273242732527326273272732827329273302733127332273332733427335273362733727338273392734027341273422734327344273452734627347273482734927350273512735227353273542735527356273572735827359273602736127362273632736427365273662736727368273692737027371273722737327374273752737627377273782737927380273812738227383273842738527386273872738827389273902739127392273932739427395273962739727398273992740027401274022740327404274052740627407274082740927410274112741227413274142741527416274172741827419274202742127422274232742427425274262742727428274292743027431274322743327434274352743627437274382743927440274412744227443274442744527446274472744827449274502745127452274532745427455274562745727458274592746027461274622746327464274652746627467274682746927470274712747227473274742747527476274772747827479274802748127482274832748427485274862748727488274892749027491274922749327494274952749627497274982749927500275012750227503275042750527506275072750827509275102751127512275132751427515275162751727518275192752027521275222752327524275252752627527275282752927530275312753227533275342753527536275372753827539275402754127542275432754427545275462754727548275492755027551275522755327554275552755627557275582755927560275612756227563275642756527566275672756827569275702757127572275732757427575275762757727578275792758027581275822758327584275852758627587275882758927590275912759227593275942759527596275972759827599276002760127602276032760427605276062760727608276092761027611276122761327614276152761627617276182761927620276212762227623276242762527626276272762827629276302763127632276332763427635276362763727638276392764027641276422764327644276452764627647276482764927650276512765227653276542765527656276572765827659276602766127662276632766427665276662766727668276692767027671276722767327674276752767627677276782767927680276812768227683276842768527686276872768827689276902769127692276932769427695276962769727698276992770027701277022770327704277052770627707277082770927710277112771227713277142771527716277172771827719277202772127722277232772427725277262772727728277292773027731277322773327734277352773627737277382773927740277412774227743277442774527746277472774827749277502775127752277532775427755277562775727758277592776027761277622776327764277652776627767277682776927770277712777227773277742777527776277772777827779277802778127782277832778427785277862778727788277892779027791277922779327794277952779627797277982779927800278012780227803278042780527806278072780827809278102781127812278132781427815278162781727818278192782027821278222782327824278252782627827278282782927830278312783227833278342783527836278372783827839278402784127842278432784427845278462784727848278492785027851278522785327854278552785627857278582785927860278612786227863278642786527866278672786827869278702787127872278732787427875278762787727878278792788027881278822788327884278852788627887278882788927890278912789227893278942789527896278972789827899279002790127902279032790427905279062790727908279092791027911279122791327914279152791627917279182791927920279212792227923279242792527926279272792827929279302793127932279332793427935279362793727938279392794027941279422794327944279452794627947279482794927950279512795227953279542795527956279572795827959279602796127962279632796427965279662796727968279692797027971279722797327974279752797627977279782797927980279812798227983279842798527986279872798827989279902799127992279932799427995279962799727998279992800028001280022800328004280052800628007280082800928010280112801228013280142801528016280172801828019280202802128022280232802428025280262802728028280292803028031280322803328034280352803628037280382803928040280412804228043280442804528046280472804828049280502805128052280532805428055280562805728058280592806028061280622806328064280652806628067280682806928070280712807228073280742807528076280772807828079280802808128082280832808428085280862808728088280892809028091280922809328094280952809628097280982809928100281012810228103281042810528106281072810828109281102811128112281132811428115281162811728118281192812028121281222812328124281252812628127281282812928130281312813228133281342813528136281372813828139281402814128142281432814428145281462814728148281492815028151281522815328154281552815628157281582815928160281612816228163281642816528166281672816828169281702817128172281732817428175281762817728178281792818028181281822818328184281852818628187281882818928190281912819228193281942819528196281972819828199282002820128202282032820428205282062820728208282092821028211282122821328214282152821628217282182821928220282212822228223282242822528226282272822828229282302823128232282332823428235282362823728238282392824028241282422824328244282452824628247282482824928250282512825228253282542825528256282572825828259282602826128262282632826428265282662826728268282692827028271282722827328274282752827628277282782827928280282812828228283282842828528286282872828828289282902829128292282932829428295282962829728298282992830028301283022830328304283052830628307283082830928310283112831228313283142831528316283172831828319283202832128322283232832428325283262832728328283292833028331283322833328334283352833628337283382833928340283412834228343283442834528346283472834828349283502835128352283532835428355283562835728358283592836028361283622836328364283652836628367283682836928370283712837228373283742837528376283772837828379283802838128382283832838428385283862838728388283892839028391283922839328394283952839628397283982839928400284012840228403284042840528406284072840828409284102841128412284132841428415284162841728418284192842028421284222842328424284252842628427284282842928430284312843228433284342843528436284372843828439284402844128442284432844428445284462844728448284492845028451284522845328454284552845628457284582845928460284612846228463284642846528466284672846828469284702847128472284732847428475284762847728478284792848028481284822848328484284852848628487284882848928490284912849228493284942849528496284972849828499285002850128502285032850428505285062850728508285092851028511285122851328514285152851628517285182851928520285212852228523285242852528526285272852828529285302853128532285332853428535285362853728538285392854028541285422854328544285452854628547285482854928550285512855228553285542855528556285572855828559285602856128562285632856428565285662856728568285692857028571285722857328574285752857628577285782857928580285812858228583285842858528586285872858828589285902859128592285932859428595285962859728598285992860028601286022860328604286052860628607286082860928610286112861228613286142861528616286172861828619286202862128622286232862428625286262862728628286292863028631286322863328634286352863628637286382863928640286412864228643286442864528646286472864828649286502865128652286532865428655286562865728658286592866028661286622866328664286652866628667286682866928670286712867228673286742867528676286772867828679286802868128682286832868428685286862868728688286892869028691286922869328694286952869628697286982869928700287012870228703287042870528706287072870828709287102871128712287132871428715287162871728718287192872028721287222872328724287252872628727287282872928730287312873228733287342873528736287372873828739287402874128742287432874428745287462874728748287492875028751287522875328754287552875628757287582875928760287612876228763287642876528766287672876828769287702877128772287732877428775287762877728778287792878028781287822878328784287852878628787287882878928790287912879228793287942879528796287972879828799288002880128802288032880428805288062880728808288092881028811288122881328814288152881628817288182881928820288212882228823288242882528826288272882828829288302883128832288332883428835288362883728838288392884028841288422884328844288452884628847288482884928850288512885228853288542885528856288572885828859288602886128862288632886428865288662886728868288692887028871288722887328874288752887628877288782887928880288812888228883288842888528886288872888828889288902889128892288932889428895288962889728898288992890028901289022890328904289052890628907289082890928910289112891228913289142891528916289172891828919289202892128922289232892428925289262892728928289292893028931289322893328934289352893628937289382893928940289412894228943289442894528946289472894828949289502895128952289532895428955289562895728958289592896028961289622896328964289652896628967289682896928970289712897228973289742897528976289772897828979289802898128982289832898428985289862898728988289892899028991289922899328994289952899628997289982899929000290012900229003290042900529006290072900829009290102901129012290132901429015290162901729018290192902029021290222902329024290252902629027290282902929030290312903229033290342903529036290372903829039290402904129042290432904429045290462904729048290492905029051290522905329054290552905629057290582905929060290612906229063290642906529066290672906829069290702907129072290732907429075290762907729078290792908029081290822908329084290852908629087290882908929090290912909229093290942909529096290972909829099291002910129102291032910429105291062910729108291092911029111291122911329114291152911629117291182911929120291212912229123291242912529126291272912829129291302913129132291332913429135291362913729138291392914029141291422914329144291452914629147291482914929150291512915229153291542915529156291572915829159291602916129162291632916429165291662916729168291692917029171291722917329174291752917629177291782917929180291812918229183291842918529186291872918829189291902919129192291932919429195291962919729198291992920029201292022920329204292052920629207292082920929210292112921229213292142921529216292172921829219292202922129222292232922429225292262922729228292292923029231292322923329234292352923629237292382923929240292412924229243292442924529246292472924829249292502925129252292532925429255292562925729258292592926029261292622926329264292652926629267292682926929270292712927229273292742927529276292772927829279292802928129282292832928429285292862928729288292892929029291292922929329294292952929629297292982929929300293012930229303293042930529306293072930829309293102931129312293132931429315293162931729318293192932029321293222932329324293252932629327293282932929330293312933229333293342933529336293372933829339293402934129342293432934429345293462934729348293492935029351293522935329354293552935629357293582935929360293612936229363293642936529366293672936829369293702937129372293732937429375293762937729378293792938029381293822938329384293852938629387293882938929390293912939229393293942939529396293972939829399294002940129402294032940429405294062940729408294092941029411294122941329414294152941629417294182941929420294212942229423294242942529426294272942829429294302943129432294332943429435294362943729438294392944029441294422944329444294452944629447294482944929450294512945229453294542945529456294572945829459294602946129462294632946429465294662946729468294692947029471294722947329474294752947629477294782947929480294812948229483294842948529486294872948829489294902949129492294932949429495294962949729498294992950029501295022950329504295052950629507295082950929510295112951229513295142951529516295172951829519295202952129522295232952429525295262952729528295292953029531295322953329534295352953629537295382953929540295412954229543295442954529546295472954829549295502955129552295532955429555295562955729558295592956029561295622956329564295652956629567295682956929570295712957229573295742957529576295772957829579295802958129582295832958429585295862958729588295892959029591295922959329594295952959629597295982959929600296012960229603296042960529606296072960829609296102961129612296132961429615296162961729618296192962029621296222962329624296252962629627296282962929630296312963229633296342963529636296372963829639296402964129642296432964429645296462964729648296492965029651296522965329654296552965629657296582965929660296612966229663296642966529666296672966829669296702967129672296732967429675296762967729678296792968029681296822968329684296852968629687296882968929690296912969229693296942969529696296972969829699297002970129702297032970429705297062970729708297092971029711297122971329714297152971629717297182971929720297212972229723297242972529726297272972829729297302973129732297332973429735297362973729738297392974029741297422974329744297452974629747297482974929750297512975229753297542975529756297572975829759297602976129762297632976429765297662976729768297692977029771297722977329774297752977629777297782977929780297812978229783297842978529786297872978829789297902979129792297932979429795297962979729798297992980029801298022980329804298052980629807298082980929810298112981229813298142981529816298172981829819298202982129822298232982429825298262982729828298292983029831298322983329834298352983629837298382983929840298412984229843298442984529846298472984829849298502985129852298532985429855298562985729858298592986029861298622986329864298652986629867298682986929870298712987229873298742987529876298772987829879298802988129882298832988429885298862988729888298892989029891298922989329894298952989629897298982989929900299012990229903299042990529906299072990829909299102991129912299132991429915299162991729918299192992029921299222992329924299252992629927299282992929930299312993229933299342993529936299372993829939299402994129942299432994429945299462994729948299492995029951299522995329954299552995629957299582995929960299612996229963299642996529966299672996829969299702997129972299732997429975299762997729978299792998029981299822998329984299852998629987299882998929990299912999229993299942999529996299972999829999300003000130002300033000430005300063000730008300093001030011300123001330014300153001630017300183001930020300213002230023300243002530026300273002830029300303003130032300333003430035300363003730038300393004030041300423004330044300453004630047300483004930050300513005230053300543005530056300573005830059300603006130062300633006430065300663006730068300693007030071300723007330074300753007630077300783007930080300813008230083300843008530086300873008830089300903009130092300933009430095300963009730098300993010030101301023010330104301053010630107301083010930110301113011230113301143011530116301173011830119301203012130122301233012430125301263012730128301293013030131301323013330134301353013630137301383013930140301413014230143301443014530146301473014830149301503015130152301533015430155301563015730158301593016030161301623016330164301653016630167301683016930170301713017230173301743017530176301773017830179301803018130182301833018430185301863018730188301893019030191301923019330194301953019630197301983019930200302013020230203302043020530206302073020830209302103021130212302133021430215302163021730218302193022030221302223022330224302253022630227302283022930230302313023230233302343023530236302373023830239302403024130242302433024430245302463024730248302493025030251302523025330254302553025630257302583025930260302613026230263302643026530266302673026830269302703027130272302733027430275302763027730278302793028030281302823028330284302853028630287302883028930290302913029230293302943029530296302973029830299303003030130302303033030430305303063030730308303093031030311303123031330314303153031630317303183031930320303213032230323303243032530326303273032830329303303033130332303333033430335303363033730338303393034030341303423034330344303453034630347303483034930350303513035230353303543035530356303573035830359303603036130362303633036430365303663036730368303693037030371303723037330374303753037630377303783037930380303813038230383303843038530386303873038830389303903039130392303933039430395303963039730398303993040030401304023040330404304053040630407304083040930410304113041230413304143041530416304173041830419304203042130422304233042430425304263042730428304293043030431304323043330434304353043630437304383043930440304413044230443304443044530446304473044830449304503045130452304533045430455304563045730458304593046030461304623046330464304653046630467304683046930470304713047230473304743047530476304773047830479304803048130482304833048430485304863048730488304893049030491304923049330494304953049630497304983049930500305013050230503305043050530506305073050830509305103051130512305133051430515305163051730518305193052030521305223052330524305253052630527305283052930530305313053230533305343053530536305373053830539305403054130542305433054430545305463054730548305493055030551305523055330554305553055630557305583055930560305613056230563305643056530566305673056830569305703057130572305733057430575305763057730578305793058030581305823058330584305853058630587305883058930590305913059230593305943059530596305973059830599306003060130602306033060430605306063060730608306093061030611306123061330614306153061630617306183061930620306213062230623306243062530626306273062830629306303063130632306333063430635306363063730638306393064030641306423064330644306453064630647306483064930650306513065230653306543065530656306573065830659306603066130662306633066430665306663066730668306693067030671306723067330674306753067630677306783067930680306813068230683306843068530686306873068830689306903069130692306933069430695306963069730698306993070030701307023070330704307053070630707307083070930710307113071230713307143071530716307173071830719307203072130722307233072430725307263072730728307293073030731307323073330734307353073630737307383073930740307413074230743307443074530746307473074830749307503075130752307533075430755307563075730758307593076030761307623076330764307653076630767307683076930770307713077230773307743077530776307773077830779307803078130782307833078430785307863078730788307893079030791307923079330794307953079630797307983079930800308013080230803308043080530806308073080830809308103081130812308133081430815308163081730818308193082030821308223082330824308253082630827308283082930830308313083230833308343083530836308373083830839308403084130842308433084430845308463084730848308493085030851308523085330854308553085630857308583085930860308613086230863308643086530866308673086830869308703087130872308733087430875308763087730878308793088030881308823088330884308853088630887308883088930890308913089230893308943089530896308973089830899309003090130902309033090430905309063090730908309093091030911309123091330914309153091630917309183091930920309213092230923309243092530926309273092830929309303093130932309333093430935309363093730938309393094030941309423094330944309453094630947309483094930950309513095230953309543095530956309573095830959309603096130962309633096430965309663096730968309693097030971309723097330974309753097630977309783097930980309813098230983309843098530986309873098830989309903099130992309933099430995309963099730998309993100031001310023100331004310053100631007310083100931010310113101231013310143101531016310173101831019310203102131022310233102431025310263102731028310293103031031310323103331034310353103631037310383103931040310413104231043310443104531046310473104831049310503105131052310533105431055310563105731058310593106031061310623106331064310653106631067310683106931070310713107231073310743107531076310773107831079310803108131082310833108431085310863108731088310893109031091310923109331094310953109631097310983109931100311013110231103311043110531106311073110831109311103111131112311133111431115311163111731118311193112031121311223112331124311253112631127311283112931130311313113231133311343113531136311373113831139311403114131142311433114431145311463114731148311493115031151311523115331154311553115631157311583115931160311613116231163311643116531166311673116831169311703117131172311733117431175311763117731178311793118031181311823118331184311853118631187311883118931190311913119231193311943119531196311973119831199312003120131202312033120431205312063120731208312093121031211312123121331214312153121631217312183121931220312213122231223312243122531226312273122831229312303123131232312333123431235312363123731238312393124031241312423124331244312453124631247312483124931250312513125231253312543125531256312573125831259312603126131262312633126431265312663126731268312693127031271312723127331274312753127631277312783127931280312813128231283312843128531286312873128831289312903129131292312933129431295312963129731298312993130031301313023130331304313053130631307313083130931310313113131231313313143131531316313173131831319313203132131322313233132431325313263132731328313293133031331313323133331334313353133631337313383133931340313413134231343313443134531346313473134831349313503135131352313533135431355313563135731358313593136031361313623136331364313653136631367313683136931370313713137231373313743137531376313773137831379313803138131382313833138431385313863138731388313893139031391313923139331394313953139631397313983139931400314013140231403314043140531406314073140831409314103141131412314133141431415314163141731418314193142031421314223142331424314253142631427314283142931430314313143231433314343143531436314373143831439314403144131442314433144431445314463144731448314493145031451314523145331454314553145631457314583145931460314613146231463314643146531466314673146831469314703147131472314733147431475314763147731478314793148031481314823148331484314853148631487314883148931490314913149231493314943149531496314973149831499315003150131502315033150431505315063150731508315093151031511315123151331514315153151631517315183151931520315213152231523315243152531526315273152831529315303153131532315333153431535315363153731538315393154031541315423154331544315453154631547315483154931550315513155231553315543155531556315573155831559315603156131562315633156431565315663156731568315693157031571315723157331574315753157631577315783157931580315813158231583315843158531586315873158831589315903159131592315933159431595315963159731598315993160031601316023160331604316053160631607316083160931610316113161231613316143161531616316173161831619316203162131622316233162431625316263162731628316293163031631316323163331634316353163631637316383163931640316413164231643316443164531646316473164831649316503165131652316533165431655316563165731658316593166031661316623166331664316653166631667316683166931670316713167231673316743167531676316773167831679316803168131682316833168431685316863168731688316893169031691316923169331694316953169631697316983169931700317013170231703317043170531706317073170831709317103171131712317133171431715317163171731718317193172031721317223172331724317253172631727317283172931730317313173231733317343173531736317373173831739317403174131742317433174431745317463174731748317493175031751317523175331754317553175631757317583175931760317613176231763317643176531766317673176831769317703177131772317733177431775317763177731778317793178031781317823178331784317853178631787317883178931790317913179231793317943179531796317973179831799318003180131802318033180431805318063180731808318093181031811318123181331814318153181631817318183181931820318213182231823318243182531826318273182831829318303183131832318333183431835318363183731838318393184031841318423184331844318453184631847318483184931850318513185231853318543185531856318573185831859318603186131862318633186431865318663186731868318693187031871318723187331874318753187631877318783187931880318813188231883318843188531886318873188831889318903189131892318933189431895318963189731898318993190031901319023190331904319053190631907319083190931910319113191231913319143191531916319173191831919319203192131922319233192431925319263192731928319293193031931319323193331934319353193631937319383193931940319413194231943319443194531946319473194831949319503195131952319533195431955319563195731958319593196031961319623196331964319653196631967319683196931970319713197231973319743197531976319773197831979319803198131982319833198431985319863198731988319893199031991319923199331994319953199631997319983199932000320013200232003320043200532006320073200832009320103201132012320133201432015320163201732018320193202032021320223202332024320253202632027320283202932030320313203232033320343203532036320373203832039320403204132042320433204432045320463204732048320493205032051320523205332054320553205632057320583205932060320613206232063320643206532066320673206832069320703207132072320733207432075320763207732078320793208032081320823208332084320853208632087320883208932090320913209232093320943209532096320973209832099321003210132102321033210432105321063210732108321093211032111321123211332114321153211632117321183211932120321213212232123321243212532126321273212832129321303213132132321333213432135321363213732138321393214032141321423214332144321453214632147321483214932150321513215232153321543215532156321573215832159321603216132162321633216432165321663216732168321693217032171321723217332174321753217632177321783217932180321813218232183321843218532186321873218832189321903219132192321933219432195321963219732198321993220032201322023220332204322053220632207322083220932210322113221232213322143221532216322173221832219322203222132222322233222432225322263222732228322293223032231322323223332234322353223632237322383223932240322413224232243322443224532246322473224832249322503225132252322533225432255322563225732258322593226032261322623226332264322653226632267322683226932270322713227232273322743227532276322773227832279322803228132282322833228432285322863228732288322893229032291322923229332294322953229632297322983229932300323013230232303323043230532306323073230832309323103231132312323133231432315323163231732318323193232032321323223232332324323253232632327323283232932330323313233232333323343233532336323373233832339323403234132342323433234432345323463234732348323493235032351323523235332354323553235632357323583235932360323613236232363323643236532366323673236832369323703237132372323733237432375323763237732378323793238032381323823238332384323853238632387323883238932390323913239232393323943239532396323973239832399324003240132402324033240432405324063240732408324093241032411324123241332414324153241632417324183241932420324213242232423324243242532426324273242832429324303243132432324333243432435324363243732438324393244032441324423244332444324453244632447324483244932450324513245232453324543245532456324573245832459324603246132462324633246432465324663246732468324693247032471324723247332474324753247632477324783247932480324813248232483324843248532486324873248832489324903249132492324933249432495324963249732498324993250032501325023250332504325053250632507325083250932510325113251232513325143251532516325173251832519325203252132522325233252432525325263252732528325293253032531325323253332534325353253632537325383253932540325413254232543325443254532546325473254832549325503255132552325533255432555325563255732558325593256032561325623256332564325653256632567325683256932570325713257232573325743257532576325773257832579325803258132582325833258432585325863258732588325893259032591325923259332594325953259632597325983259932600326013260232603326043260532606326073260832609326103261132612326133261432615326163261732618326193262032621326223262332624326253262632627326283262932630326313263232633326343263532636326373263832639326403264132642326433264432645326463264732648326493265032651326523265332654326553265632657326583265932660326613266232663326643266532666326673266832669326703267132672326733267432675326763267732678326793268032681326823268332684326853268632687326883268932690326913269232693326943269532696326973269832699327003270132702327033270432705327063270732708327093271032711327123271332714327153271632717327183271932720327213272232723327243272532726327273272832729327303273132732327333273432735327363273732738327393274032741327423274332744327453274632747327483274932750327513275232753327543275532756327573275832759327603276132762327633276432765327663276732768327693277032771327723277332774327753277632777327783277932780327813278232783327843278532786327873278832789327903279132792327933279432795327963279732798327993280032801328023280332804328053280632807328083280932810328113281232813328143281532816328173281832819328203282132822328233282432825328263282732828328293283032831328323283332834328353283632837328383283932840328413284232843328443284532846328473284832849328503285132852328533285432855328563285732858328593286032861328623286332864328653286632867328683286932870328713287232873328743287532876328773287832879328803288132882328833288432885328863288732888328893289032891328923289332894328953289632897328983289932900329013290232903329043290532906329073290832909329103291132912329133291432915329163291732918329193292032921329223292332924329253292632927329283292932930329313293232933329343293532936329373293832939329403294132942329433294432945329463294732948329493295032951329523295332954329553295632957329583295932960329613296232963329643296532966329673296832969329703297132972329733297432975329763297732978329793298032981329823298332984329853298632987329883298932990329913299232993329943299532996329973299832999330003300133002330033300433005330063300733008330093301033011330123301333014330153301633017330183301933020330213302233023330243302533026330273302833029330303303133032330333303433035330363303733038330393304033041330423304333044330453304633047330483304933050330513305233053330543305533056330573305833059330603306133062330633306433065330663306733068330693307033071330723307333074330753307633077330783307933080330813308233083330843308533086330873308833089330903309133092330933309433095330963309733098330993310033101331023310333104331053310633107331083310933110331113311233113331143311533116331173311833119331203312133122331233312433125331263312733128331293313033131331323313333134331353313633137331383313933140331413314233143331443314533146331473314833149331503315133152331533315433155331563315733158331593316033161331623316333164331653316633167331683316933170331713317233173331743317533176331773317833179331803318133182331833318433185331863318733188331893319033191331923319333194331953319633197331983319933200332013320233203332043320533206332073320833209332103321133212332133321433215332163321733218332193322033221332223322333224332253322633227332283322933230332313323233233332343323533236332373323833239332403324133242332433324433245332463324733248332493325033251332523325333254332553325633257332583325933260332613326233263332643326533266332673326833269332703327133272332733327433275332763327733278332793328033281332823328333284332853328633287332883328933290332913329233293332943329533296332973329833299333003330133302333033330433305333063330733308333093331033311333123331333314333153331633317333183331933320333213332233323333243332533326333273332833329333303333133332333333333433335333363333733338333393334033341333423334333344333453334633347333483334933350333513335233353333543335533356333573335833359333603336133362333633336433365333663336733368333693337033371333723337333374333753337633377333783337933380333813338233383333843338533386333873338833389333903339133392333933339433395333963339733398333993340033401334023340333404334053340633407334083340933410334113341233413334143341533416334173341833419334203342133422334233342433425334263342733428334293343033431334323343333434334353343633437334383343933440334413344233443334443344533446334473344833449334503345133452334533345433455334563345733458334593346033461334623346333464334653346633467334683346933470334713347233473334743347533476334773347833479334803348133482334833348433485334863348733488334893349033491334923349333494334953349633497334983349933500335013350233503335043350533506335073350833509335103351133512335133351433515335163351733518335193352033521335223352333524335253352633527335283352933530335313353233533335343353533536335373353833539335403354133542335433354433545335463354733548335493355033551335523355333554335553355633557335583355933560335613356233563335643356533566335673356833569335703357133572335733357433575335763357733578335793358033581335823358333584335853358633587335883358933590335913359233593335943359533596335973359833599336003360133602336033360433605336063360733608336093361033611336123361333614336153361633617336183361933620336213362233623336243362533626336273362833629336303363133632336333363433635336363363733638336393364033641336423364333644336453364633647336483364933650336513365233653336543365533656336573365833659336603366133662336633366433665336663366733668336693367033671336723367333674336753367633677336783367933680336813368233683336843368533686336873368833689336903369133692336933369433695336963369733698336993370033701337023370333704337053370633707337083370933710337113371233713337143371533716337173371833719337203372133722337233372433725337263372733728337293373033731337323373333734337353373633737337383373933740337413374233743337443374533746337473374833749337503375133752337533375433755337563375733758337593376033761337623376333764337653376633767337683376933770337713377233773337743377533776337773377833779337803378133782337833378433785337863378733788337893379033791337923379333794337953379633797337983379933800338013380233803338043380533806338073380833809338103381133812338133381433815338163381733818338193382033821338223382333824338253382633827338283382933830338313383233833338343383533836338373383833839338403384133842338433384433845338463384733848338493385033851338523385333854338553385633857338583385933860338613386233863338643386533866338673386833869338703387133872338733387433875338763387733878338793388033881338823388333884338853388633887338883388933890338913389233893338943389533896338973389833899339003390133902339033390433905339063390733908339093391033911339123391333914339153391633917339183391933920339213392233923339243392533926339273392833929339303393133932339333393433935339363393733938339393394033941339423394333944339453394633947339483394933950339513395233953339543395533956339573395833959339603396133962339633396433965339663396733968339693397033971339723397333974339753397633977339783397933980339813398233983339843398533986339873398833989339903399133992339933399433995339963399733998339993400034001340023400334004340053400634007340083400934010340113401234013340143401534016340173401834019340203402134022340233402434025340263402734028340293403034031340323403334034340353403634037340383403934040340413404234043340443404534046340473404834049340503405134052340533405434055340563405734058340593406034061340623406334064340653406634067340683406934070340713407234073340743407534076340773407834079340803408134082340833408434085340863408734088340893409034091340923409334094340953409634097340983409934100341013410234103341043410534106341073410834109341103411134112341133411434115341163411734118341193412034121341223412334124341253412634127341283412934130341313413234133341343413534136341373413834139341403414134142341433414434145341463414734148341493415034151341523415334154341553415634157341583415934160341613416234163341643416534166341673416834169341703417134172341733417434175341763417734178341793418034181341823418334184341853418634187341883418934190341913419234193341943419534196341973419834199342003420134202342033420434205342063420734208342093421034211342123421334214342153421634217342183421934220342213422234223342243422534226342273422834229342303423134232342333423434235342363423734238342393424034241342423424334244342453424634247342483424934250342513425234253342543425534256342573425834259342603426134262342633426434265342663426734268342693427034271342723427334274342753427634277342783427934280342813428234283342843428534286342873428834289342903429134292342933429434295342963429734298342993430034301343023430334304343053430634307343083430934310343113431234313343143431534316343173431834319343203432134322343233432434325343263432734328343293433034331343323433334334343353433634337343383433934340343413434234343343443434534346343473434834349343503435134352343533435434355343563435734358343593436034361343623436334364343653436634367343683436934370343713437234373343743437534376343773437834379343803438134382343833438434385343863438734388343893439034391343923439334394343953439634397343983439934400344013440234403344043440534406344073440834409344103441134412344133441434415344163441734418344193442034421344223442334424344253442634427344283442934430344313443234433344343443534436344373443834439344403444134442344433444434445344463444734448344493445034451344523445334454344553445634457344583445934460344613446234463344643446534466344673446834469344703447134472344733447434475344763447734478344793448034481344823448334484344853448634487344883448934490344913449234493344943449534496344973449834499345003450134502345033450434505345063450734508345093451034511345123451334514345153451634517345183451934520345213452234523345243452534526345273452834529345303453134532345333453434535345363453734538345393454034541345423454334544345453454634547345483454934550345513455234553345543455534556345573455834559345603456134562345633456434565345663456734568345693457034571345723457334574345753457634577345783457934580345813458234583345843458534586345873458834589345903459134592345933459434595345963459734598345993460034601346023460334604346053460634607346083460934610346113461234613346143461534616346173461834619346203462134622346233462434625346263462734628346293463034631346323463334634346353463634637346383463934640346413464234643346443464534646346473464834649346503465134652346533465434655346563465734658346593466034661346623466334664346653466634667346683466934670346713467234673346743467534676346773467834679346803468134682346833468434685346863468734688346893469034691346923469334694346953469634697346983469934700347013470234703347043470534706347073470834709347103471134712347133471434715347163471734718347193472034721347223472334724347253472634727347283472934730347313473234733347343473534736347373473834739347403474134742347433474434745347463474734748347493475034751347523475334754347553475634757347583475934760347613476234763347643476534766347673476834769347703477134772347733477434775347763477734778347793478034781347823478334784347853478634787347883478934790347913479234793347943479534796347973479834799348003480134802348033480434805348063480734808348093481034811348123481334814348153481634817348183481934820348213482234823348243482534826348273482834829348303483134832348333483434835348363483734838348393484034841348423484334844348453484634847348483484934850348513485234853348543485534856348573485834859348603486134862348633486434865348663486734868348693487034871348723487334874348753487634877348783487934880348813488234883348843488534886348873488834889348903489134892348933489434895348963489734898348993490034901349023490334904349053490634907349083490934910349113491234913349143491534916349173491834919349203492134922349233492434925349263492734928349293493034931349323493334934349353493634937349383493934940349413494234943349443494534946349473494834949349503495134952349533495434955349563495734958349593496034961349623496334964349653496634967349683496934970349713497234973349743497534976349773497834979349803498134982349833498434985349863498734988349893499034991349923499334994349953499634997349983499935000350013500235003350043500535006350073500835009350103501135012350133501435015350163501735018350193502035021350223502335024350253502635027350283502935030350313503235033350343503535036350373503835039350403504135042350433504435045350463504735048350493505035051350523505335054350553505635057350583505935060350613506235063350643506535066350673506835069350703507135072350733507435075350763507735078350793508035081350823508335084350853508635087350883508935090350913509235093350943509535096350973509835099351003510135102351033510435105351063510735108351093511035111351123511335114351153511635117351183511935120351213512235123351243512535126351273512835129351303513135132351333513435135351363513735138351393514035141351423514335144351453514635147351483514935150351513515235153351543515535156351573515835159351603516135162351633516435165351663516735168351693517035171351723517335174351753517635177351783517935180351813518235183351843518535186351873518835189351903519135192351933519435195351963519735198351993520035201352023520335204352053520635207352083520935210352113521235213352143521535216352173521835219352203522135222352233522435225352263522735228352293523035231352323523335234352353523635237352383523935240352413524235243352443524535246352473524835249352503525135252352533525435255352563525735258352593526035261352623526335264352653526635267352683526935270352713527235273352743527535276352773527835279352803528135282352833528435285352863528735288352893529035291352923529335294352953529635297352983529935300353013530235303353043530535306353073530835309353103531135312353133531435315353163531735318353193532035321353223532335324353253532635327353283532935330353313533235333353343533535336353373533835339353403534135342353433534435345353463534735348353493535035351353523535335354353553535635357353583535935360353613536235363353643536535366353673536835369353703537135372353733537435375353763537735378353793538035381353823538335384353853538635387353883538935390353913539235393353943539535396353973539835399354003540135402354033540435405354063540735408354093541035411354123541335414354153541635417354183541935420354213542235423354243542535426354273542835429354303543135432354333543435435354363543735438354393544035441354423544335444354453544635447354483544935450354513545235453354543545535456354573545835459354603546135462354633546435465354663546735468354693547035471354723547335474354753547635477354783547935480354813548235483354843548535486354873548835489354903549135492354933549435495354963549735498354993550035501355023550335504355053550635507355083550935510355113551235513355143551535516355173551835519355203552135522355233552435525355263552735528355293553035531355323553335534355353553635537355383553935540355413554235543355443554535546355473554835549355503555135552355533555435555355563555735558355593556035561355623556335564355653556635567355683556935570355713557235573355743557535576355773557835579355803558135582355833558435585355863558735588355893559035591355923559335594355953559635597355983559935600356013560235603356043560535606356073560835609356103561135612356133561435615356163561735618356193562035621356223562335624356253562635627356283562935630356313563235633356343563535636356373563835639356403564135642356433564435645356463564735648356493565035651356523565335654356553565635657356583565935660356613566235663356643566535666356673566835669356703567135672356733567435675356763567735678356793568035681356823568335684356853568635687356883568935690356913569235693356943569535696356973569835699357003570135702357033570435705357063570735708357093571035711357123571335714357153571635717357183571935720357213572235723357243572535726357273572835729357303573135732357333573435735357363573735738357393574035741357423574335744357453574635747357483574935750357513575235753357543575535756357573575835759357603576135762357633576435765357663576735768357693577035771357723577335774357753577635777357783577935780357813578235783357843578535786357873578835789357903579135792357933579435795357963579735798357993580035801358023580335804358053580635807358083580935810358113581235813358143581535816358173581835819358203582135822358233582435825358263582735828358293583035831358323583335834358353583635837358383583935840358413584235843358443584535846358473584835849358503585135852358533585435855358563585735858358593586035861358623586335864358653586635867358683586935870358713587235873358743587535876358773587835879358803588135882358833588435885358863588735888358893589035891358923589335894358953589635897358983589935900359013590235903359043590535906359073590835909359103591135912359133591435915359163591735918359193592035921359223592335924359253592635927359283592935930359313593235933359343593535936359373593835939359403594135942359433594435945359463594735948359493595035951359523595335954359553595635957359583595935960359613596235963359643596535966359673596835969359703597135972359733597435975359763597735978359793598035981359823598335984359853598635987359883598935990359913599235993359943599535996359973599835999360003600136002360033600436005360063600736008360093601036011360123601336014360153601636017360183601936020360213602236023360243602536026360273602836029360303603136032360333603436035360363603736038360393604036041360423604336044360453604636047360483604936050360513605236053360543605536056360573605836059360603606136062360633606436065360663606736068360693607036071360723607336074360753607636077360783607936080360813608236083360843608536086360873608836089360903609136092360933609436095360963609736098360993610036101361023610336104361053610636107361083610936110361113611236113361143611536116361173611836119361203612136122361233612436125361263612736128361293613036131361323613336134361353613636137361383613936140361413614236143361443614536146361473614836149361503615136152361533615436155361563615736158361593616036161361623616336164361653616636167361683616936170361713617236173361743617536176361773617836179361803618136182361833618436185361863618736188361893619036191361923619336194361953619636197361983619936200362013620236203362043620536206362073620836209362103621136212362133621436215362163621736218362193622036221362223622336224362253622636227362283622936230362313623236233362343623536236362373623836239362403624136242362433624436245362463624736248362493625036251362523625336254362553625636257362583625936260362613626236263362643626536266362673626836269362703627136272362733627436275362763627736278362793628036281362823628336284362853628636287362883628936290362913629236293362943629536296362973629836299363003630136302363033630436305363063630736308363093631036311363123631336314363153631636317363183631936320363213632236323363243632536326363273632836329363303633136332363333633436335363363633736338363393634036341363423634336344363453634636347363483634936350363513635236353363543635536356363573635836359363603636136362363633636436365363663636736368363693637036371363723637336374363753637636377363783637936380363813638236383363843638536386363873638836389363903639136392363933639436395363963639736398363993640036401364023640336404364053640636407364083640936410364113641236413364143641536416364173641836419364203642136422364233642436425364263642736428364293643036431364323643336434364353643636437364383643936440364413644236443364443644536446364473644836449364503645136452364533645436455364563645736458364593646036461364623646336464364653646636467364683646936470364713647236473364743647536476364773647836479364803648136482364833648436485364863648736488364893649036491364923649336494364953649636497364983649936500365013650236503365043650536506365073650836509365103651136512365133651436515365163651736518365193652036521365223652336524365253652636527365283652936530365313653236533365343653536536365373653836539365403654136542365433654436545365463654736548365493655036551365523655336554365553655636557365583655936560365613656236563365643656536566365673656836569365703657136572365733657436575365763657736578365793658036581365823658336584365853658636587365883658936590365913659236593365943659536596365973659836599366003660136602366033660436605366063660736608366093661036611366123661336614366153661636617366183661936620366213662236623366243662536626366273662836629366303663136632366333663436635366363663736638366393664036641366423664336644366453664636647366483664936650366513665236653366543665536656366573665836659366603666136662366633666436665366663666736668366693667036671366723667336674366753667636677366783667936680366813668236683366843668536686366873668836689366903669136692366933669436695366963669736698366993670036701367023670336704367053670636707367083670936710367113671236713367143671536716367173671836719367203672136722367233672436725367263672736728367293673036731367323673336734367353673636737367383673936740367413674236743367443674536746367473674836749367503675136752367533675436755367563675736758367593676036761367623676336764367653676636767367683676936770367713677236773367743677536776367773677836779367803678136782367833678436785367863678736788367893679036791367923679336794367953679636797367983679936800368013680236803368043680536806368073680836809368103681136812368133681436815368163681736818368193682036821368223682336824368253682636827368283682936830368313683236833368343683536836368373683836839368403684136842368433684436845368463684736848368493685036851368523685336854368553685636857368583685936860368613686236863368643686536866368673686836869368703687136872368733687436875368763687736878368793688036881368823688336884368853688636887368883688936890368913689236893368943689536896368973689836899369003690136902369033690436905369063690736908369093691036911369123691336914369153691636917369183691936920369213692236923369243692536926369273692836929369303693136932369333693436935369363693736938369393694036941369423694336944369453694636947369483694936950369513695236953369543695536956369573695836959369603696136962369633696436965369663696736968369693697036971369723697336974369753697636977369783697936980369813698236983369843698536986369873698836989369903699136992369933699436995369963699736998369993700037001370023700337004370053700637007370083700937010370113701237013370143701537016370173701837019370203702137022370233702437025370263702737028370293703037031370323703337034370353703637037370383703937040370413704237043370443704537046370473704837049370503705137052370533705437055370563705737058370593706037061370623706337064370653706637067370683706937070370713707237073370743707537076370773707837079370803708137082370833708437085370863708737088370893709037091370923709337094370953709637097370983709937100371013710237103371043710537106371073710837109371103711137112371133711437115371163711737118371193712037121371223712337124371253712637127371283712937130371313713237133371343713537136371373713837139371403714137142371433714437145371463714737148371493715037151371523715337154371553715637157371583715937160371613716237163371643716537166371673716837169371703717137172371733717437175371763717737178371793718037181371823718337184371853718637187371883718937190371913719237193371943719537196371973719837199372003720137202372033720437205372063720737208372093721037211372123721337214372153721637217372183721937220372213722237223372243722537226372273722837229372303723137232372333723437235372363723737238372393724037241372423724337244372453724637247372483724937250372513725237253372543725537256372573725837259372603726137262372633726437265372663726737268372693727037271372723727337274372753727637277372783727937280372813728237283372843728537286372873728837289372903729137292372933729437295372963729737298372993730037301373023730337304373053730637307373083730937310373113731237313373143731537316373173731837319373203732137322373233732437325373263732737328373293733037331373323733337334373353733637337373383733937340373413734237343373443734537346373473734837349373503735137352373533735437355373563735737358373593736037361373623736337364373653736637367373683736937370373713737237373373743737537376373773737837379373803738137382373833738437385373863738737388373893739037391373923739337394373953739637397373983739937400374013740237403374043740537406374073740837409374103741137412374133741437415374163741737418374193742037421374223742337424374253742637427374283742937430374313743237433374343743537436374373743837439374403744137442374433744437445374463744737448374493745037451374523745337454374553745637457374583745937460374613746237463374643746537466374673746837469374703747137472374733747437475374763747737478374793748037481374823748337484374853748637487374883748937490374913749237493374943749537496374973749837499375003750137502375033750437505375063750737508375093751037511375123751337514375153751637517375183751937520375213752237523375243752537526375273752837529375303753137532375333753437535375363753737538375393754037541375423754337544375453754637547375483754937550375513755237553375543755537556375573755837559375603756137562375633756437565375663756737568375693757037571375723757337574375753757637577375783757937580375813758237583375843758537586375873758837589375903759137592375933759437595375963759737598375993760037601376023760337604376053760637607376083760937610376113761237613376143761537616376173761837619376203762137622376233762437625376263762737628376293763037631376323763337634376353763637637376383763937640376413764237643376443764537646376473764837649376503765137652376533765437655376563765737658376593766037661376623766337664376653766637667376683766937670376713767237673376743767537676376773767837679376803768137682376833768437685376863768737688376893769037691376923769337694376953769637697376983769937700377013770237703377043770537706377073770837709377103771137712377133771437715377163771737718377193772037721377223772337724377253772637727377283772937730377313773237733377343773537736377373773837739377403774137742377433774437745377463774737748377493775037751377523775337754377553775637757377583775937760377613776237763377643776537766377673776837769377703777137772377733777437775377763777737778377793778037781377823778337784377853778637787377883778937790377913779237793377943779537796377973779837799378003780137802378033780437805378063780737808378093781037811378123781337814378153781637817378183781937820378213782237823378243782537826378273782837829378303783137832378333783437835378363783737838378393784037841378423784337844378453784637847378483784937850378513785237853378543785537856378573785837859378603786137862378633786437865378663786737868378693787037871378723787337874378753787637877378783787937880378813788237883378843788537886378873788837889378903789137892378933789437895378963789737898378993790037901379023790337904379053790637907379083790937910379113791237913379143791537916379173791837919379203792137922379233792437925379263792737928379293793037931379323793337934379353793637937379383793937940379413794237943379443794537946379473794837949379503795137952379533795437955379563795737958379593796037961379623796337964379653796637967379683796937970379713797237973379743797537976379773797837979379803798137982379833798437985379863798737988379893799037991379923799337994379953799637997379983799938000380013800238003380043800538006380073800838009380103801138012380133801438015380163801738018380193802038021380223802338024380253802638027380283802938030380313803238033380343803538036380373803838039380403804138042380433804438045380463804738048380493805038051380523805338054380553805638057380583805938060380613806238063380643806538066380673806838069380703807138072380733807438075380763807738078380793808038081380823808338084380853808638087380883808938090380913809238093380943809538096380973809838099381003810138102381033810438105381063810738108381093811038111381123811338114381153811638117381183811938120381213812238123381243812538126381273812838129381303813138132381333813438135381363813738138381393814038141381423814338144381453814638147381483814938150381513815238153381543815538156381573815838159381603816138162381633816438165381663816738168381693817038171381723817338174381753817638177381783817938180381813818238183381843818538186381873818838189381903819138192381933819438195381963819738198381993820038201382023820338204382053820638207382083820938210382113821238213382143821538216382173821838219382203822138222382233822438225382263822738228382293823038231382323823338234382353823638237382383823938240382413824238243382443824538246382473824838249382503825138252382533825438255382563825738258382593826038261382623826338264382653826638267382683826938270382713827238273382743827538276382773827838279382803828138282382833828438285382863828738288382893829038291382923829338294382953829638297382983829938300383013830238303383043830538306383073830838309383103831138312383133831438315383163831738318383193832038321383223832338324383253832638327383283832938330383313833238333383343833538336383373833838339383403834138342383433834438345383463834738348383493835038351383523835338354383553835638357383583835938360383613836238363383643836538366383673836838369383703837138372383733837438375383763837738378383793838038381383823838338384383853838638387383883838938390383913839238393383943839538396383973839838399384003840138402384033840438405384063840738408384093841038411384123841338414384153841638417384183841938420384213842238423384243842538426384273842838429384303843138432384333843438435384363843738438384393844038441384423844338444384453844638447384483844938450384513845238453384543845538456384573845838459384603846138462384633846438465384663846738468384693847038471384723847338474384753847638477384783847938480384813848238483384843848538486384873848838489384903849138492384933849438495384963849738498384993850038501385023850338504385053850638507385083850938510385113851238513385143851538516385173851838519385203852138522385233852438525385263852738528385293853038531385323853338534385353853638537385383853938540385413854238543385443854538546385473854838549385503855138552385533855438555385563855738558385593856038561385623856338564385653856638567385683856938570385713857238573385743857538576385773857838579385803858138582385833858438585385863858738588385893859038591385923859338594385953859638597385983859938600386013860238603386043860538606386073860838609386103861138612386133861438615386163861738618386193862038621386223862338624386253862638627386283862938630386313863238633386343863538636386373863838639386403864138642386433864438645386463864738648386493865038651386523865338654386553865638657386583865938660386613866238663386643866538666386673866838669386703867138672386733867438675386763867738678386793868038681386823868338684386853868638687386883868938690386913869238693386943869538696386973869838699387003870138702387033870438705387063870738708387093871038711387123871338714387153871638717387183871938720387213872238723387243872538726387273872838729387303873138732387333873438735387363873738738387393874038741387423874338744387453874638747387483874938750387513875238753387543875538756387573875838759387603876138762387633876438765387663876738768387693877038771387723877338774387753877638777387783877938780387813878238783387843878538786387873878838789387903879138792387933879438795387963879738798387993880038801388023880338804388053880638807388083880938810388113881238813388143881538816388173881838819388203882138822388233882438825388263882738828388293883038831388323883338834388353883638837388383883938840388413884238843388443884538846388473884838849388503885138852388533885438855388563885738858388593886038861388623886338864388653886638867388683886938870388713887238873388743887538876388773887838879388803888138882388833888438885388863888738888388893889038891388923889338894388953889638897388983889938900389013890238903389043890538906389073890838909389103891138912389133891438915389163891738918389193892038921389223892338924389253892638927389283892938930389313893238933389343893538936389373893838939389403894138942389433894438945389463894738948389493895038951389523895338954389553895638957389583895938960389613896238963389643896538966389673896838969389703897138972389733897438975389763897738978389793898038981389823898338984389853898638987389883898938990389913899238993389943899538996389973899838999390003900139002390033900439005390063900739008390093901039011390123901339014390153901639017390183901939020390213902239023390243902539026390273902839029390303903139032390333903439035390363903739038390393904039041390423904339044390453904639047390483904939050390513905239053390543905539056390573905839059390603906139062390633906439065390663906739068390693907039071390723907339074390753907639077390783907939080390813908239083390843908539086390873908839089390903909139092390933909439095390963909739098390993910039101391023910339104391053910639107391083910939110391113911239113391143911539116391173911839119391203912139122391233912439125391263912739128391293913039131391323913339134391353913639137391383913939140391413914239143391443914539146391473914839149391503915139152391533915439155391563915739158391593916039161391623916339164391653916639167391683916939170391713917239173391743917539176391773917839179391803918139182391833918439185391863918739188391893919039191391923919339194391953919639197391983919939200392013920239203392043920539206392073920839209392103921139212392133921439215392163921739218392193922039221392223922339224392253922639227392283922939230392313923239233392343923539236392373923839239392403924139242392433924439245392463924739248392493925039251392523925339254392553925639257392583925939260392613926239263392643926539266392673926839269392703927139272392733927439275392763927739278392793928039281392823928339284392853928639287392883928939290392913929239293392943929539296392973929839299393003930139302393033930439305393063930739308393093931039311393123931339314393153931639317393183931939320393213932239323393243932539326393273932839329393303933139332393333933439335393363933739338393393934039341393423934339344393453934639347393483934939350393513935239353393543935539356393573935839359393603936139362393633936439365393663936739368393693937039371393723937339374393753937639377393783937939380393813938239383393843938539386393873938839389393903939139392393933939439395393963939739398393993940039401394023940339404394053940639407394083940939410394113941239413394143941539416394173941839419394203942139422394233942439425394263942739428394293943039431394323943339434394353943639437394383943939440394413944239443394443944539446394473944839449394503945139452394533945439455394563945739458394593946039461394623946339464394653946639467394683946939470394713947239473394743947539476394773947839479394803948139482394833948439485394863948739488394893949039491394923949339494394953949639497394983949939500395013950239503395043950539506395073950839509395103951139512395133951439515395163951739518395193952039521395223952339524395253952639527395283952939530395313953239533395343953539536395373953839539395403954139542395433954439545395463954739548395493955039551395523955339554395553955639557395583955939560395613956239563395643956539566395673956839569395703957139572395733957439575395763957739578395793958039581395823958339584395853958639587395883958939590395913959239593395943959539596395973959839599396003960139602396033960439605396063960739608396093961039611396123961339614396153961639617396183961939620396213962239623396243962539626396273962839629396303963139632396333963439635396363963739638396393964039641396423964339644396453964639647396483964939650396513965239653396543965539656396573965839659396603966139662396633966439665396663966739668396693967039671396723967339674396753967639677396783967939680396813968239683396843968539686396873968839689396903969139692396933969439695396963969739698396993970039701397023970339704397053970639707397083970939710397113971239713397143971539716397173971839719397203972139722397233972439725397263972739728397293973039731397323973339734397353973639737397383973939740397413974239743397443974539746397473974839749397503975139752397533975439755397563975739758397593976039761397623976339764397653976639767397683976939770397713977239773397743977539776397773977839779397803978139782397833978439785397863978739788397893979039791397923979339794397953979639797397983979939800398013980239803398043980539806398073980839809398103981139812398133981439815398163981739818398193982039821398223982339824398253982639827398283982939830398313983239833398343983539836398373983839839398403984139842398433984439845398463984739848398493985039851398523985339854398553985639857398583985939860398613986239863398643986539866398673986839869398703987139872398733987439875398763987739878398793988039881398823988339884398853988639887398883988939890398913989239893398943989539896398973989839899399003990139902399033990439905399063990739908399093991039911399123991339914399153991639917399183991939920399213992239923399243992539926399273992839929399303993139932399333993439935399363993739938399393994039941399423994339944399453994639947399483994939950399513995239953399543995539956399573995839959399603996139962399633996439965399663996739968399693997039971399723997339974399753997639977399783997939980399813998239983399843998539986399873998839989399903999139992399933999439995399963999739998399994000040001400024000340004400054000640007400084000940010400114001240013400144001540016400174001840019400204002140022400234002440025400264002740028400294003040031400324003340034400354003640037400384003940040400414004240043400444004540046400474004840049400504005140052400534005440055400564005740058400594006040061400624006340064400654006640067400684006940070400714007240073400744007540076400774007840079400804008140082400834008440085400864008740088400894009040091400924009340094400954009640097400984009940100401014010240103401044010540106401074010840109401104011140112401134011440115401164011740118401194012040121401224012340124401254012640127401284012940130401314013240133401344013540136401374013840139401404014140142401434014440145401464014740148401494015040151401524015340154401554015640157401584015940160401614016240163401644016540166401674016840169401704017140172401734017440175401764017740178401794018040181401824018340184401854018640187401884018940190401914019240193401944019540196401974019840199402004020140202402034020440205402064020740208402094021040211402124021340214402154021640217402184021940220402214022240223402244022540226402274022840229402304023140232402334023440235402364023740238402394024040241402424024340244402454024640247402484024940250402514025240253402544025540256402574025840259402604026140262402634026440265402664026740268402694027040271402724027340274402754027640277402784027940280402814028240283402844028540286402874028840289402904029140292402934029440295402964029740298402994030040301403024030340304403054030640307403084030940310403114031240313403144031540316403174031840319403204032140322403234032440325403264032740328403294033040331403324033340334403354033640337403384033940340403414034240343403444034540346403474034840349403504035140352403534035440355403564035740358403594036040361403624036340364403654036640367403684036940370403714037240373403744037540376403774037840379403804038140382403834038440385403864038740388403894039040391403924039340394403954039640397403984039940400404014040240403404044040540406404074040840409404104041140412404134041440415404164041740418404194042040421404224042340424404254042640427404284042940430404314043240433404344043540436404374043840439404404044140442404434044440445404464044740448404494045040451404524045340454404554045640457404584045940460404614046240463404644046540466404674046840469404704047140472404734047440475404764047740478404794048040481404824048340484404854048640487404884048940490404914049240493404944049540496404974049840499405004050140502405034050440505405064050740508405094051040511405124051340514405154051640517405184051940520405214052240523405244052540526405274052840529405304053140532405334053440535405364053740538405394054040541405424054340544405454054640547405484054940550405514055240553405544055540556405574055840559405604056140562405634056440565405664056740568405694057040571405724057340574405754057640577405784057940580405814058240583405844058540586405874058840589405904059140592405934059440595405964059740598405994060040601406024060340604406054060640607406084060940610406114061240613406144061540616406174061840619406204062140622406234062440625406264062740628406294063040631406324063340634406354063640637406384063940640406414064240643406444064540646406474064840649406504065140652406534065440655406564065740658406594066040661406624066340664406654066640667406684066940670406714067240673406744067540676406774067840679406804068140682406834068440685406864068740688406894069040691406924069340694406954069640697406984069940700407014070240703407044070540706407074070840709407104071140712407134071440715407164071740718407194072040721407224072340724407254072640727407284072940730407314073240733407344073540736407374073840739407404074140742407434074440745407464074740748407494075040751407524075340754407554075640757407584075940760407614076240763407644076540766407674076840769407704077140772407734077440775407764077740778407794078040781407824078340784407854078640787407884078940790407914079240793407944079540796407974079840799408004080140802408034080440805408064080740808408094081040811408124081340814408154081640817408184081940820408214082240823408244082540826408274082840829408304083140832408334083440835408364083740838408394084040841408424084340844408454084640847408484084940850408514085240853408544085540856408574085840859408604086140862408634086440865408664086740868408694087040871408724087340874408754087640877408784087940880408814088240883408844088540886408874088840889408904089140892408934089440895408964089740898408994090040901409024090340904409054090640907409084090940910409114091240913409144091540916409174091840919409204092140922409234092440925409264092740928409294093040931409324093340934409354093640937409384093940940409414094240943409444094540946409474094840949409504095140952409534095440955409564095740958409594096040961409624096340964409654096640967409684096940970409714097240973409744097540976409774097840979409804098140982409834098440985409864098740988409894099040991409924099340994409954099640997409984099941000410014100241003410044100541006410074100841009410104101141012410134101441015410164101741018410194102041021410224102341024410254102641027410284102941030410314103241033410344103541036410374103841039410404104141042410434104441045410464104741048410494105041051410524105341054410554105641057410584105941060410614106241063410644106541066410674106841069410704107141072410734107441075410764107741078410794108041081410824108341084410854108641087410884108941090410914109241093410944109541096410974109841099411004110141102411034110441105411064110741108411094111041111411124111341114411154111641117411184111941120411214112241123411244112541126411274112841129411304113141132411334113441135411364113741138411394114041141411424114341144411454114641147411484114941150411514115241153411544115541156411574115841159411604116141162411634116441165411664116741168411694117041171411724117341174411754117641177411784117941180411814118241183411844118541186411874118841189411904119141192411934119441195411964119741198411994120041201412024120341204412054120641207412084120941210412114121241213412144121541216412174121841219412204122141222412234122441225412264122741228412294123041231412324123341234412354123641237412384123941240412414124241243412444124541246412474124841249412504125141252412534125441255412564125741258412594126041261412624126341264412654126641267412684126941270412714127241273412744127541276412774127841279412804128141282412834128441285412864128741288412894129041291412924129341294412954129641297412984129941300413014130241303413044130541306413074130841309413104131141312413134131441315413164131741318413194132041321413224132341324413254132641327413284132941330413314133241333413344133541336413374133841339413404134141342413434134441345413464134741348413494135041351413524135341354413554135641357413584135941360413614136241363413644136541366413674136841369413704137141372413734137441375413764137741378413794138041381413824138341384413854138641387413884138941390413914139241393413944139541396413974139841399414004140141402414034140441405414064140741408414094141041411414124141341414414154141641417414184141941420414214142241423414244142541426414274142841429414304143141432414334143441435414364143741438414394144041441414424144341444414454144641447414484144941450414514145241453414544145541456414574145841459414604146141462414634146441465414664146741468414694147041471414724147341474414754147641477414784147941480414814148241483414844148541486414874148841489414904149141492414934149441495414964149741498414994150041501415024150341504415054150641507415084150941510415114151241513415144151541516415174151841519415204152141522415234152441525415264152741528415294153041531415324153341534415354153641537415384153941540415414154241543415444154541546415474154841549415504155141552415534155441555415564155741558415594156041561415624156341564415654156641567415684156941570415714157241573415744157541576415774157841579415804158141582415834158441585415864158741588415894159041591415924159341594415954159641597415984159941600416014160241603416044160541606416074160841609416104161141612416134161441615416164161741618416194162041621416224162341624416254162641627416284162941630416314163241633416344163541636416374163841639416404164141642416434164441645416464164741648416494165041651416524165341654416554165641657416584165941660416614166241663416644166541666416674166841669416704167141672416734167441675416764167741678416794168041681416824168341684416854168641687416884168941690416914169241693416944169541696416974169841699417004170141702417034170441705417064170741708417094171041711417124171341714417154171641717417184171941720417214172241723417244172541726417274172841729417304173141732417334173441735417364173741738417394174041741417424174341744417454174641747417484174941750417514175241753417544175541756417574175841759417604176141762417634176441765417664176741768417694177041771417724177341774417754177641777417784177941780417814178241783417844178541786417874178841789417904179141792417934179441795417964179741798417994180041801418024180341804418054180641807418084180941810418114181241813418144181541816418174181841819418204182141822418234182441825418264182741828418294183041831418324183341834418354183641837418384183941840418414184241843418444184541846418474184841849418504185141852418534185441855418564185741858418594186041861418624186341864418654186641867418684186941870418714187241873418744187541876418774187841879418804188141882418834188441885418864188741888418894189041891418924189341894418954189641897418984189941900419014190241903419044190541906419074190841909419104191141912419134191441915419164191741918419194192041921419224192341924419254192641927419284192941930419314193241933419344193541936419374193841939419404194141942419434194441945419464194741948419494195041951419524195341954419554195641957419584195941960419614196241963419644196541966419674196841969419704197141972419734197441975419764197741978419794198041981419824198341984419854198641987419884198941990419914199241993419944199541996419974199841999420004200142002420034200442005420064200742008420094201042011420124201342014420154201642017420184201942020420214202242023420244202542026420274202842029420304203142032420334203442035420364203742038420394204042041420424204342044420454204642047420484204942050420514205242053420544205542056420574205842059420604206142062420634206442065420664206742068420694207042071420724207342074420754207642077420784207942080420814208242083420844208542086420874208842089420904209142092420934209442095420964209742098420994210042101421024210342104421054210642107421084210942110421114211242113421144211542116421174211842119421204212142122421234212442125421264212742128421294213042131421324213342134421354213642137421384213942140421414214242143421444214542146421474214842149421504215142152421534215442155421564215742158421594216042161421624216342164421654216642167421684216942170421714217242173421744217542176421774217842179421804218142182421834218442185421864218742188421894219042191421924219342194421954219642197421984219942200422014220242203422044220542206422074220842209422104221142212422134221442215422164221742218422194222042221422224222342224422254222642227422284222942230422314223242233422344223542236422374223842239422404224142242422434224442245422464224742248422494225042251422524225342254422554225642257422584225942260422614226242263422644226542266422674226842269422704227142272422734227442275422764227742278422794228042281422824228342284422854228642287422884228942290422914229242293422944229542296422974229842299423004230142302423034230442305423064230742308423094231042311423124231342314423154231642317423184231942320423214232242323423244232542326423274232842329423304233142332423334233442335423364233742338423394234042341423424234342344423454234642347423484234942350423514235242353423544235542356423574235842359423604236142362423634236442365423664236742368423694237042371423724237342374423754237642377423784237942380423814238242383423844238542386423874238842389423904239142392423934239442395423964239742398423994240042401424024240342404424054240642407424084240942410424114241242413424144241542416424174241842419424204242142422424234242442425424264242742428424294243042431424324243342434424354243642437424384243942440424414244242443424444244542446424474244842449424504245142452424534245442455424564245742458424594246042461424624246342464424654246642467424684246942470424714247242473424744247542476424774247842479424804248142482424834248442485424864248742488424894249042491424924249342494424954249642497424984249942500425014250242503425044250542506425074250842509425104251142512425134251442515425164251742518425194252042521425224252342524425254252642527425284252942530425314253242533425344253542536425374253842539425404254142542425434254442545425464254742548425494255042551425524255342554425554255642557425584255942560425614256242563425644256542566425674256842569425704257142572425734257442575425764257742578425794258042581425824258342584425854258642587425884258942590425914259242593425944259542596425974259842599426004260142602426034260442605426064260742608426094261042611426124261342614426154261642617426184261942620426214262242623426244262542626426274262842629426304263142632426334263442635426364263742638426394264042641426424264342644426454264642647426484264942650426514265242653426544265542656426574265842659426604266142662426634266442665426664266742668426694267042671426724267342674426754267642677426784267942680426814268242683426844268542686426874268842689426904269142692426934269442695426964269742698426994270042701427024270342704427054270642707427084270942710427114271242713427144271542716427174271842719427204272142722427234272442725427264272742728427294273042731427324273342734427354273642737427384273942740427414274242743427444274542746427474274842749427504275142752427534275442755427564275742758427594276042761427624276342764427654276642767427684276942770427714277242773427744277542776427774277842779427804278142782427834278442785427864278742788427894279042791427924279342794427954279642797427984279942800428014280242803428044280542806428074280842809428104281142812428134281442815428164281742818428194282042821428224282342824428254282642827428284282942830428314283242833428344283542836428374283842839428404284142842428434284442845428464284742848428494285042851428524285342854428554285642857428584285942860428614286242863428644286542866428674286842869428704287142872428734287442875428764287742878428794288042881428824288342884428854288642887428884288942890428914289242893428944289542896428974289842899429004290142902429034290442905429064290742908429094291042911429124291342914429154291642917429184291942920429214292242923429244292542926429274292842929429304293142932429334293442935429364293742938429394294042941429424294342944429454294642947429484294942950429514295242953429544295542956429574295842959429604296142962429634296442965429664296742968429694297042971429724297342974429754297642977429784297942980429814298242983429844298542986429874298842989429904299142992429934299442995429964299742998429994300043001430024300343004430054300643007430084300943010430114301243013430144301543016430174301843019430204302143022430234302443025430264302743028430294303043031430324303343034430354303643037430384303943040430414304243043430444304543046430474304843049430504305143052430534305443055430564305743058430594306043061430624306343064430654306643067430684306943070430714307243073430744307543076430774307843079430804308143082430834308443085430864308743088430894309043091430924309343094430954309643097430984309943100431014310243103431044310543106431074310843109431104311143112431134311443115431164311743118431194312043121431224312343124431254312643127431284312943130431314313243133431344313543136431374313843139431404314143142431434314443145431464314743148431494315043151431524315343154431554315643157431584315943160431614316243163431644316543166431674316843169431704317143172431734317443175431764317743178431794318043181431824318343184431854318643187431884318943190431914319243193431944319543196431974319843199432004320143202432034320443205432064320743208432094321043211432124321343214432154321643217432184321943220432214322243223432244322543226432274322843229432304323143232432334323443235432364323743238432394324043241432424324343244432454324643247432484324943250432514325243253432544325543256432574325843259432604326143262432634326443265432664326743268432694327043271432724327343274432754327643277432784327943280432814328243283432844328543286432874328843289432904329143292432934329443295432964329743298432994330043301433024330343304433054330643307433084330943310433114331243313433144331543316433174331843319433204332143322433234332443325433264332743328433294333043331433324333343334433354333643337433384333943340433414334243343433444334543346433474334843349433504335143352433534335443355433564335743358433594336043361433624336343364433654336643367433684336943370433714337243373433744337543376433774337843379433804338143382433834338443385433864338743388433894339043391433924339343394433954339643397433984339943400434014340243403434044340543406434074340843409434104341143412434134341443415434164341743418434194342043421434224342343424434254342643427434284342943430434314343243433434344343543436434374343843439434404344143442434434344443445434464344743448434494345043451434524345343454434554345643457434584345943460434614346243463434644346543466434674346843469434704347143472434734347443475434764347743478434794348043481434824348343484434854348643487434884348943490434914349243493434944349543496434974349843499435004350143502435034350443505435064350743508435094351043511435124351343514435154351643517435184351943520435214352243523435244352543526435274352843529435304353143532435334353443535435364353743538435394354043541435424354343544435454354643547435484354943550435514355243553435544355543556435574355843559435604356143562435634356443565435664356743568435694357043571435724357343574435754357643577435784357943580435814358243583435844358543586435874358843589435904359143592435934359443595435964359743598435994360043601436024360343604436054360643607436084360943610436114361243613436144361543616436174361843619436204362143622436234362443625436264362743628436294363043631436324363343634436354363643637436384363943640436414364243643436444364543646436474364843649436504365143652436534365443655436564365743658436594366043661436624366343664436654366643667436684366943670436714367243673436744367543676436774367843679436804368143682436834368443685436864368743688436894369043691436924369343694436954369643697436984369943700437014370243703437044370543706437074370843709437104371143712437134371443715437164371743718437194372043721437224372343724437254372643727437284372943730437314373243733437344373543736437374373843739437404374143742437434374443745437464374743748437494375043751437524375343754437554375643757437584375943760437614376243763437644376543766437674376843769437704377143772437734377443775437764377743778437794378043781437824378343784437854378643787437884378943790437914379243793437944379543796437974379843799438004380143802438034380443805438064380743808438094381043811438124381343814438154381643817438184381943820438214382243823438244382543826438274382843829438304383143832438334383443835438364383743838438394384043841438424384343844438454384643847438484384943850438514385243853438544385543856438574385843859438604386143862438634386443865438664386743868438694387043871438724387343874438754387643877438784387943880438814388243883438844388543886438874388843889438904389143892438934389443895438964389743898438994390043901439024390343904439054390643907439084390943910439114391243913439144391543916439174391843919439204392143922439234392443925439264392743928439294393043931439324393343934439354393643937439384393943940439414394243943439444394543946439474394843949439504395143952439534395443955439564395743958439594396043961439624396343964439654396643967439684396943970439714397243973439744397543976439774397843979439804398143982439834398443985439864398743988439894399043991439924399343994439954399643997439984399944000440014400244003440044400544006440074400844009440104401144012440134401444015440164401744018440194402044021440224402344024440254402644027440284402944030440314403244033440344403544036440374403844039440404404144042440434404444045440464404744048440494405044051440524405344054440554405644057440584405944060440614406244063440644406544066440674406844069440704407144072440734407444075440764407744078440794408044081440824408344084440854408644087440884408944090440914409244093440944409544096440974409844099441004410144102441034410444105441064410744108441094411044111441124411344114441154411644117441184411944120441214412244123441244412544126441274412844129441304413144132441334413444135441364413744138441394414044141441424414344144441454414644147441484414944150441514415244153441544415544156441574415844159441604416144162441634416444165441664416744168441694417044171441724417344174441754417644177441784417944180441814418244183441844418544186441874418844189441904419144192441934419444195441964419744198441994420044201442024420344204442054420644207442084420944210442114421244213442144421544216442174421844219442204422144222442234422444225442264422744228442294423044231442324423344234442354423644237442384423944240442414424244243442444424544246442474424844249442504425144252442534425444255442564425744258442594426044261442624426344264442654426644267442684426944270442714427244273442744427544276442774427844279442804428144282442834428444285442864428744288442894429044291442924429344294442954429644297442984429944300443014430244303443044430544306443074430844309443104431144312443134431444315443164431744318443194432044321443224432344324443254432644327443284432944330443314433244333443344433544336443374433844339443404434144342443434434444345443464434744348443494435044351443524435344354443554435644357443584435944360443614436244363443644436544366443674436844369443704437144372443734437444375443764437744378443794438044381443824438344384443854438644387443884438944390443914439244393443944439544396443974439844399444004440144402444034440444405444064440744408444094441044411444124441344414444154441644417444184441944420444214442244423444244442544426444274442844429444304443144432444334443444435444364443744438444394444044441444424444344444444454444644447444484444944450444514445244453444544445544456444574445844459444604446144462444634446444465444664446744468444694447044471444724447344474444754447644477444784447944480444814448244483444844448544486444874448844489444904449144492444934449444495444964449744498444994450044501445024450344504445054450644507445084450944510445114451244513445144451544516445174451844519445204452144522445234452444525445264452744528445294453044531445324453344534445354453644537445384453944540445414454244543445444454544546445474454844549445504455144552445534455444555445564455744558445594456044561445624456344564445654456644567445684456944570445714457244573445744457544576445774457844579445804458144582445834458444585445864458744588445894459044591445924459344594445954459644597445984459944600446014460244603446044460544606446074460844609446104461144612446134461444615446164461744618446194462044621446224462344624446254462644627446284462944630446314463244633446344463544636446374463844639446404464144642446434464444645446464464744648446494465044651446524465344654446554465644657446584465944660446614466244663446644466544666446674466844669446704467144672446734467444675446764467744678446794468044681446824468344684446854468644687446884468944690446914469244693446944469544696446974469844699447004470144702447034470444705447064470744708447094471044711447124471344714447154471644717447184471944720447214472244723447244472544726447274472844729447304473144732447334473444735447364473744738447394474044741447424474344744447454474644747447484474944750447514475244753447544475544756447574475844759447604476144762447634476444765447664476744768447694477044771447724477344774447754477644777447784477944780447814478244783447844478544786447874478844789447904479144792447934479444795447964479744798447994480044801448024480344804448054480644807448084480944810448114481244813448144481544816448174481844819448204482144822448234482444825448264482744828448294483044831448324483344834448354483644837448384483944840448414484244843448444484544846448474484844849448504485144852448534485444855448564485744858448594486044861448624486344864448654486644867448684486944870448714487244873448744487544876448774487844879448804488144882448834488444885448864488744888448894489044891448924489344894448954489644897448984489944900449014490244903449044490544906449074490844909449104491144912449134491444915449164491744918449194492044921449224492344924449254492644927449284492944930449314493244933449344493544936449374493844939449404494144942449434494444945449464494744948449494495044951449524495344954449554495644957449584495944960449614496244963449644496544966449674496844969449704497144972449734497444975449764497744978449794498044981449824498344984449854498644987449884498944990449914499244993449944499544996449974499844999450004500145002450034500445005450064500745008450094501045011450124501345014450154501645017450184501945020450214502245023450244502545026450274502845029450304503145032450334503445035450364503745038450394504045041450424504345044450454504645047450484504945050450514505245053450544505545056450574505845059450604506145062450634506445065450664506745068450694507045071450724507345074450754507645077450784507945080450814508245083450844508545086450874508845089450904509145092450934509445095450964509745098450994510045101451024510345104451054510645107451084510945110451114511245113451144511545116451174511845119451204512145122451234512445125451264512745128451294513045131451324513345134451354513645137451384513945140451414514245143451444514545146451474514845149451504515145152451534515445155451564515745158451594516045161451624516345164451654516645167451684516945170451714517245173451744517545176451774517845179451804518145182451834518445185451864518745188451894519045191451924519345194451954519645197451984519945200452014520245203452044520545206452074520845209452104521145212452134521445215452164521745218452194522045221452224522345224452254522645227452284522945230452314523245233452344523545236452374523845239452404524145242452434524445245452464524745248452494525045251452524525345254452554525645257452584525945260452614526245263452644526545266452674526845269452704527145272452734527445275452764527745278452794528045281452824528345284452854528645287452884528945290452914529245293452944529545296452974529845299453004530145302453034530445305453064530745308453094531045311453124531345314453154531645317453184531945320453214532245323453244532545326453274532845329453304533145332453334533445335453364533745338453394534045341453424534345344453454534645347453484534945350453514535245353453544535545356453574535845359453604536145362453634536445365453664536745368453694537045371453724537345374453754537645377453784537945380453814538245383453844538545386453874538845389453904539145392453934539445395453964539745398453994540045401454024540345404454054540645407454084540945410454114541245413454144541545416454174541845419454204542145422454234542445425454264542745428454294543045431454324543345434454354543645437454384543945440454414544245443454444544545446454474544845449454504545145452454534545445455454564545745458454594546045461454624546345464454654546645467454684546945470454714547245473454744547545476454774547845479454804548145482454834548445485454864548745488454894549045491454924549345494454954549645497454984549945500455014550245503455044550545506455074550845509455104551145512455134551445515455164551745518455194552045521455224552345524455254552645527455284552945530455314553245533455344553545536455374553845539455404554145542455434554445545455464554745548455494555045551455524555345554455554555645557455584555945560455614556245563455644556545566455674556845569455704557145572455734557445575455764557745578455794558045581455824558345584455854558645587455884558945590455914559245593455944559545596455974559845599456004560145602456034560445605456064560745608456094561045611456124561345614456154561645617456184561945620456214562245623456244562545626456274562845629456304563145632456334563445635456364563745638456394564045641456424564345644456454564645647456484564945650456514565245653456544565545656456574565845659456604566145662456634566445665456664566745668456694567045671456724567345674456754567645677456784567945680456814568245683456844568545686456874568845689456904569145692456934569445695456964569745698456994570045701457024570345704457054570645707457084570945710457114571245713457144571545716457174571845719457204572145722457234572445725457264572745728457294573045731457324573345734457354573645737457384573945740457414574245743457444574545746457474574845749457504575145752457534575445755457564575745758457594576045761457624576345764457654576645767457684576945770457714577245773457744577545776457774577845779457804578145782457834578445785457864578745788457894579045791457924579345794457954579645797457984579945800458014580245803458044580545806458074580845809458104581145812458134581445815458164581745818458194582045821458224582345824458254582645827458284582945830458314583245833458344583545836458374583845839458404584145842458434584445845458464584745848458494585045851458524585345854458554585645857458584585945860458614586245863458644586545866458674586845869458704587145872458734587445875458764587745878458794588045881458824588345884458854588645887458884588945890458914589245893458944589545896458974589845899459004590145902459034590445905459064590745908459094591045911459124591345914459154591645917459184591945920459214592245923459244592545926459274592845929459304593145932459334593445935459364593745938459394594045941459424594345944459454594645947459484594945950459514595245953459544595545956459574595845959459604596145962459634596445965459664596745968459694597045971459724597345974459754597645977459784597945980459814598245983459844598545986459874598845989459904599145992459934599445995459964599745998459994600046001460024600346004460054600646007460084600946010460114601246013460144601546016460174601846019460204602146022460234602446025460264602746028460294603046031460324603346034460354603646037460384603946040460414604246043460444604546046460474604846049460504605146052460534605446055460564605746058460594606046061460624606346064460654606646067460684606946070460714607246073460744607546076460774607846079460804608146082460834608446085460864608746088460894609046091460924609346094460954609646097460984609946100461014610246103461044610546106461074610846109461104611146112461134611446115461164611746118461194612046121461224612346124461254612646127461284612946130461314613246133461344613546136461374613846139461404614146142461434614446145461464614746148461494615046151461524615346154461554615646157461584615946160461614616246163461644616546166461674616846169461704617146172461734617446175461764617746178461794618046181461824618346184461854618646187461884618946190461914619246193461944619546196461974619846199462004620146202462034620446205462064620746208462094621046211462124621346214462154621646217462184621946220462214622246223462244622546226462274622846229462304623146232462334623446235462364623746238462394624046241462424624346244462454624646247462484624946250462514625246253462544625546256462574625846259462604626146262462634626446265462664626746268462694627046271462724627346274462754627646277462784627946280462814628246283462844628546286462874628846289462904629146292462934629446295462964629746298462994630046301463024630346304463054630646307463084630946310463114631246313463144631546316463174631846319463204632146322463234632446325463264632746328463294633046331463324633346334463354633646337463384633946340463414634246343463444634546346463474634846349463504635146352463534635446355463564635746358463594636046361463624636346364463654636646367463684636946370463714637246373463744637546376463774637846379463804638146382463834638446385463864638746388463894639046391463924639346394463954639646397463984639946400464014640246403464044640546406464074640846409464104641146412464134641446415464164641746418464194642046421464224642346424464254642646427464284642946430464314643246433464344643546436464374643846439464404644146442464434644446445464464644746448464494645046451464524645346454464554645646457464584645946460464614646246463464644646546466464674646846469464704647146472464734647446475464764647746478464794648046481464824648346484464854648646487464884648946490464914649246493464944649546496464974649846499465004650146502465034650446505465064650746508465094651046511465124651346514465154651646517465184651946520465214652246523465244652546526465274652846529465304653146532465334653446535465364653746538465394654046541465424654346544465454654646547465484654946550465514655246553465544655546556465574655846559465604656146562465634656446565465664656746568465694657046571465724657346574465754657646577465784657946580465814658246583465844658546586465874658846589465904659146592465934659446595465964659746598465994660046601466024660346604466054660646607466084660946610466114661246613466144661546616466174661846619466204662146622466234662446625466264662746628466294663046631466324663346634466354663646637466384663946640466414664246643466444664546646466474664846649466504665146652466534665446655466564665746658466594666046661466624666346664466654666646667466684666946670466714667246673466744667546676466774667846679466804668146682466834668446685466864668746688466894669046691466924669346694466954669646697466984669946700467014670246703467044670546706467074670846709467104671146712467134671446715467164671746718467194672046721467224672346724467254672646727467284672946730467314673246733467344673546736467374673846739467404674146742467434674446745467464674746748467494675046751467524675346754467554675646757467584675946760467614676246763467644676546766467674676846769467704677146772467734677446775467764677746778467794678046781467824678346784467854678646787467884678946790467914679246793467944679546796467974679846799468004680146802468034680446805468064680746808468094681046811468124681346814468154681646817468184681946820468214682246823468244682546826468274682846829468304683146832468334683446835468364683746838468394684046841468424684346844468454684646847468484684946850468514685246853468544685546856468574685846859468604686146862468634686446865468664686746868468694687046871468724687346874468754687646877468784687946880468814688246883468844688546886468874688846889468904689146892468934689446895468964689746898468994690046901469024690346904469054690646907469084690946910469114691246913469144691546916469174691846919469204692146922
  1. // 4.4.1 (2016-07-26)
  2. /**
  3. * Compiled inline version. (Library mode)
  4. */
  5. /*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
  6. /*globals $code */
  7. (function(exports, undefined) {
  8. "use strict";
  9. var modules = {};
  10. function require(ids, callback) {
  11. var module, defs = [];
  12. for (var i = 0; i < ids.length; ++i) {
  13. module = modules[ids[i]] || resolve(ids[i]);
  14. if (!module) {
  15. throw 'module definition dependecy not found: ' + ids[i];
  16. }
  17. defs.push(module);
  18. }
  19. callback.apply(null, defs);
  20. }
  21. function define(id, dependencies, definition) {
  22. if (typeof id !== 'string') {
  23. throw 'invalid module definition, module id must be defined and be a string';
  24. }
  25. if (dependencies === undefined) {
  26. throw 'invalid module definition, dependencies must be specified';
  27. }
  28. if (definition === undefined) {
  29. throw 'invalid module definition, definition function must be specified';
  30. }
  31. require(dependencies, function() {
  32. modules[id] = definition.apply(null, arguments);
  33. });
  34. }
  35. function defined(id) {
  36. return !!modules[id];
  37. }
  38. function resolve(id) {
  39. var target = exports;
  40. var fragments = id.split(/[.\/]/);
  41. for (var fi = 0; fi < fragments.length; ++fi) {
  42. if (!target[fragments[fi]]) {
  43. return;
  44. }
  45. target = target[fragments[fi]];
  46. }
  47. return target;
  48. }
  49. function expose(ids) {
  50. var i, target, id, fragments, privateModules;
  51. for (i = 0; i < ids.length; i++) {
  52. target = exports;
  53. id = ids[i];
  54. fragments = id.split(/[.\/]/);
  55. for (var fi = 0; fi < fragments.length - 1; ++fi) {
  56. if (target[fragments[fi]] === undefined) {
  57. target[fragments[fi]] = {};
  58. }
  59. target = target[fragments[fi]];
  60. }
  61. target[fragments[fragments.length - 1]] = modules[id];
  62. }
  63. // Expose private modules for unit tests
  64. if (exports.AMDLC_TESTS) {
  65. privateModules = exports.privateModules || {};
  66. for (id in modules) {
  67. privateModules[id] = modules[id];
  68. }
  69. for (i = 0; i < ids.length; i++) {
  70. delete privateModules[ids[i]];
  71. }
  72. exports.privateModules = privateModules;
  73. }
  74. }
  75. // Included from: js/tinymce/classes/geom/Rect.js
  76. /**
  77. * Rect.js
  78. *
  79. * Released under LGPL License.
  80. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  81. *
  82. * License: http://www.tinymce.com/license
  83. * Contributing: http://www.tinymce.com/contributing
  84. */
  85. /**
  86. * Contains various tools for rect/position calculation.
  87. *
  88. * @class tinymce.geom.Rect
  89. */
  90. define("tinymce/geom/Rect", [
  91. ], function() {
  92. "use strict";
  93. var min = Math.min, max = Math.max, round = Math.round;
  94. /**
  95. * Returns the rect positioned based on the relative position name
  96. * to the target rect.
  97. *
  98. * @method relativePosition
  99. * @param {Rect} rect Source rect to modify into a new rect.
  100. * @param {Rect} targetRect Rect to move relative to based on the rel option.
  101. * @param {String} rel Relative position. For example: tr-bl.
  102. */
  103. function relativePosition(rect, targetRect, rel) {
  104. var x, y, w, h, targetW, targetH;
  105. x = targetRect.x;
  106. y = targetRect.y;
  107. w = rect.w;
  108. h = rect.h;
  109. targetW = targetRect.w;
  110. targetH = targetRect.h;
  111. rel = (rel || '').split('');
  112. if (rel[0] === 'b') {
  113. y += targetH;
  114. }
  115. if (rel[1] === 'r') {
  116. x += targetW;
  117. }
  118. if (rel[0] === 'c') {
  119. y += round(targetH / 2);
  120. }
  121. if (rel[1] === 'c') {
  122. x += round(targetW / 2);
  123. }
  124. if (rel[3] === 'b') {
  125. y -= h;
  126. }
  127. if (rel[4] === 'r') {
  128. x -= w;
  129. }
  130. if (rel[3] === 'c') {
  131. y -= round(h / 2);
  132. }
  133. if (rel[4] === 'c') {
  134. x -= round(w / 2);
  135. }
  136. return create(x, y, w, h);
  137. }
  138. /**
  139. * Tests various positions to get the most suitable one.
  140. *
  141. * @method findBestRelativePosition
  142. * @param {Rect} rect Rect to use as source.
  143. * @param {Rect} targetRect Rect to move relative to.
  144. * @param {Rect} constrainRect Rect to constrain within.
  145. * @param {Array} rels Array of relative positions to test against.
  146. */
  147. function findBestRelativePosition(rect, targetRect, constrainRect, rels) {
  148. var pos, i;
  149. for (i = 0; i < rels.length; i++) {
  150. pos = relativePosition(rect, targetRect, rels[i]);
  151. if (pos.x >= constrainRect.x && pos.x + pos.w <= constrainRect.w + constrainRect.x &&
  152. pos.y >= constrainRect.y && pos.y + pos.h <= constrainRect.h + constrainRect.y) {
  153. return rels[i];
  154. }
  155. }
  156. return null;
  157. }
  158. /**
  159. * Inflates the rect in all directions.
  160. *
  161. * @method inflate
  162. * @param {Rect} rect Rect to expand.
  163. * @param {Number} w Relative width to expand by.
  164. * @param {Number} h Relative height to expand by.
  165. * @return {Rect} New expanded rect.
  166. */
  167. function inflate(rect, w, h) {
  168. return create(rect.x - w, rect.y - h, rect.w + w * 2, rect.h + h * 2);
  169. }
  170. /**
  171. * Returns the intersection of the specified rectangles.
  172. *
  173. * @method intersect
  174. * @param {Rect} rect The first rectangle to compare.
  175. * @param {Rect} cropRect The second rectangle to compare.
  176. * @return {Rect} The intersection of the two rectangles or null if they don't intersect.
  177. */
  178. function intersect(rect, cropRect) {
  179. var x1, y1, x2, y2;
  180. x1 = max(rect.x, cropRect.x);
  181. y1 = max(rect.y, cropRect.y);
  182. x2 = min(rect.x + rect.w, cropRect.x + cropRect.w);
  183. y2 = min(rect.y + rect.h, cropRect.y + cropRect.h);
  184. if (x2 - x1 < 0 || y2 - y1 < 0) {
  185. return null;
  186. }
  187. return create(x1, y1, x2 - x1, y2 - y1);
  188. }
  189. /**
  190. * Returns a rect clamped within the specified clamp rect. This forces the
  191. * rect to be inside the clamp rect.
  192. *
  193. * @method clamp
  194. * @param {Rect} rect Rectangle to force within clamp rect.
  195. * @param {Rect} clampRect Rectable to force within.
  196. * @param {Boolean} fixedSize True/false if size should be fixed.
  197. * @return {Rect} Clamped rect.
  198. */
  199. function clamp(rect, clampRect, fixedSize) {
  200. var underflowX1, underflowY1, overflowX2, overflowY2,
  201. x1, y1, x2, y2, cx2, cy2;
  202. x1 = rect.x;
  203. y1 = rect.y;
  204. x2 = rect.x + rect.w;
  205. y2 = rect.y + rect.h;
  206. cx2 = clampRect.x + clampRect.w;
  207. cy2 = clampRect.y + clampRect.h;
  208. underflowX1 = max(0, clampRect.x - x1);
  209. underflowY1 = max(0, clampRect.y - y1);
  210. overflowX2 = max(0, x2 - cx2);
  211. overflowY2 = max(0, y2 - cy2);
  212. x1 += underflowX1;
  213. y1 += underflowY1;
  214. if (fixedSize) {
  215. x2 += underflowX1;
  216. y2 += underflowY1;
  217. x1 -= overflowX2;
  218. y1 -= overflowY2;
  219. }
  220. x2 -= overflowX2;
  221. y2 -= overflowY2;
  222. return create(x1, y1, x2 - x1, y2 - y1);
  223. }
  224. /**
  225. * Creates a new rectangle object.
  226. *
  227. * @method create
  228. * @param {Number} x Rectangle x location.
  229. * @param {Number} y Rectangle y location.
  230. * @param {Number} w Rectangle width.
  231. * @param {Number} h Rectangle height.
  232. * @return {Rect} New rectangle object.
  233. */
  234. function create(x, y, w, h) {
  235. return {x: x, y: y, w: w, h: h};
  236. }
  237. /**
  238. * Creates a new rectangle object form a clientRects object.
  239. *
  240. * @method fromClientRect
  241. * @param {ClientRect} clientRect DOM ClientRect object.
  242. * @return {Rect} New rectangle object.
  243. */
  244. function fromClientRect(clientRect) {
  245. return create(clientRect.left, clientRect.top, clientRect.width, clientRect.height);
  246. }
  247. return {
  248. inflate: inflate,
  249. relativePosition: relativePosition,
  250. findBestRelativePosition: findBestRelativePosition,
  251. intersect: intersect,
  252. clamp: clamp,
  253. create: create,
  254. fromClientRect: fromClientRect
  255. };
  256. });
  257. // Included from: js/tinymce/classes/util/Promise.js
  258. /**
  259. * Promise.js
  260. *
  261. * Released under LGPL License.
  262. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  263. *
  264. * Promise polyfill under MIT license: https://github.com/taylorhakes/promise-polyfill
  265. *
  266. * License: http://www.tinymce.com/license
  267. * Contributing: http://www.tinymce.com/contributing
  268. */
  269. /* eslint-disable */
  270. /* jshint ignore:start */
  271. /**
  272. * Modifed to be a feature fill and wrapped as tinymce module.
  273. */
  274. define("tinymce/util/Promise", [], function() {
  275. if (window.Promise) {
  276. return window.Promise;
  277. }
  278. // Use polyfill for setImmediate for performance gains
  279. var asap = Promise.immediateFn || (typeof setImmediate === 'function' && setImmediate) ||
  280. function(fn) { setTimeout(fn, 1); };
  281. // Polyfill for Function.prototype.bind
  282. function bind(fn, thisArg) {
  283. return function() {
  284. fn.apply(thisArg, arguments);
  285. };
  286. }
  287. var isArray = Array.isArray || function(value) { return Object.prototype.toString.call(value) === "[object Array]"; };
  288. function Promise(fn) {
  289. if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
  290. if (typeof fn !== 'function') throw new TypeError('not a function');
  291. this._state = null;
  292. this._value = null;
  293. this._deferreds = [];
  294. doResolve(fn, bind(resolve, this), bind(reject, this));
  295. }
  296. function handle(deferred) {
  297. var me = this;
  298. if (this._state === null) {
  299. this._deferreds.push(deferred);
  300. return;
  301. }
  302. asap(function() {
  303. var cb = me._state ? deferred.onFulfilled : deferred.onRejected;
  304. if (cb === null) {
  305. (me._state ? deferred.resolve : deferred.reject)(me._value);
  306. return;
  307. }
  308. var ret;
  309. try {
  310. ret = cb(me._value);
  311. }
  312. catch (e) {
  313. deferred.reject(e);
  314. return;
  315. }
  316. deferred.resolve(ret);
  317. });
  318. }
  319. function resolve(newValue) {
  320. try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
  321. if (newValue === this) throw new TypeError('A promise cannot be resolved with itself.');
  322. if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
  323. var then = newValue.then;
  324. if (typeof then === 'function') {
  325. doResolve(bind(then, newValue), bind(resolve, this), bind(reject, this));
  326. return;
  327. }
  328. }
  329. this._state = true;
  330. this._value = newValue;
  331. finale.call(this);
  332. } catch (e) { reject.call(this, e); }
  333. }
  334. function reject(newValue) {
  335. this._state = false;
  336. this._value = newValue;
  337. finale.call(this);
  338. }
  339. function finale() {
  340. for (var i = 0, len = this._deferreds.length; i < len; i++) {
  341. handle.call(this, this._deferreds[i]);
  342. }
  343. this._deferreds = null;
  344. }
  345. function Handler(onFulfilled, onRejected, resolve, reject){
  346. this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
  347. this.onRejected = typeof onRejected === 'function' ? onRejected : null;
  348. this.resolve = resolve;
  349. this.reject = reject;
  350. }
  351. /**
  352. * Take a potentially misbehaving resolver function and make sure
  353. * onFulfilled and onRejected are only called once.
  354. *
  355. * Makes no guarantees about asynchrony.
  356. */
  357. function doResolve(fn, onFulfilled, onRejected) {
  358. var done = false;
  359. try {
  360. fn(function (value) {
  361. if (done) return;
  362. done = true;
  363. onFulfilled(value);
  364. }, function (reason) {
  365. if (done) return;
  366. done = true;
  367. onRejected(reason);
  368. });
  369. } catch (ex) {
  370. if (done) return;
  371. done = true;
  372. onRejected(ex);
  373. }
  374. }
  375. Promise.prototype['catch'] = function (onRejected) {
  376. return this.then(null, onRejected);
  377. };
  378. Promise.prototype.then = function(onFulfilled, onRejected) {
  379. var me = this;
  380. return new Promise(function(resolve, reject) {
  381. handle.call(me, new Handler(onFulfilled, onRejected, resolve, reject));
  382. });
  383. };
  384. Promise.all = function () {
  385. var args = Array.prototype.slice.call(arguments.length === 1 && isArray(arguments[0]) ? arguments[0] : arguments);
  386. return new Promise(function (resolve, reject) {
  387. if (args.length === 0) return resolve([]);
  388. var remaining = args.length;
  389. function res(i, val) {
  390. try {
  391. if (val && (typeof val === 'object' || typeof val === 'function')) {
  392. var then = val.then;
  393. if (typeof then === 'function') {
  394. then.call(val, function (val) { res(i, val); }, reject);
  395. return;
  396. }
  397. }
  398. args[i] = val;
  399. if (--remaining === 0) {
  400. resolve(args);
  401. }
  402. } catch (ex) {
  403. reject(ex);
  404. }
  405. }
  406. for (var i = 0; i < args.length; i++) {
  407. res(i, args[i]);
  408. }
  409. });
  410. };
  411. Promise.resolve = function (value) {
  412. if (value && typeof value === 'object' && value.constructor === Promise) {
  413. return value;
  414. }
  415. return new Promise(function (resolve) {
  416. resolve(value);
  417. });
  418. };
  419. Promise.reject = function (value) {
  420. return new Promise(function (resolve, reject) {
  421. reject(value);
  422. });
  423. };
  424. Promise.race = function (values) {
  425. return new Promise(function (resolve, reject) {
  426. for(var i = 0, len = values.length; i < len; i++) {
  427. values[i].then(resolve, reject);
  428. }
  429. });
  430. };
  431. return Promise;
  432. });
  433. /* jshint ignore:end */
  434. /* eslint-enable */
  435. // Included from: js/tinymce/classes/util/Delay.js
  436. /**
  437. * Delay.js
  438. *
  439. * Released under LGPL License.
  440. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  441. *
  442. * License: http://www.tinymce.com/license
  443. * Contributing: http://www.tinymce.com/contributing
  444. */
  445. /**
  446. * Utility class for working with delayed actions like setTimeout.
  447. *
  448. * @class tinymce.util.Delay
  449. */
  450. define("tinymce/util/Delay", [
  451. "tinymce/util/Promise"
  452. ], function(Promise) {
  453. var requestAnimationFramePromise;
  454. function requestAnimationFrame(callback, element) {
  455. var i, requestAnimationFrameFunc = window.requestAnimationFrame, vendors = ['ms', 'moz', 'webkit'];
  456. function featurefill(callback) {
  457. window.setTimeout(callback, 0);
  458. }
  459. for (i = 0; i < vendors.length && !requestAnimationFrameFunc; i++) {
  460. requestAnimationFrameFunc = window[vendors[i] + 'RequestAnimationFrame'];
  461. }
  462. if (!requestAnimationFrameFunc) {
  463. requestAnimationFrameFunc = featurefill;
  464. }
  465. requestAnimationFrameFunc(callback, element);
  466. }
  467. function wrappedSetTimeout(callback, time) {
  468. if (typeof time != 'number') {
  469. time = 0;
  470. }
  471. return setTimeout(callback, time);
  472. }
  473. function wrappedSetInterval(callback, time) {
  474. if (typeof time != 'number') {
  475. time = 1; // IE 8 needs it to be > 0
  476. }
  477. return setInterval(callback, time);
  478. }
  479. function wrappedClearTimeout(id) {
  480. return clearTimeout(id);
  481. }
  482. function wrappedClearInterval(id) {
  483. return clearInterval(id);
  484. }
  485. return {
  486. /**
  487. * Requests an animation frame and fallbacks to a timeout on older browsers.
  488. *
  489. * @method requestAnimationFrame
  490. * @param {function} callback Callback to execute when a new frame is available.
  491. * @param {DOMElement} element Optional element to scope it to.
  492. */
  493. requestAnimationFrame: function(callback, element) {
  494. if (requestAnimationFramePromise) {
  495. requestAnimationFramePromise.then(callback);
  496. return;
  497. }
  498. requestAnimationFramePromise = new Promise(function(resolve) {
  499. if (!element) {
  500. element = document.body;
  501. }
  502. requestAnimationFrame(resolve, element);
  503. }).then(callback);
  504. },
  505. /**
  506. * Sets a timer in ms and executes the specified callback when the timer runs out.
  507. *
  508. * @method setTimeout
  509. * @param {function} callback Callback to execute when timer runs out.
  510. * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
  511. * @return {Number} Timeout id number.
  512. */
  513. setTimeout: wrappedSetTimeout,
  514. /**
  515. * Sets an interval timer in ms and executes the specified callback at every interval of that time.
  516. *
  517. * @method setInterval
  518. * @param {function} callback Callback to execute when interval time runs out.
  519. * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
  520. * @return {Number} Timeout id number.
  521. */
  522. setInterval: wrappedSetInterval,
  523. /**
  524. * Sets an editor timeout it's similar to setTimeout except that it checks if the editor instance is
  525. * still alive when the callback gets executed.
  526. *
  527. * @method setEditorTimeout
  528. * @param {tinymce.Editor} editor Editor instance to check the removed state on.
  529. * @param {function} callback Callback to execute when timer runs out.
  530. * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
  531. * @return {Number} Timeout id number.
  532. */
  533. setEditorTimeout: function(editor, callback, time) {
  534. return wrappedSetTimeout(function() {
  535. if (!editor.removed) {
  536. callback();
  537. }
  538. }, time);
  539. },
  540. /**
  541. * Sets an interval timer it's similar to setInterval except that it checks if the editor instance is
  542. * still alive when the callback gets executed.
  543. *
  544. * @method setEditorInterval
  545. * @param {function} callback Callback to execute when interval time runs out.
  546. * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
  547. * @return {Number} Timeout id number.
  548. */
  549. setEditorInterval: function(editor, callback, time) {
  550. var timer;
  551. timer = wrappedSetInterval(function() {
  552. if (!editor.removed) {
  553. callback();
  554. } else {
  555. clearInterval(timer);
  556. }
  557. }, time);
  558. return timer;
  559. },
  560. /**
  561. * Creates throttled callback function that only gets executed once within the specified time.
  562. *
  563. * @method throttle
  564. * @param {function} callback Callback to execute when timer finishes.
  565. * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
  566. * @return {Function} Throttled function callback.
  567. */
  568. throttle: function(callback, time) {
  569. var timer, func;
  570. func = function() {
  571. var args = arguments;
  572. clearTimeout(timer);
  573. timer = wrappedSetTimeout(function() {
  574. callback.apply(this, args);
  575. }, time);
  576. };
  577. func.stop = function() {
  578. clearTimeout(timer);
  579. };
  580. return func;
  581. },
  582. /**
  583. * Clears an interval timer so it won't execute.
  584. *
  585. * @method clearInterval
  586. * @param {Number} Interval timer id number.
  587. */
  588. clearInterval: wrappedClearInterval,
  589. /**
  590. * Clears an timeout timer so it won't execute.
  591. *
  592. * @method clearTimeout
  593. * @param {Number} Timeout timer id number.
  594. */
  595. clearTimeout: wrappedClearTimeout
  596. };
  597. });
  598. // Included from: js/tinymce/classes/Env.js
  599. /**
  600. * Env.js
  601. *
  602. * Released under LGPL License.
  603. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  604. *
  605. * License: http://www.tinymce.com/license
  606. * Contributing: http://www.tinymce.com/contributing
  607. */
  608. /**
  609. * This class contains various environment constants like browser versions etc.
  610. * Normally you don't want to sniff specific browser versions but sometimes you have
  611. * to when it's impossible to feature detect. So use this with care.
  612. *
  613. * @class tinymce.Env
  614. * @static
  615. */
  616. define("tinymce/Env", [], function() {
  617. var nav = navigator, userAgent = nav.userAgent;
  618. var opera, webkit, ie, ie11, ie12, gecko, mac, iDevice, android, fileApi, phone, tablet, windowsPhone;
  619. function matchMediaQuery(query) {
  620. return "matchMedia" in window ? matchMedia(query).matches : false;
  621. }
  622. opera = window.opera && window.opera.buildNumber;
  623. android = /Android/.test(userAgent);
  624. webkit = /WebKit/.test(userAgent);
  625. ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
  626. ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
  627. ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
  628. ie12 = (userAgent.indexOf('Edge/') != -1 && !ie && !ie11) ? 12 : false;
  629. ie = ie || ie11 || ie12;
  630. gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
  631. mac = userAgent.indexOf('Mac') != -1;
  632. iDevice = /(iPad|iPhone)/.test(userAgent);
  633. fileApi = "FormData" in window && "FileReader" in window && "URL" in window && !!URL.createObjectURL;
  634. phone = matchMediaQuery("only screen and (max-device-width: 480px)") && (android || iDevice);
  635. tablet = matchMediaQuery("only screen and (min-width: 800px)") && (android || iDevice);
  636. windowsPhone = userAgent.indexOf('Windows Phone') != -1;
  637. if (ie12) {
  638. webkit = false;
  639. }
  640. // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
  641. // says it has contentEditable support but there is no visible caret.
  642. var contentEditable = !iDevice || fileApi || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
  643. return {
  644. /**
  645. * Constant that is true if the browser is Opera.
  646. *
  647. * @property opera
  648. * @type Boolean
  649. * @final
  650. */
  651. opera: opera,
  652. /**
  653. * Constant that is true if the browser is WebKit (Safari/Chrome).
  654. *
  655. * @property webKit
  656. * @type Boolean
  657. * @final
  658. */
  659. webkit: webkit,
  660. /**
  661. * Constant that is more than zero if the browser is IE.
  662. *
  663. * @property ie
  664. * @type Boolean
  665. * @final
  666. */
  667. ie: ie,
  668. /**
  669. * Constant that is true if the browser is Gecko.
  670. *
  671. * @property gecko
  672. * @type Boolean
  673. * @final
  674. */
  675. gecko: gecko,
  676. /**
  677. * Constant that is true if the os is Mac OS.
  678. *
  679. * @property mac
  680. * @type Boolean
  681. * @final
  682. */
  683. mac: mac,
  684. /**
  685. * Constant that is true if the os is iOS.
  686. *
  687. * @property iOS
  688. * @type Boolean
  689. * @final
  690. */
  691. iOS: iDevice,
  692. /**
  693. * Constant that is true if the os is android.
  694. *
  695. * @property android
  696. * @type Boolean
  697. * @final
  698. */
  699. android: android,
  700. /**
  701. * Constant that is true if the browser supports editing.
  702. *
  703. * @property contentEditable
  704. * @type Boolean
  705. * @final
  706. */
  707. contentEditable: contentEditable,
  708. /**
  709. * Transparent image data url.
  710. *
  711. * @property transparentSrc
  712. * @type Boolean
  713. * @final
  714. */
  715. transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
  716. /**
  717. * Returns true/false if the browser can or can't place the caret after a inline block like an image.
  718. *
  719. * @property noCaretAfter
  720. * @type Boolean
  721. * @final
  722. */
  723. caretAfter: ie != 8,
  724. /**
  725. * Constant that is true if the browser supports native DOM Ranges. IE 9+.
  726. *
  727. * @property range
  728. * @type Boolean
  729. */
  730. range: window.getSelection && "Range" in window,
  731. /**
  732. * Returns the IE document mode for non IE browsers this will fake IE 10.
  733. *
  734. * @property documentMode
  735. * @type Number
  736. */
  737. documentMode: ie && !ie12 ? (document.documentMode || 7) : 10,
  738. /**
  739. * Constant that is true if the browser has a modern file api.
  740. *
  741. * @property fileApi
  742. * @type Boolean
  743. */
  744. fileApi: fileApi,
  745. /**
  746. * Constant that is true if the browser supports contentEditable=false regions.
  747. *
  748. * @property ceFalse
  749. * @type Boolean
  750. */
  751. ceFalse: (ie === false || ie > 8),
  752. desktop: !phone && !tablet,
  753. windowsPhone: windowsPhone
  754. };
  755. });
  756. // Included from: js/tinymce/classes/dom/EventUtils.js
  757. /**
  758. * EventUtils.js
  759. *
  760. * Released under LGPL License.
  761. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  762. *
  763. * License: http://www.tinymce.com/license
  764. * Contributing: http://www.tinymce.com/contributing
  765. */
  766. /*jshint loopfunc:true*/
  767. /*eslint no-loop-func:0 */
  768. /**
  769. * This class wraps the browsers native event logic with more convenient methods.
  770. *
  771. * @class tinymce.dom.EventUtils
  772. */
  773. define("tinymce/dom/EventUtils", [
  774. "tinymce/util/Delay",
  775. "tinymce/Env"
  776. ], function(Delay, Env) {
  777. "use strict";
  778. var eventExpandoPrefix = "mce-data-";
  779. var mouseEventRe = /^(?:mouse|contextmenu)|click/;
  780. var deprecated = {
  781. keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1,
  782. webkitMovementX: 1, webkitMovementY: 1, keyIdentifier: 1
  783. };
  784. /**
  785. * Binds a native event to a callback on the speified target.
  786. */
  787. function addEvent(target, name, callback, capture) {
  788. if (target.addEventListener) {
  789. target.addEventListener(name, callback, capture || false);
  790. } else if (target.attachEvent) {
  791. target.attachEvent('on' + name, callback);
  792. }
  793. }
  794. /**
  795. * Unbinds a native event callback on the specified target.
  796. */
  797. function removeEvent(target, name, callback, capture) {
  798. if (target.removeEventListener) {
  799. target.removeEventListener(name, callback, capture || false);
  800. } else if (target.detachEvent) {
  801. target.detachEvent('on' + name, callback);
  802. }
  803. }
  804. /**
  805. * Gets the event target based on shadow dom properties like path and deepPath.
  806. */
  807. function getTargetFromShadowDom(event, defaultTarget) {
  808. var path, target = defaultTarget;
  809. // When target element is inside Shadow DOM we need to take first element from path
  810. // otherwise we'll get Shadow Root parent, not actual target element
  811. // Normalize target for WebComponents v0 implementation (in Chrome)
  812. path = event.path;
  813. if (path && path.length > 0) {
  814. target = path[0];
  815. }
  816. // Normalize target for WebComponents v1 implementation (standard)
  817. if (event.deepPath) {
  818. path = event.deepPath();
  819. if (path && path.length > 0) {
  820. target = path[0];
  821. }
  822. }
  823. return target;
  824. }
  825. /**
  826. * Normalizes a native event object or just adds the event specific methods on a custom event.
  827. */
  828. function fix(originalEvent, data) {
  829. var name, event = data || {}, undef;
  830. // Dummy function that gets replaced on the delegation state functions
  831. function returnFalse() {
  832. return false;
  833. }
  834. // Dummy function that gets replaced on the delegation state functions
  835. function returnTrue() {
  836. return true;
  837. }
  838. // Copy all properties from the original event
  839. for (name in originalEvent) {
  840. // layerX/layerY is deprecated in Chrome and produces a warning
  841. if (!deprecated[name]) {
  842. event[name] = originalEvent[name];
  843. }
  844. }
  845. // Normalize target IE uses srcElement
  846. if (!event.target) {
  847. event.target = event.srcElement || document;
  848. }
  849. // Experimental shadow dom support
  850. if (Env.experimentalShadowDom) {
  851. event.target = getTargetFromShadowDom(originalEvent, event.target);
  852. }
  853. // Calculate pageX/Y if missing and clientX/Y available
  854. if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
  855. var eventDoc = event.target.ownerDocument || document;
  856. var doc = eventDoc.documentElement;
  857. var body = eventDoc.body;
  858. event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
  859. (doc && doc.clientLeft || body && body.clientLeft || 0);
  860. event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) -
  861. (doc && doc.clientTop || body && body.clientTop || 0);
  862. }
  863. // Add preventDefault method
  864. event.preventDefault = function() {
  865. event.isDefaultPrevented = returnTrue;
  866. // Execute preventDefault on the original event object
  867. if (originalEvent) {
  868. if (originalEvent.preventDefault) {
  869. originalEvent.preventDefault();
  870. } else {
  871. originalEvent.returnValue = false; // IE
  872. }
  873. }
  874. };
  875. // Add stopPropagation
  876. event.stopPropagation = function() {
  877. event.isPropagationStopped = returnTrue;
  878. // Execute stopPropagation on the original event object
  879. if (originalEvent) {
  880. if (originalEvent.stopPropagation) {
  881. originalEvent.stopPropagation();
  882. } else {
  883. originalEvent.cancelBubble = true; // IE
  884. }
  885. }
  886. };
  887. // Add stopImmediatePropagation
  888. event.stopImmediatePropagation = function() {
  889. event.isImmediatePropagationStopped = returnTrue;
  890. event.stopPropagation();
  891. };
  892. // Add event delegation states
  893. if (!event.isDefaultPrevented) {
  894. event.isDefaultPrevented = returnFalse;
  895. event.isPropagationStopped = returnFalse;
  896. event.isImmediatePropagationStopped = returnFalse;
  897. }
  898. // Add missing metaKey for IE 8
  899. if (typeof event.metaKey == 'undefined') {
  900. event.metaKey = false;
  901. }
  902. return event;
  903. }
  904. /**
  905. * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
  906. * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
  907. */
  908. function bindOnReady(win, callback, eventUtils) {
  909. var doc = win.document, event = {type: 'ready'};
  910. if (eventUtils.domLoaded) {
  911. callback(event);
  912. return;
  913. }
  914. // Gets called when the DOM is ready
  915. function readyHandler() {
  916. if (!eventUtils.domLoaded) {
  917. eventUtils.domLoaded = true;
  918. callback(event);
  919. }
  920. }
  921. function waitForDomLoaded() {
  922. // Check complete or interactive state if there is a body
  923. // element on some iframes IE 8 will produce a null body
  924. if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
  925. removeEvent(doc, "readystatechange", waitForDomLoaded);
  926. readyHandler();
  927. }
  928. }
  929. function tryScroll() {
  930. try {
  931. // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
  932. // http://javascript.nwbox.com/IEContentLoaded/
  933. doc.documentElement.doScroll("left");
  934. } catch (ex) {
  935. Delay.setTimeout(tryScroll);
  936. return;
  937. }
  938. readyHandler();
  939. }
  940. // Use W3C method
  941. if (doc.addEventListener) {
  942. if (doc.readyState === "complete") {
  943. readyHandler();
  944. } else {
  945. addEvent(win, 'DOMContentLoaded', readyHandler);
  946. }
  947. } else {
  948. // Use IE method
  949. addEvent(doc, "readystatechange", waitForDomLoaded);
  950. // Wait until we can scroll, when we can the DOM is initialized
  951. if (doc.documentElement.doScroll && win.self === win.top) {
  952. tryScroll();
  953. }
  954. }
  955. // Fallback if any of the above methods should fail for some odd reason
  956. addEvent(win, 'load', readyHandler);
  957. }
  958. /**
  959. * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
  960. */
  961. function EventUtils() {
  962. var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
  963. expando = eventExpandoPrefix + (+new Date()).toString(32);
  964. hasMouseEnterLeave = "onmouseenter" in document.documentElement;
  965. hasFocusIn = "onfocusin" in document.documentElement;
  966. mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
  967. count = 1;
  968. // State if the DOMContentLoaded was executed or not
  969. self.domLoaded = false;
  970. self.events = events;
  971. /**
  972. * Executes all event handler callbacks for a specific event.
  973. *
  974. * @private
  975. * @param {Event} evt Event object.
  976. * @param {String} id Expando id value to look for.
  977. */
  978. function executeHandlers(evt, id) {
  979. var callbackList, i, l, callback, container = events[id];
  980. callbackList = container && container[evt.type];
  981. if (callbackList) {
  982. for (i = 0, l = callbackList.length; i < l; i++) {
  983. callback = callbackList[i];
  984. // Check if callback exists might be removed if a unbind is called inside the callback
  985. if (callback && callback.func.call(callback.scope, evt) === false) {
  986. evt.preventDefault();
  987. }
  988. // Should we stop propagation to immediate listeners
  989. if (evt.isImmediatePropagationStopped()) {
  990. return;
  991. }
  992. }
  993. }
  994. }
  995. /**
  996. * Binds a callback to an event on the specified target.
  997. *
  998. * @method bind
  999. * @param {Object} target Target node/window or custom object.
  1000. * @param {String} names Name of the event to bind.
  1001. * @param {function} callback Callback function to execute when the event occurs.
  1002. * @param {Object} scope Scope to call the callback function on, defaults to target.
  1003. * @return {function} Callback function that got bound.
  1004. */
  1005. self.bind = function(target, names, callback, scope) {
  1006. var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
  1007. // Native event handler function patches the event and executes the callbacks for the expando
  1008. function defaultNativeHandler(evt) {
  1009. executeHandlers(fix(evt || win.event), id);
  1010. }
  1011. // Don't bind to text nodes or comments
  1012. if (!target || target.nodeType === 3 || target.nodeType === 8) {
  1013. return;
  1014. }
  1015. // Create or get events id for the target
  1016. if (!target[expando]) {
  1017. id = count++;
  1018. target[expando] = id;
  1019. events[id] = {};
  1020. } else {
  1021. id = target[expando];
  1022. }
  1023. // Setup the specified scope or use the target as a default
  1024. scope = scope || target;
  1025. // Split names and bind each event, enables you to bind multiple events with one call
  1026. names = names.split(' ');
  1027. i = names.length;
  1028. while (i--) {
  1029. name = names[i];
  1030. nativeHandler = defaultNativeHandler;
  1031. fakeName = capture = false;
  1032. // Use ready instead of DOMContentLoaded
  1033. if (name === "DOMContentLoaded") {
  1034. name = "ready";
  1035. }
  1036. // DOM is already ready
  1037. if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
  1038. callback.call(scope, fix({type: name}));
  1039. continue;
  1040. }
  1041. // Handle mouseenter/mouseleaver
  1042. if (!hasMouseEnterLeave) {
  1043. fakeName = mouseEnterLeave[name];
  1044. if (fakeName) {
  1045. nativeHandler = function(evt) {
  1046. var current, related;
  1047. current = evt.currentTarget;
  1048. related = evt.relatedTarget;
  1049. // Check if related is inside the current target if it's not then the event should
  1050. // be ignored since it's a mouseover/mouseout inside the element
  1051. if (related && current.contains) {
  1052. // Use contains for performance
  1053. related = current.contains(related);
  1054. } else {
  1055. while (related && related !== current) {
  1056. related = related.parentNode;
  1057. }
  1058. }
  1059. // Fire fake event
  1060. if (!related) {
  1061. evt = fix(evt || win.event);
  1062. evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
  1063. evt.target = current;
  1064. executeHandlers(evt, id);
  1065. }
  1066. };
  1067. }
  1068. }
  1069. // Fake bubbling of focusin/focusout
  1070. if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
  1071. capture = true;
  1072. fakeName = name === "focusin" ? "focus" : "blur";
  1073. nativeHandler = function(evt) {
  1074. evt = fix(evt || win.event);
  1075. evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
  1076. executeHandlers(evt, id);
  1077. };
  1078. }
  1079. // Setup callback list and bind native event
  1080. callbackList = events[id][name];
  1081. if (!callbackList) {
  1082. events[id][name] = callbackList = [{func: callback, scope: scope}];
  1083. callbackList.fakeName = fakeName;
  1084. callbackList.capture = capture;
  1085. //callbackList.callback = callback;
  1086. // Add the nativeHandler to the callback list so that we can later unbind it
  1087. callbackList.nativeHandler = nativeHandler;
  1088. // Check if the target has native events support
  1089. if (name === "ready") {
  1090. bindOnReady(target, nativeHandler, self);
  1091. } else {
  1092. addEvent(target, fakeName || name, nativeHandler, capture);
  1093. }
  1094. } else {
  1095. if (name === "ready" && self.domLoaded) {
  1096. callback({type: name});
  1097. } else {
  1098. // If it already has an native handler then just push the callback
  1099. callbackList.push({func: callback, scope: scope});
  1100. }
  1101. }
  1102. }
  1103. target = callbackList = 0; // Clean memory for IE
  1104. return callback;
  1105. };
  1106. /**
  1107. * Unbinds the specified event by name, name and callback or all events on the target.
  1108. *
  1109. * @method unbind
  1110. * @param {Object} target Target node/window or custom object.
  1111. * @param {String} names Optional event name to unbind.
  1112. * @param {function} callback Optional callback function to unbind.
  1113. * @return {EventUtils} Event utils instance.
  1114. */
  1115. self.unbind = function(target, names, callback) {
  1116. var id, callbackList, i, ci, name, eventMap;
  1117. // Don't bind to text nodes or comments
  1118. if (!target || target.nodeType === 3 || target.nodeType === 8) {
  1119. return self;
  1120. }
  1121. // Unbind event or events if the target has the expando
  1122. id = target[expando];
  1123. if (id) {
  1124. eventMap = events[id];
  1125. // Specific callback
  1126. if (names) {
  1127. names = names.split(' ');
  1128. i = names.length;
  1129. while (i--) {
  1130. name = names[i];
  1131. callbackList = eventMap[name];
  1132. // Unbind the event if it exists in the map
  1133. if (callbackList) {
  1134. // Remove specified callback
  1135. if (callback) {
  1136. ci = callbackList.length;
  1137. while (ci--) {
  1138. if (callbackList[ci].func === callback) {
  1139. var nativeHandler = callbackList.nativeHandler;
  1140. var fakeName = callbackList.fakeName, capture = callbackList.capture;
  1141. // Clone callbackList since unbind inside a callback would otherwise break the handlers loop
  1142. callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
  1143. callbackList.nativeHandler = nativeHandler;
  1144. callbackList.fakeName = fakeName;
  1145. callbackList.capture = capture;
  1146. eventMap[name] = callbackList;
  1147. }
  1148. }
  1149. }
  1150. // Remove all callbacks if there isn't a specified callback or there is no callbacks left
  1151. if (!callback || callbackList.length === 0) {
  1152. delete eventMap[name];
  1153. removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
  1154. }
  1155. }
  1156. }
  1157. } else {
  1158. // All events for a specific element
  1159. for (name in eventMap) {
  1160. callbackList = eventMap[name];
  1161. removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
  1162. }
  1163. eventMap = {};
  1164. }
  1165. // Check if object is empty, if it isn't then we won't remove the expando map
  1166. for (name in eventMap) {
  1167. return self;
  1168. }
  1169. // Delete event object
  1170. delete events[id];
  1171. // Remove expando from target
  1172. try {
  1173. // IE will fail here since it can't delete properties from window
  1174. delete target[expando];
  1175. } catch (ex) {
  1176. // IE will set it to null
  1177. target[expando] = null;
  1178. }
  1179. }
  1180. return self;
  1181. };
  1182. /**
  1183. * Fires the specified event on the specified target.
  1184. *
  1185. * @method fire
  1186. * @param {Object} target Target node/window or custom object.
  1187. * @param {String} name Event name to fire.
  1188. * @param {Object} args Optional arguments to send to the observers.
  1189. * @return {EventUtils} Event utils instance.
  1190. */
  1191. self.fire = function(target, name, args) {
  1192. var id;
  1193. // Don't bind to text nodes or comments
  1194. if (!target || target.nodeType === 3 || target.nodeType === 8) {
  1195. return self;
  1196. }
  1197. // Build event object by patching the args
  1198. args = fix(null, args);
  1199. args.type = name;
  1200. args.target = target;
  1201. do {
  1202. // Found an expando that means there is listeners to execute
  1203. id = target[expando];
  1204. if (id) {
  1205. executeHandlers(args, id);
  1206. }
  1207. // Walk up the DOM
  1208. target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
  1209. } while (target && !args.isPropagationStopped());
  1210. return self;
  1211. };
  1212. /**
  1213. * Removes all bound event listeners for the specified target. This will also remove any bound
  1214. * listeners to child nodes within that target.
  1215. *
  1216. * @method clean
  1217. * @param {Object} target Target node/window object.
  1218. * @return {EventUtils} Event utils instance.
  1219. */
  1220. self.clean = function(target) {
  1221. var i, children, unbind = self.unbind;
  1222. // Don't bind to text nodes or comments
  1223. if (!target || target.nodeType === 3 || target.nodeType === 8) {
  1224. return self;
  1225. }
  1226. // Unbind any element on the specified target
  1227. if (target[expando]) {
  1228. unbind(target);
  1229. }
  1230. // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
  1231. if (!target.getElementsByTagName) {
  1232. target = target.document;
  1233. }
  1234. // Remove events from each child element
  1235. if (target && target.getElementsByTagName) {
  1236. unbind(target);
  1237. children = target.getElementsByTagName('*');
  1238. i = children.length;
  1239. while (i--) {
  1240. target = children[i];
  1241. if (target[expando]) {
  1242. unbind(target);
  1243. }
  1244. }
  1245. }
  1246. return self;
  1247. };
  1248. /**
  1249. * Destroys the event object. Call this on IE to remove memory leaks.
  1250. */
  1251. self.destroy = function() {
  1252. events = {};
  1253. };
  1254. // Legacy function for canceling events
  1255. self.cancel = function(e) {
  1256. if (e) {
  1257. e.preventDefault();
  1258. e.stopImmediatePropagation();
  1259. }
  1260. return false;
  1261. };
  1262. }
  1263. EventUtils.Event = new EventUtils();
  1264. EventUtils.Event.bind(window, 'ready', function() {});
  1265. return EventUtils;
  1266. });
  1267. // Included from: js/tinymce/classes/dom/Sizzle.js
  1268. /**
  1269. * Sizzle.js
  1270. *
  1271. * Released under LGPL License.
  1272. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  1273. *
  1274. * License: http://www.tinymce.com/license
  1275. * Contributing: http://www.tinymce.com/contributing
  1276. *
  1277. * @ignore-file
  1278. */
  1279. /*jshint bitwise:false, expr:true, noempty:false, sub:true, eqnull:true, latedef:false, maxlen:255 */
  1280. /*eslint-disable */
  1281. /**
  1282. * Sizzle CSS Selector Engine v@VERSION
  1283. * http://sizzlejs.com/
  1284. *
  1285. * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
  1286. * Released under the MIT license
  1287. * http://jquery.org/license
  1288. *
  1289. * Date: @DATE
  1290. */
  1291. define("tinymce/dom/Sizzle", [], function() {
  1292. var i,
  1293. support,
  1294. Expr,
  1295. getText,
  1296. isXML,
  1297. tokenize,
  1298. compile,
  1299. select,
  1300. outermostContext,
  1301. sortInput,
  1302. hasDuplicate,
  1303. // Local document vars
  1304. setDocument,
  1305. document,
  1306. docElem,
  1307. documentIsHTML,
  1308. rbuggyQSA,
  1309. rbuggyMatches,
  1310. matches,
  1311. contains,
  1312. // Instance-specific data
  1313. expando = "sizzle" + -(new Date()),
  1314. preferredDoc = window.document,
  1315. dirruns = 0,
  1316. done = 0,
  1317. classCache = createCache(),
  1318. tokenCache = createCache(),
  1319. compilerCache = createCache(),
  1320. sortOrder = function( a, b ) {
  1321. if ( a === b ) {
  1322. hasDuplicate = true;
  1323. }
  1324. return 0;
  1325. },
  1326. // General-purpose constants
  1327. strundefined = typeof undefined,
  1328. MAX_NEGATIVE = 1 << 31,
  1329. // Instance methods
  1330. hasOwn = ({}).hasOwnProperty,
  1331. arr = [],
  1332. pop = arr.pop,
  1333. push_native = arr.push,
  1334. push = arr.push,
  1335. slice = arr.slice,
  1336. // Use a stripped-down indexOf if we can't use a native one
  1337. indexOf = arr.indexOf || function( elem ) {
  1338. var i = 0,
  1339. len = this.length;
  1340. for ( ; i < len; i++ ) {
  1341. if ( this[i] === elem ) {
  1342. return i;
  1343. }
  1344. }
  1345. return -1;
  1346. },
  1347. booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
  1348. // Regular expressions
  1349. // http://www.w3.org/TR/css3-selectors/#whitespace
  1350. whitespace = "[\\x20\\t\\r\\n\\f]",
  1351. // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
  1352. identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
  1353. // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
  1354. attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
  1355. // Operator (capture 2)
  1356. "*([*^$|!~]?=)" + whitespace +
  1357. // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
  1358. "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
  1359. "*\\]",
  1360. pseudos = ":(" + identifier + ")(?:\\((" +
  1361. // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
  1362. // 1. quoted (capture 3; capture 4 or capture 5)
  1363. "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
  1364. // 2. simple (capture 6)
  1365. "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
  1366. // 3. anything else (capture 2)
  1367. ".*" +
  1368. ")\\)|)",
  1369. // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
  1370. rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
  1371. rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
  1372. rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
  1373. rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
  1374. rpseudo = new RegExp( pseudos ),
  1375. ridentifier = new RegExp( "^" + identifier + "$" ),
  1376. matchExpr = {
  1377. "ID": new RegExp( "^#(" + identifier + ")" ),
  1378. "CLASS": new RegExp( "^\\.(" + identifier + ")" ),
  1379. "TAG": new RegExp( "^(" + identifier + "|[*])" ),
  1380. "ATTR": new RegExp( "^" + attributes ),
  1381. "PSEUDO": new RegExp( "^" + pseudos ),
  1382. "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
  1383. "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
  1384. "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
  1385. "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
  1386. // For use in libraries implementing .is()
  1387. // We use this for POS matching in `select`
  1388. "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
  1389. whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
  1390. },
  1391. rinputs = /^(?:input|select|textarea|button)$/i,
  1392. rheader = /^h\d$/i,
  1393. rnative = /^[^{]+\{\s*\[native \w/,
  1394. // Easily-parseable/retrievable ID or TAG or CLASS selectors
  1395. rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
  1396. rsibling = /[+~]/,
  1397. rescape = /'|\\/g,
  1398. // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
  1399. runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
  1400. funescape = function( _, escaped, escapedWhitespace ) {
  1401. var high = "0x" + escaped - 0x10000;
  1402. // NaN means non-codepoint
  1403. // Support: Firefox<24
  1404. // Workaround erroneous numeric interpretation of +"0x"
  1405. return high !== high || escapedWhitespace ?
  1406. escaped :
  1407. high < 0 ?
  1408. // BMP codepoint
  1409. String.fromCharCode( high + 0x10000 ) :
  1410. // Supplemental Plane codepoint (surrogate pair)
  1411. String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
  1412. };
  1413. // Optimize for push.apply( _, NodeList )
  1414. try {
  1415. push.apply(
  1416. (arr = slice.call( preferredDoc.childNodes )),
  1417. preferredDoc.childNodes
  1418. );
  1419. // Support: Android<4.0
  1420. // Detect silently failing push.apply
  1421. arr[ preferredDoc.childNodes.length ].nodeType;
  1422. } catch ( e ) {
  1423. push = { apply: arr.length ?
  1424. // Leverage slice if possible
  1425. function( target, els ) {
  1426. push_native.apply( target, slice.call(els) );
  1427. } :
  1428. // Support: IE<9
  1429. // Otherwise append directly
  1430. function( target, els ) {
  1431. var j = target.length,
  1432. i = 0;
  1433. // Can't trust NodeList.length
  1434. while ( (target[j++] = els[i++]) ) {}
  1435. target.length = j - 1;
  1436. }
  1437. };
  1438. }
  1439. function Sizzle( selector, context, results, seed ) {
  1440. var match, elem, m, nodeType,
  1441. // QSA vars
  1442. i, groups, old, nid, newContext, newSelector;
  1443. if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
  1444. setDocument( context );
  1445. }
  1446. context = context || document;
  1447. results = results || [];
  1448. if ( !selector || typeof selector !== "string" ) {
  1449. return results;
  1450. }
  1451. if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
  1452. return [];
  1453. }
  1454. if ( documentIsHTML && !seed ) {
  1455. // Shortcuts
  1456. if ( (match = rquickExpr.exec( selector )) ) {
  1457. // Speed-up: Sizzle("#ID")
  1458. if ( (m = match[1]) ) {
  1459. if ( nodeType === 9 ) {
  1460. elem = context.getElementById( m );
  1461. // Check parentNode to catch when Blackberry 4.6 returns
  1462. // nodes that are no longer in the document (jQuery #6963)
  1463. if ( elem && elem.parentNode ) {
  1464. // Handle the case where IE, Opera, and Webkit return items
  1465. // by name instead of ID
  1466. if ( elem.id === m ) {
  1467. results.push( elem );
  1468. return results;
  1469. }
  1470. } else {
  1471. return results;
  1472. }
  1473. } else {
  1474. // Context is not a document
  1475. if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
  1476. contains( context, elem ) && elem.id === m ) {
  1477. results.push( elem );
  1478. return results;
  1479. }
  1480. }
  1481. // Speed-up: Sizzle("TAG")
  1482. } else if ( match[2] ) {
  1483. push.apply( results, context.getElementsByTagName( selector ) );
  1484. return results;
  1485. // Speed-up: Sizzle(".CLASS")
  1486. } else if ( (m = match[3]) && support.getElementsByClassName ) {
  1487. push.apply( results, context.getElementsByClassName( m ) );
  1488. return results;
  1489. }
  1490. }
  1491. // QSA path
  1492. if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
  1493. nid = old = expando;
  1494. newContext = context;
  1495. newSelector = nodeType === 9 && selector;
  1496. // qSA works strangely on Element-rooted queries
  1497. // We can work around this by specifying an extra ID on the root
  1498. // and working up from there (Thanks to Andrew Dupont for the technique)
  1499. // IE 8 doesn't work on object elements
  1500. if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
  1501. groups = tokenize( selector );
  1502. if ( (old = context.getAttribute("id")) ) {
  1503. nid = old.replace( rescape, "\\$&" );
  1504. } else {
  1505. context.setAttribute( "id", nid );
  1506. }
  1507. nid = "[id='" + nid + "'] ";
  1508. i = groups.length;
  1509. while ( i-- ) {
  1510. groups[i] = nid + toSelector( groups[i] );
  1511. }
  1512. newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
  1513. newSelector = groups.join(",");
  1514. }
  1515. if ( newSelector ) {
  1516. try {
  1517. push.apply( results,
  1518. newContext.querySelectorAll( newSelector )
  1519. );
  1520. return results;
  1521. } catch(qsaError) {
  1522. } finally {
  1523. if ( !old ) {
  1524. context.removeAttribute("id");
  1525. }
  1526. }
  1527. }
  1528. }
  1529. }
  1530. // All others
  1531. return select( selector.replace( rtrim, "$1" ), context, results, seed );
  1532. }
  1533. /**
  1534. * Create key-value caches of limited size
  1535. * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
  1536. * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
  1537. * deleting the oldest entry
  1538. */
  1539. function createCache() {
  1540. var keys = [];
  1541. function cache( key, value ) {
  1542. // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
  1543. if ( keys.push( key + " " ) > Expr.cacheLength ) {
  1544. // Only keep the most recent entries
  1545. delete cache[ keys.shift() ];
  1546. }
  1547. return (cache[ key + " " ] = value);
  1548. }
  1549. return cache;
  1550. }
  1551. /**
  1552. * Mark a function for special use by Sizzle
  1553. * @param {Function} fn The function to mark
  1554. */
  1555. function markFunction( fn ) {
  1556. fn[ expando ] = true;
  1557. return fn;
  1558. }
  1559. /**
  1560. * Support testing using an element
  1561. * @param {Function} fn Passed the created div and expects a boolean result
  1562. */
  1563. function assert( fn ) {
  1564. var div = document.createElement("div");
  1565. try {
  1566. return !!fn( div );
  1567. } catch (e) {
  1568. return false;
  1569. } finally {
  1570. // Remove from its parent by default
  1571. if ( div.parentNode ) {
  1572. div.parentNode.removeChild( div );
  1573. }
  1574. // release memory in IE
  1575. div = null;
  1576. }
  1577. }
  1578. /**
  1579. * Adds the same handler for all of the specified attrs
  1580. * @param {String} attrs Pipe-separated list of attributes
  1581. * @param {Function} handler The method that will be applied
  1582. */
  1583. function addHandle( attrs, handler ) {
  1584. var arr = attrs.split("|"),
  1585. i = attrs.length;
  1586. while ( i-- ) {
  1587. Expr.attrHandle[ arr[i] ] = handler;
  1588. }
  1589. }
  1590. /**
  1591. * Checks document order of two siblings
  1592. * @param {Element} a
  1593. * @param {Element} b
  1594. * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
  1595. */
  1596. function siblingCheck( a, b ) {
  1597. var cur = b && a,
  1598. diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
  1599. ( ~b.sourceIndex || MAX_NEGATIVE ) -
  1600. ( ~a.sourceIndex || MAX_NEGATIVE );
  1601. // Use IE sourceIndex if available on both nodes
  1602. if ( diff ) {
  1603. return diff;
  1604. }
  1605. // Check if b follows a
  1606. if ( cur ) {
  1607. while ( (cur = cur.nextSibling) ) {
  1608. if ( cur === b ) {
  1609. return -1;
  1610. }
  1611. }
  1612. }
  1613. return a ? 1 : -1;
  1614. }
  1615. /**
  1616. * Returns a function to use in pseudos for input types
  1617. * @param {String} type
  1618. */
  1619. function createInputPseudo( type ) {
  1620. return function( elem ) {
  1621. var name = elem.nodeName.toLowerCase();
  1622. return name === "input" && elem.type === type;
  1623. };
  1624. }
  1625. /**
  1626. * Returns a function to use in pseudos for buttons
  1627. * @param {String} type
  1628. */
  1629. function createButtonPseudo( type ) {
  1630. return function( elem ) {
  1631. var name = elem.nodeName.toLowerCase();
  1632. return (name === "input" || name === "button") && elem.type === type;
  1633. };
  1634. }
  1635. /**
  1636. * Returns a function to use in pseudos for positionals
  1637. * @param {Function} fn
  1638. */
  1639. function createPositionalPseudo( fn ) {
  1640. return markFunction(function( argument ) {
  1641. argument = +argument;
  1642. return markFunction(function( seed, matches ) {
  1643. var j,
  1644. matchIndexes = fn( [], seed.length, argument ),
  1645. i = matchIndexes.length;
  1646. // Match elements found at the specified indexes
  1647. while ( i-- ) {
  1648. if ( seed[ (j = matchIndexes[i]) ] ) {
  1649. seed[j] = !(matches[j] = seed[j]);
  1650. }
  1651. }
  1652. });
  1653. });
  1654. }
  1655. /**
  1656. * Checks a node for validity as a Sizzle context
  1657. * @param {Element|Object=} context
  1658. * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
  1659. */
  1660. function testContext( context ) {
  1661. return context && typeof context.getElementsByTagName !== strundefined && context;
  1662. }
  1663. // Expose support vars for convenience
  1664. support = Sizzle.support = {};
  1665. /**
  1666. * Detects XML nodes
  1667. * @param {Element|Object} elem An element or a document
  1668. * @returns {Boolean} True iff elem is a non-HTML XML node
  1669. */
  1670. isXML = Sizzle.isXML = function( elem ) {
  1671. // documentElement is verified for cases where it doesn't yet exist
  1672. // (such as loading iframes in IE - #4833)
  1673. var documentElement = elem && (elem.ownerDocument || elem).documentElement;
  1674. return documentElement ? documentElement.nodeName !== "HTML" : false;
  1675. };
  1676. /**
  1677. * Sets document-related variables once based on the current document
  1678. * @param {Element|Object} [doc] An element or document object to use to set the document
  1679. * @returns {Object} Returns the current document
  1680. */
  1681. setDocument = Sizzle.setDocument = function( node ) {
  1682. var hasCompare,
  1683. doc = node ? node.ownerDocument || node : preferredDoc,
  1684. parent = doc.defaultView;
  1685. function getTop(win) {
  1686. // Edge throws a lovely Object expected if you try to get top on a detached reference see #2642
  1687. try {
  1688. return win.top;
  1689. } catch (ex) {
  1690. // Ignore
  1691. }
  1692. return null;
  1693. }
  1694. // If no document and documentElement is available, return
  1695. if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
  1696. return document;
  1697. }
  1698. // Set our document
  1699. document = doc;
  1700. docElem = doc.documentElement;
  1701. // Support tests
  1702. documentIsHTML = !isXML( doc );
  1703. // Support: IE>8
  1704. // If iframe document is assigned to "document" variable and if iframe has been reloaded,
  1705. // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
  1706. // IE6-8 do not support the defaultView property so parent will be undefined
  1707. if ( parent && parent !== getTop(parent) ) {
  1708. // IE11 does not have attachEvent, so all must suffer
  1709. if ( parent.addEventListener ) {
  1710. parent.addEventListener( "unload", function() {
  1711. setDocument();
  1712. }, false );
  1713. } else if ( parent.attachEvent ) {
  1714. parent.attachEvent( "onunload", function() {
  1715. setDocument();
  1716. });
  1717. }
  1718. }
  1719. /* Attributes
  1720. ---------------------------------------------------------------------- */
  1721. // Support: IE<8
  1722. // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
  1723. support.attributes = assert(function( div ) {
  1724. div.className = "i";
  1725. return !div.getAttribute("className");
  1726. });
  1727. /* getElement(s)By*
  1728. ---------------------------------------------------------------------- */
  1729. // Check if getElementsByTagName("*") returns only elements
  1730. support.getElementsByTagName = assert(function( div ) {
  1731. div.appendChild( doc.createComment("") );
  1732. return !div.getElementsByTagName("*").length;
  1733. });
  1734. // Support: IE<9
  1735. support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
  1736. // Support: IE<10
  1737. // Check if getElementById returns elements by name
  1738. // The broken getElementById methods don't pick up programatically-set names,
  1739. // so use a roundabout getElementsByName test
  1740. support.getById = assert(function( div ) {
  1741. docElem.appendChild( div ).id = expando;
  1742. return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
  1743. });
  1744. // ID find and filter
  1745. if ( support.getById ) {
  1746. Expr.find["ID"] = function( id, context ) {
  1747. if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
  1748. var m = context.getElementById( id );
  1749. // Check parentNode to catch when Blackberry 4.6 returns
  1750. // nodes that are no longer in the document #6963
  1751. return m && m.parentNode ? [ m ] : [];
  1752. }
  1753. };
  1754. Expr.filter["ID"] = function( id ) {
  1755. var attrId = id.replace( runescape, funescape );
  1756. return function( elem ) {
  1757. return elem.getAttribute("id") === attrId;
  1758. };
  1759. };
  1760. } else {
  1761. // Support: IE6/7
  1762. // getElementById is not reliable as a find shortcut
  1763. delete Expr.find["ID"];
  1764. Expr.filter["ID"] = function( id ) {
  1765. var attrId = id.replace( runescape, funescape );
  1766. return function( elem ) {
  1767. var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
  1768. return node && node.value === attrId;
  1769. };
  1770. };
  1771. }
  1772. // Tag
  1773. Expr.find["TAG"] = support.getElementsByTagName ?
  1774. function( tag, context ) {
  1775. if ( typeof context.getElementsByTagName !== strundefined ) {
  1776. return context.getElementsByTagName( tag );
  1777. }
  1778. } :
  1779. function( tag, context ) {
  1780. var elem,
  1781. tmp = [],
  1782. i = 0,
  1783. results = context.getElementsByTagName( tag );
  1784. // Filter out possible comments
  1785. if ( tag === "*" ) {
  1786. while ( (elem = results[i++]) ) {
  1787. if ( elem.nodeType === 1 ) {
  1788. tmp.push( elem );
  1789. }
  1790. }
  1791. return tmp;
  1792. }
  1793. return results;
  1794. };
  1795. // Class
  1796. Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
  1797. if ( documentIsHTML ) {
  1798. return context.getElementsByClassName( className );
  1799. }
  1800. };
  1801. /* QSA/matchesSelector
  1802. ---------------------------------------------------------------------- */
  1803. // QSA and matchesSelector support
  1804. // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
  1805. rbuggyMatches = [];
  1806. // qSa(:focus) reports false when true (Chrome 21)
  1807. // We allow this because of a bug in IE8/9 that throws an error
  1808. // whenever `document.activeElement` is accessed on an iframe
  1809. // So, we allow :focus to pass through QSA all the time to avoid the IE error
  1810. // See http://bugs.jquery.com/ticket/13378
  1811. rbuggyQSA = [];
  1812. if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
  1813. // Build QSA regex
  1814. // Regex strategy adopted from Diego Perini
  1815. assert(function( div ) {
  1816. // Select is set to empty string on purpose
  1817. // This is to test IE's treatment of not explicitly
  1818. // setting a boolean content attribute,
  1819. // since its presence should be enough
  1820. // http://bugs.jquery.com/ticket/12359
  1821. div.innerHTML = "<select msallowcapture=''><option selected=''></option></select>";
  1822. // Support: IE8, Opera 11-12.16
  1823. // Nothing should be selected when empty strings follow ^= or $= or *=
  1824. // The test attribute must be unknown in Opera but "safe" for WinRT
  1825. // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
  1826. if ( div.querySelectorAll("[msallowcapture^='']").length ) {
  1827. rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
  1828. }
  1829. // Support: IE8
  1830. // Boolean attributes and "value" are not treated correctly
  1831. if ( !div.querySelectorAll("[selected]").length ) {
  1832. rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
  1833. }
  1834. // Webkit/Opera - :checked should return selected option elements
  1835. // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
  1836. // IE8 throws error here and will not see later tests
  1837. if ( !div.querySelectorAll(":checked").length ) {
  1838. rbuggyQSA.push(":checked");
  1839. }
  1840. });
  1841. assert(function( div ) {
  1842. // Support: Windows 8 Native Apps
  1843. // The type and name attributes are restricted during .innerHTML assignment
  1844. var input = doc.createElement("input");
  1845. input.setAttribute( "type", "hidden" );
  1846. div.appendChild( input ).setAttribute( "name", "D" );
  1847. // Support: IE8
  1848. // Enforce case-sensitivity of name attribute
  1849. if ( div.querySelectorAll("[name=d]").length ) {
  1850. rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
  1851. }
  1852. // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
  1853. // IE8 throws error here and will not see later tests
  1854. if ( !div.querySelectorAll(":enabled").length ) {
  1855. rbuggyQSA.push( ":enabled", ":disabled" );
  1856. }
  1857. // Opera 10-11 does not throw on post-comma invalid pseudos
  1858. div.querySelectorAll("*,:x");
  1859. rbuggyQSA.push(",.*:");
  1860. });
  1861. }
  1862. if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
  1863. docElem.webkitMatchesSelector ||
  1864. docElem.mozMatchesSelector ||
  1865. docElem.oMatchesSelector ||
  1866. docElem.msMatchesSelector) )) ) {
  1867. assert(function( div ) {
  1868. // Check to see if it's possible to do matchesSelector
  1869. // on a disconnected node (IE 9)
  1870. support.disconnectedMatch = matches.call( div, "div" );
  1871. // This should fail with an exception
  1872. // Gecko does not error, returns false instead
  1873. matches.call( div, "[s!='']:x" );
  1874. rbuggyMatches.push( "!=", pseudos );
  1875. });
  1876. }
  1877. rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
  1878. rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
  1879. /* Contains
  1880. ---------------------------------------------------------------------- */
  1881. hasCompare = rnative.test( docElem.compareDocumentPosition );
  1882. // Element contains another
  1883. // Purposefully does not implement inclusive descendent
  1884. // As in, an element does not contain itself
  1885. contains = hasCompare || rnative.test( docElem.contains ) ?
  1886. function( a, b ) {
  1887. var adown = a.nodeType === 9 ? a.documentElement : a,
  1888. bup = b && b.parentNode;
  1889. return a === bup || !!( bup && bup.nodeType === 1 && (
  1890. adown.contains ?
  1891. adown.contains( bup ) :
  1892. a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
  1893. ));
  1894. } :
  1895. function( a, b ) {
  1896. if ( b ) {
  1897. while ( (b = b.parentNode) ) {
  1898. if ( b === a ) {
  1899. return true;
  1900. }
  1901. }
  1902. }
  1903. return false;
  1904. };
  1905. /* Sorting
  1906. ---------------------------------------------------------------------- */
  1907. // Document order sorting
  1908. sortOrder = hasCompare ?
  1909. function( a, b ) {
  1910. // Flag for duplicate removal
  1911. if ( a === b ) {
  1912. hasDuplicate = true;
  1913. return 0;
  1914. }
  1915. // Sort on method existence if only one input has compareDocumentPosition
  1916. var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
  1917. if ( compare ) {
  1918. return compare;
  1919. }
  1920. // Calculate position if both inputs belong to the same document
  1921. compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
  1922. a.compareDocumentPosition( b ) :
  1923. // Otherwise we know they are disconnected
  1924. 1;
  1925. // Disconnected nodes
  1926. if ( compare & 1 ||
  1927. (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
  1928. // Choose the first element that is related to our preferred document
  1929. if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
  1930. return -1;
  1931. }
  1932. if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
  1933. return 1;
  1934. }
  1935. // Maintain original order
  1936. return sortInput ?
  1937. ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
  1938. 0;
  1939. }
  1940. return compare & 4 ? -1 : 1;
  1941. } :
  1942. function( a, b ) {
  1943. // Exit early if the nodes are identical
  1944. if ( a === b ) {
  1945. hasDuplicate = true;
  1946. return 0;
  1947. }
  1948. var cur,
  1949. i = 0,
  1950. aup = a.parentNode,
  1951. bup = b.parentNode,
  1952. ap = [ a ],
  1953. bp = [ b ];
  1954. // Parentless nodes are either documents or disconnected
  1955. if ( !aup || !bup ) {
  1956. return a === doc ? -1 :
  1957. b === doc ? 1 :
  1958. aup ? -1 :
  1959. bup ? 1 :
  1960. sortInput ?
  1961. ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
  1962. 0;
  1963. // If the nodes are siblings, we can do a quick check
  1964. } else if ( aup === bup ) {
  1965. return siblingCheck( a, b );
  1966. }
  1967. // Otherwise we need full lists of their ancestors for comparison
  1968. cur = a;
  1969. while ( (cur = cur.parentNode) ) {
  1970. ap.unshift( cur );
  1971. }
  1972. cur = b;
  1973. while ( (cur = cur.parentNode) ) {
  1974. bp.unshift( cur );
  1975. }
  1976. // Walk down the tree looking for a discrepancy
  1977. while ( ap[i] === bp[i] ) {
  1978. i++;
  1979. }
  1980. return i ?
  1981. // Do a sibling check if the nodes have a common ancestor
  1982. siblingCheck( ap[i], bp[i] ) :
  1983. // Otherwise nodes in our document sort first
  1984. ap[i] === preferredDoc ? -1 :
  1985. bp[i] === preferredDoc ? 1 :
  1986. 0;
  1987. };
  1988. return doc;
  1989. };
  1990. Sizzle.matches = function( expr, elements ) {
  1991. return Sizzle( expr, null, null, elements );
  1992. };
  1993. Sizzle.matchesSelector = function( elem, expr ) {
  1994. // Set document vars if needed
  1995. if ( ( elem.ownerDocument || elem ) !== document ) {
  1996. setDocument( elem );
  1997. }
  1998. // Make sure that attribute selectors are quoted
  1999. expr = expr.replace( rattributeQuotes, "='$1']" );
  2000. if ( support.matchesSelector && documentIsHTML &&
  2001. ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
  2002. ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
  2003. try {
  2004. var ret = matches.call( elem, expr );
  2005. // IE 9's matchesSelector returns false on disconnected nodes
  2006. if ( ret || support.disconnectedMatch ||
  2007. // As well, disconnected nodes are said to be in a document
  2008. // fragment in IE 9
  2009. elem.document && elem.document.nodeType !== 11 ) {
  2010. return ret;
  2011. }
  2012. } catch(e) {}
  2013. }
  2014. return Sizzle( expr, document, null, [ elem ] ).length > 0;
  2015. };
  2016. Sizzle.contains = function( context, elem ) {
  2017. // Set document vars if needed
  2018. if ( ( context.ownerDocument || context ) !== document ) {
  2019. setDocument( context );
  2020. }
  2021. return contains( context, elem );
  2022. };
  2023. Sizzle.attr = function( elem, name ) {
  2024. // Set document vars if needed
  2025. if ( ( elem.ownerDocument || elem ) !== document ) {
  2026. setDocument( elem );
  2027. }
  2028. var fn = Expr.attrHandle[ name.toLowerCase() ],
  2029. // Don't get fooled by Object.prototype properties (jQuery #13807)
  2030. val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
  2031. fn( elem, name, !documentIsHTML ) :
  2032. undefined;
  2033. return val !== undefined ?
  2034. val :
  2035. support.attributes || !documentIsHTML ?
  2036. elem.getAttribute( name ) :
  2037. (val = elem.getAttributeNode(name)) && val.specified ?
  2038. val.value :
  2039. null;
  2040. };
  2041. Sizzle.error = function( msg ) {
  2042. throw new Error( "Syntax error, unrecognized expression: " + msg );
  2043. };
  2044. /**
  2045. * Document sorting and removing duplicates
  2046. * @param {ArrayLike} results
  2047. */
  2048. Sizzle.uniqueSort = function( results ) {
  2049. var elem,
  2050. duplicates = [],
  2051. j = 0,
  2052. i = 0;
  2053. // Unless we *know* we can detect duplicates, assume their presence
  2054. hasDuplicate = !support.detectDuplicates;
  2055. sortInput = !support.sortStable && results.slice( 0 );
  2056. results.sort( sortOrder );
  2057. if ( hasDuplicate ) {
  2058. while ( (elem = results[i++]) ) {
  2059. if ( elem === results[ i ] ) {
  2060. j = duplicates.push( i );
  2061. }
  2062. }
  2063. while ( j-- ) {
  2064. results.splice( duplicates[ j ], 1 );
  2065. }
  2066. }
  2067. // Clear input after sorting to release objects
  2068. // See https://github.com/jquery/sizzle/pull/225
  2069. sortInput = null;
  2070. return results;
  2071. };
  2072. /**
  2073. * Utility function for retrieving the text value of an array of DOM nodes
  2074. * @param {Array|Element} elem
  2075. */
  2076. getText = Sizzle.getText = function( elem ) {
  2077. var node,
  2078. ret = "",
  2079. i = 0,
  2080. nodeType = elem.nodeType;
  2081. if ( !nodeType ) {
  2082. // If no nodeType, this is expected to be an array
  2083. while ( (node = elem[i++]) ) {
  2084. // Do not traverse comment nodes
  2085. ret += getText( node );
  2086. }
  2087. } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
  2088. // Use textContent for elements
  2089. // innerText usage removed for consistency of new lines (jQuery #11153)
  2090. if ( typeof elem.textContent === "string" ) {
  2091. return elem.textContent;
  2092. } else {
  2093. // Traverse its children
  2094. for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
  2095. ret += getText( elem );
  2096. }
  2097. }
  2098. } else if ( nodeType === 3 || nodeType === 4 ) {
  2099. return elem.nodeValue;
  2100. }
  2101. // Do not include comment or processing instruction nodes
  2102. return ret;
  2103. };
  2104. Expr = Sizzle.selectors = {
  2105. // Can be adjusted by the user
  2106. cacheLength: 50,
  2107. createPseudo: markFunction,
  2108. match: matchExpr,
  2109. attrHandle: {},
  2110. find: {},
  2111. relative: {
  2112. ">": { dir: "parentNode", first: true },
  2113. " ": { dir: "parentNode" },
  2114. "+": { dir: "previousSibling", first: true },
  2115. "~": { dir: "previousSibling" }
  2116. },
  2117. preFilter: {
  2118. "ATTR": function( match ) {
  2119. match[1] = match[1].replace( runescape, funescape );
  2120. // Move the given value to match[3] whether quoted or unquoted
  2121. match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
  2122. if ( match[2] === "~=" ) {
  2123. match[3] = " " + match[3] + " ";
  2124. }
  2125. return match.slice( 0, 4 );
  2126. },
  2127. "CHILD": function( match ) {
  2128. /* matches from matchExpr["CHILD"]
  2129. 1 type (only|nth|...)
  2130. 2 what (child|of-type)
  2131. 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
  2132. 4 xn-component of xn+y argument ([+-]?\d*n|)
  2133. 5 sign of xn-component
  2134. 6 x of xn-component
  2135. 7 sign of y-component
  2136. 8 y of y-component
  2137. */
  2138. match[1] = match[1].toLowerCase();
  2139. if ( match[1].slice( 0, 3 ) === "nth" ) {
  2140. // nth-* requires argument
  2141. if ( !match[3] ) {
  2142. Sizzle.error( match[0] );
  2143. }
  2144. // numeric x and y parameters for Expr.filter.CHILD
  2145. // remember that false/true cast respectively to 0/1
  2146. match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
  2147. match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
  2148. // other types prohibit arguments
  2149. } else if ( match[3] ) {
  2150. Sizzle.error( match[0] );
  2151. }
  2152. return match;
  2153. },
  2154. "PSEUDO": function( match ) {
  2155. var excess,
  2156. unquoted = !match[6] && match[2];
  2157. if ( matchExpr["CHILD"].test( match[0] ) ) {
  2158. return null;
  2159. }
  2160. // Accept quoted arguments as-is
  2161. if ( match[3] ) {
  2162. match[2] = match[4] || match[5] || "";
  2163. // Strip excess characters from unquoted arguments
  2164. } else if ( unquoted && rpseudo.test( unquoted ) &&
  2165. // Get excess from tokenize (recursively)
  2166. (excess = tokenize( unquoted, true )) &&
  2167. // advance to the next closing parenthesis
  2168. (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
  2169. // excess is a negative index
  2170. match[0] = match[0].slice( 0, excess );
  2171. match[2] = unquoted.slice( 0, excess );
  2172. }
  2173. // Return only captures needed by the pseudo filter method (type and argument)
  2174. return match.slice( 0, 3 );
  2175. }
  2176. },
  2177. filter: {
  2178. "TAG": function( nodeNameSelector ) {
  2179. var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
  2180. return nodeNameSelector === "*" ?
  2181. function() { return true; } :
  2182. function( elem ) {
  2183. return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
  2184. };
  2185. },
  2186. "CLASS": function( className ) {
  2187. var pattern = classCache[ className + " " ];
  2188. return pattern ||
  2189. (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
  2190. classCache( className, function( elem ) {
  2191. return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
  2192. });
  2193. },
  2194. "ATTR": function( name, operator, check ) {
  2195. return function( elem ) {
  2196. var result = Sizzle.attr( elem, name );
  2197. if ( result == null ) {
  2198. return operator === "!=";
  2199. }
  2200. if ( !operator ) {
  2201. return true;
  2202. }
  2203. result += "";
  2204. return operator === "=" ? result === check :
  2205. operator === "!=" ? result !== check :
  2206. operator === "^=" ? check && result.indexOf( check ) === 0 :
  2207. operator === "*=" ? check && result.indexOf( check ) > -1 :
  2208. operator === "$=" ? check && result.slice( -check.length ) === check :
  2209. operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
  2210. operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
  2211. false;
  2212. };
  2213. },
  2214. "CHILD": function( type, what, argument, first, last ) {
  2215. var simple = type.slice( 0, 3 ) !== "nth",
  2216. forward = type.slice( -4 ) !== "last",
  2217. ofType = what === "of-type";
  2218. return first === 1 && last === 0 ?
  2219. // Shortcut for :nth-*(n)
  2220. function( elem ) {
  2221. return !!elem.parentNode;
  2222. } :
  2223. function( elem, context, xml ) {
  2224. var cache, outerCache, node, diff, nodeIndex, start,
  2225. dir = simple !== forward ? "nextSibling" : "previousSibling",
  2226. parent = elem.parentNode,
  2227. name = ofType && elem.nodeName.toLowerCase(),
  2228. useCache = !xml && !ofType;
  2229. if ( parent ) {
  2230. // :(first|last|only)-(child|of-type)
  2231. if ( simple ) {
  2232. while ( dir ) {
  2233. node = elem;
  2234. while ( (node = node[ dir ]) ) {
  2235. if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
  2236. return false;
  2237. }
  2238. }
  2239. // Reverse direction for :only-* (if we haven't yet done so)
  2240. start = dir = type === "only" && !start && "nextSibling";
  2241. }
  2242. return true;
  2243. }
  2244. start = [ forward ? parent.firstChild : parent.lastChild ];
  2245. // non-xml :nth-child(...) stores cache data on `parent`
  2246. if ( forward && useCache ) {
  2247. // Seek `elem` from a previously-cached index
  2248. outerCache = parent[ expando ] || (parent[ expando ] = {});
  2249. cache = outerCache[ type ] || [];
  2250. nodeIndex = cache[0] === dirruns && cache[1];
  2251. diff = cache[0] === dirruns && cache[2];
  2252. node = nodeIndex && parent.childNodes[ nodeIndex ];
  2253. while ( (node = ++nodeIndex && node && node[ dir ] ||
  2254. // Fallback to seeking `elem` from the start
  2255. (diff = nodeIndex = 0) || start.pop()) ) {
  2256. // When found, cache indexes on `parent` and break
  2257. if ( node.nodeType === 1 && ++diff && node === elem ) {
  2258. outerCache[ type ] = [ dirruns, nodeIndex, diff ];
  2259. break;
  2260. }
  2261. }
  2262. // Use previously-cached element index if available
  2263. } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
  2264. diff = cache[1];
  2265. // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
  2266. } else {
  2267. // Use the same loop as above to seek `elem` from the start
  2268. while ( (node = ++nodeIndex && node && node[ dir ] ||
  2269. (diff = nodeIndex = 0) || start.pop()) ) {
  2270. if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
  2271. // Cache the index of each encountered element
  2272. if ( useCache ) {
  2273. (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
  2274. }
  2275. if ( node === elem ) {
  2276. break;
  2277. }
  2278. }
  2279. }
  2280. }
  2281. // Incorporate the offset, then check against cycle size
  2282. diff -= last;
  2283. return diff === first || ( diff % first === 0 && diff / first >= 0 );
  2284. }
  2285. };
  2286. },
  2287. "PSEUDO": function( pseudo, argument ) {
  2288. // pseudo-class names are case-insensitive
  2289. // http://www.w3.org/TR/selectors/#pseudo-classes
  2290. // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
  2291. // Remember that setFilters inherits from pseudos
  2292. var args,
  2293. fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
  2294. Sizzle.error( "unsupported pseudo: " + pseudo );
  2295. // The user may use createPseudo to indicate that
  2296. // arguments are needed to create the filter function
  2297. // just as Sizzle does
  2298. if ( fn[ expando ] ) {
  2299. return fn( argument );
  2300. }
  2301. // But maintain support for old signatures
  2302. if ( fn.length > 1 ) {
  2303. args = [ pseudo, pseudo, "", argument ];
  2304. return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
  2305. markFunction(function( seed, matches ) {
  2306. var idx,
  2307. matched = fn( seed, argument ),
  2308. i = matched.length;
  2309. while ( i-- ) {
  2310. idx = indexOf.call( seed, matched[i] );
  2311. seed[ idx ] = !( matches[ idx ] = matched[i] );
  2312. }
  2313. }) :
  2314. function( elem ) {
  2315. return fn( elem, 0, args );
  2316. };
  2317. }
  2318. return fn;
  2319. }
  2320. },
  2321. pseudos: {
  2322. // Potentially complex pseudos
  2323. "not": markFunction(function( selector ) {
  2324. // Trim the selector passed to compile
  2325. // to avoid treating leading and trailing
  2326. // spaces as combinators
  2327. var input = [],
  2328. results = [],
  2329. matcher = compile( selector.replace( rtrim, "$1" ) );
  2330. return matcher[ expando ] ?
  2331. markFunction(function( seed, matches, context, xml ) {
  2332. var elem,
  2333. unmatched = matcher( seed, null, xml, [] ),
  2334. i = seed.length;
  2335. // Match elements unmatched by `matcher`
  2336. while ( i-- ) {
  2337. if ( (elem = unmatched[i]) ) {
  2338. seed[i] = !(matches[i] = elem);
  2339. }
  2340. }
  2341. }) :
  2342. function( elem, context, xml ) {
  2343. input[0] = elem;
  2344. matcher( input, null, xml, results );
  2345. return !results.pop();
  2346. };
  2347. }),
  2348. "has": markFunction(function( selector ) {
  2349. return function( elem ) {
  2350. return Sizzle( selector, elem ).length > 0;
  2351. };
  2352. }),
  2353. "contains": markFunction(function( text ) {
  2354. text = text.replace( runescape, funescape );
  2355. return function( elem ) {
  2356. return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
  2357. };
  2358. }),
  2359. // "Whether an element is represented by a :lang() selector
  2360. // is based solely on the element's language value
  2361. // being equal to the identifier C,
  2362. // or beginning with the identifier C immediately followed by "-".
  2363. // The matching of C against the element's language value is performed case-insensitively.
  2364. // The identifier C does not have to be a valid language name."
  2365. // http://www.w3.org/TR/selectors/#lang-pseudo
  2366. "lang": markFunction( function( lang ) {
  2367. // lang value must be a valid identifier
  2368. if ( !ridentifier.test(lang || "") ) {
  2369. Sizzle.error( "unsupported lang: " + lang );
  2370. }
  2371. lang = lang.replace( runescape, funescape ).toLowerCase();
  2372. return function( elem ) {
  2373. var elemLang;
  2374. do {
  2375. if ( (elemLang = documentIsHTML ?
  2376. elem.lang :
  2377. elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
  2378. elemLang = elemLang.toLowerCase();
  2379. return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
  2380. }
  2381. } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
  2382. return false;
  2383. };
  2384. }),
  2385. // Miscellaneous
  2386. "target": function( elem ) {
  2387. var hash = window.location && window.location.hash;
  2388. return hash && hash.slice( 1 ) === elem.id;
  2389. },
  2390. "root": function( elem ) {
  2391. return elem === docElem;
  2392. },
  2393. "focus": function( elem ) {
  2394. return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
  2395. },
  2396. // Boolean properties
  2397. "enabled": function( elem ) {
  2398. return elem.disabled === false;
  2399. },
  2400. "disabled": function( elem ) {
  2401. return elem.disabled === true;
  2402. },
  2403. "checked": function( elem ) {
  2404. // In CSS3, :checked should return both checked and selected elements
  2405. // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
  2406. var nodeName = elem.nodeName.toLowerCase();
  2407. return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
  2408. },
  2409. "selected": function( elem ) {
  2410. // Accessing this property makes selected-by-default
  2411. // options in Safari work properly
  2412. if ( elem.parentNode ) {
  2413. elem.parentNode.selectedIndex;
  2414. }
  2415. return elem.selected === true;
  2416. },
  2417. // Contents
  2418. "empty": function( elem ) {
  2419. // http://www.w3.org/TR/selectors/#empty-pseudo
  2420. // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
  2421. // but not by others (comment: 8; processing instruction: 7; etc.)
  2422. // nodeType < 6 works because attributes (2) do not appear as children
  2423. for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
  2424. if ( elem.nodeType < 6 ) {
  2425. return false;
  2426. }
  2427. }
  2428. return true;
  2429. },
  2430. "parent": function( elem ) {
  2431. return !Expr.pseudos["empty"]( elem );
  2432. },
  2433. // Element/input types
  2434. "header": function( elem ) {
  2435. return rheader.test( elem.nodeName );
  2436. },
  2437. "input": function( elem ) {
  2438. return rinputs.test( elem.nodeName );
  2439. },
  2440. "button": function( elem ) {
  2441. var name = elem.nodeName.toLowerCase();
  2442. return name === "input" && elem.type === "button" || name === "button";
  2443. },
  2444. "text": function( elem ) {
  2445. var attr;
  2446. return elem.nodeName.toLowerCase() === "input" &&
  2447. elem.type === "text" &&
  2448. // Support: IE<8
  2449. // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
  2450. ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
  2451. },
  2452. // Position-in-collection
  2453. "first": createPositionalPseudo(function() {
  2454. return [ 0 ];
  2455. }),
  2456. "last": createPositionalPseudo(function( matchIndexes, length ) {
  2457. return [ length - 1 ];
  2458. }),
  2459. "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
  2460. return [ argument < 0 ? argument + length : argument ];
  2461. }),
  2462. "even": createPositionalPseudo(function( matchIndexes, length ) {
  2463. var i = 0;
  2464. for ( ; i < length; i += 2 ) {
  2465. matchIndexes.push( i );
  2466. }
  2467. return matchIndexes;
  2468. }),
  2469. "odd": createPositionalPseudo(function( matchIndexes, length ) {
  2470. var i = 1;
  2471. for ( ; i < length; i += 2 ) {
  2472. matchIndexes.push( i );
  2473. }
  2474. return matchIndexes;
  2475. }),
  2476. "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
  2477. var i = argument < 0 ? argument + length : argument;
  2478. for ( ; --i >= 0; ) {
  2479. matchIndexes.push( i );
  2480. }
  2481. return matchIndexes;
  2482. }),
  2483. "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
  2484. var i = argument < 0 ? argument + length : argument;
  2485. for ( ; ++i < length; ) {
  2486. matchIndexes.push( i );
  2487. }
  2488. return matchIndexes;
  2489. })
  2490. }
  2491. };
  2492. Expr.pseudos["nth"] = Expr.pseudos["eq"];
  2493. // Add button/input type pseudos
  2494. for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
  2495. Expr.pseudos[ i ] = createInputPseudo( i );
  2496. }
  2497. for ( i in { submit: true, reset: true } ) {
  2498. Expr.pseudos[ i ] = createButtonPseudo( i );
  2499. }
  2500. // Easy API for creating new setFilters
  2501. function setFilters() {}
  2502. setFilters.prototype = Expr.filters = Expr.pseudos;
  2503. Expr.setFilters = new setFilters();
  2504. tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
  2505. var matched, match, tokens, type,
  2506. soFar, groups, preFilters,
  2507. cached = tokenCache[ selector + " " ];
  2508. if ( cached ) {
  2509. return parseOnly ? 0 : cached.slice( 0 );
  2510. }
  2511. soFar = selector;
  2512. groups = [];
  2513. preFilters = Expr.preFilter;
  2514. while ( soFar ) {
  2515. // Comma and first run
  2516. if ( !matched || (match = rcomma.exec( soFar )) ) {
  2517. if ( match ) {
  2518. // Don't consume trailing commas as valid
  2519. soFar = soFar.slice( match[0].length ) || soFar;
  2520. }
  2521. groups.push( (tokens = []) );
  2522. }
  2523. matched = false;
  2524. // Combinators
  2525. if ( (match = rcombinators.exec( soFar )) ) {
  2526. matched = match.shift();
  2527. tokens.push({
  2528. value: matched,
  2529. // Cast descendant combinators to space
  2530. type: match[0].replace( rtrim, " " )
  2531. });
  2532. soFar = soFar.slice( matched.length );
  2533. }
  2534. // Filters
  2535. for ( type in Expr.filter ) {
  2536. if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
  2537. (match = preFilters[ type ]( match ))) ) {
  2538. matched = match.shift();
  2539. tokens.push({
  2540. value: matched,
  2541. type: type,
  2542. matches: match
  2543. });
  2544. soFar = soFar.slice( matched.length );
  2545. }
  2546. }
  2547. if ( !matched ) {
  2548. break;
  2549. }
  2550. }
  2551. // Return the length of the invalid excess
  2552. // if we're just parsing
  2553. // Otherwise, throw an error or return tokens
  2554. return parseOnly ?
  2555. soFar.length :
  2556. soFar ?
  2557. Sizzle.error( selector ) :
  2558. // Cache the tokens
  2559. tokenCache( selector, groups ).slice( 0 );
  2560. };
  2561. function toSelector( tokens ) {
  2562. var i = 0,
  2563. len = tokens.length,
  2564. selector = "";
  2565. for ( ; i < len; i++ ) {
  2566. selector += tokens[i].value;
  2567. }
  2568. return selector;
  2569. }
  2570. function addCombinator( matcher, combinator, base ) {
  2571. var dir = combinator.dir,
  2572. checkNonElements = base && dir === "parentNode",
  2573. doneName = done++;
  2574. return combinator.first ?
  2575. // Check against closest ancestor/preceding element
  2576. function( elem, context, xml ) {
  2577. while ( (elem = elem[ dir ]) ) {
  2578. if ( elem.nodeType === 1 || checkNonElements ) {
  2579. return matcher( elem, context, xml );
  2580. }
  2581. }
  2582. } :
  2583. // Check against all ancestor/preceding elements
  2584. function( elem, context, xml ) {
  2585. var oldCache, outerCache,
  2586. newCache = [ dirruns, doneName ];
  2587. // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
  2588. if ( xml ) {
  2589. while ( (elem = elem[ dir ]) ) {
  2590. if ( elem.nodeType === 1 || checkNonElements ) {
  2591. if ( matcher( elem, context, xml ) ) {
  2592. return true;
  2593. }
  2594. }
  2595. }
  2596. } else {
  2597. while ( (elem = elem[ dir ]) ) {
  2598. if ( elem.nodeType === 1 || checkNonElements ) {
  2599. outerCache = elem[ expando ] || (elem[ expando ] = {});
  2600. if ( (oldCache = outerCache[ dir ]) &&
  2601. oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
  2602. // Assign to newCache so results back-propagate to previous elements
  2603. return (newCache[ 2 ] = oldCache[ 2 ]);
  2604. } else {
  2605. // Reuse newcache so results back-propagate to previous elements
  2606. outerCache[ dir ] = newCache;
  2607. // A match means we're done; a fail means we have to keep checking
  2608. if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
  2609. return true;
  2610. }
  2611. }
  2612. }
  2613. }
  2614. }
  2615. };
  2616. }
  2617. function elementMatcher( matchers ) {
  2618. return matchers.length > 1 ?
  2619. function( elem, context, xml ) {
  2620. var i = matchers.length;
  2621. while ( i-- ) {
  2622. if ( !matchers[i]( elem, context, xml ) ) {
  2623. return false;
  2624. }
  2625. }
  2626. return true;
  2627. } :
  2628. matchers[0];
  2629. }
  2630. function multipleContexts( selector, contexts, results ) {
  2631. var i = 0,
  2632. len = contexts.length;
  2633. for ( ; i < len; i++ ) {
  2634. Sizzle( selector, contexts[i], results );
  2635. }
  2636. return results;
  2637. }
  2638. function condense( unmatched, map, filter, context, xml ) {
  2639. var elem,
  2640. newUnmatched = [],
  2641. i = 0,
  2642. len = unmatched.length,
  2643. mapped = map != null;
  2644. for ( ; i < len; i++ ) {
  2645. if ( (elem = unmatched[i]) ) {
  2646. if ( !filter || filter( elem, context, xml ) ) {
  2647. newUnmatched.push( elem );
  2648. if ( mapped ) {
  2649. map.push( i );
  2650. }
  2651. }
  2652. }
  2653. }
  2654. return newUnmatched;
  2655. }
  2656. function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
  2657. if ( postFilter && !postFilter[ expando ] ) {
  2658. postFilter = setMatcher( postFilter );
  2659. }
  2660. if ( postFinder && !postFinder[ expando ] ) {
  2661. postFinder = setMatcher( postFinder, postSelector );
  2662. }
  2663. return markFunction(function( seed, results, context, xml ) {
  2664. var temp, i, elem,
  2665. preMap = [],
  2666. postMap = [],
  2667. preexisting = results.length,
  2668. // Get initial elements from seed or context
  2669. elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
  2670. // Prefilter to get matcher input, preserving a map for seed-results synchronization
  2671. matcherIn = preFilter && ( seed || !selector ) ?
  2672. condense( elems, preMap, preFilter, context, xml ) :
  2673. elems,
  2674. matcherOut = matcher ?
  2675. // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
  2676. postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
  2677. // ...intermediate processing is necessary
  2678. [] :
  2679. // ...otherwise use results directly
  2680. results :
  2681. matcherIn;
  2682. // Find primary matches
  2683. if ( matcher ) {
  2684. matcher( matcherIn, matcherOut, context, xml );
  2685. }
  2686. // Apply postFilter
  2687. if ( postFilter ) {
  2688. temp = condense( matcherOut, postMap );
  2689. postFilter( temp, [], context, xml );
  2690. // Un-match failing elements by moving them back to matcherIn
  2691. i = temp.length;
  2692. while ( i-- ) {
  2693. if ( (elem = temp[i]) ) {
  2694. matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
  2695. }
  2696. }
  2697. }
  2698. if ( seed ) {
  2699. if ( postFinder || preFilter ) {
  2700. if ( postFinder ) {
  2701. // Get the final matcherOut by condensing this intermediate into postFinder contexts
  2702. temp = [];
  2703. i = matcherOut.length;
  2704. while ( i-- ) {
  2705. if ( (elem = matcherOut[i]) ) {
  2706. // Restore matcherIn since elem is not yet a final match
  2707. temp.push( (matcherIn[i] = elem) );
  2708. }
  2709. }
  2710. postFinder( null, (matcherOut = []), temp, xml );
  2711. }
  2712. // Move matched elements from seed to results to keep them synchronized
  2713. i = matcherOut.length;
  2714. while ( i-- ) {
  2715. if ( (elem = matcherOut[i]) &&
  2716. (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
  2717. seed[temp] = !(results[temp] = elem);
  2718. }
  2719. }
  2720. }
  2721. // Add elements to results, through postFinder if defined
  2722. } else {
  2723. matcherOut = condense(
  2724. matcherOut === results ?
  2725. matcherOut.splice( preexisting, matcherOut.length ) :
  2726. matcherOut
  2727. );
  2728. if ( postFinder ) {
  2729. postFinder( null, results, matcherOut, xml );
  2730. } else {
  2731. push.apply( results, matcherOut );
  2732. }
  2733. }
  2734. });
  2735. }
  2736. function matcherFromTokens( tokens ) {
  2737. var checkContext, matcher, j,
  2738. len = tokens.length,
  2739. leadingRelative = Expr.relative[ tokens[0].type ],
  2740. implicitRelative = leadingRelative || Expr.relative[" "],
  2741. i = leadingRelative ? 1 : 0,
  2742. // The foundational matcher ensures that elements are reachable from top-level context(s)
  2743. matchContext = addCombinator( function( elem ) {
  2744. return elem === checkContext;
  2745. }, implicitRelative, true ),
  2746. matchAnyContext = addCombinator( function( elem ) {
  2747. return indexOf.call( checkContext, elem ) > -1;
  2748. }, implicitRelative, true ),
  2749. matchers = [ function( elem, context, xml ) {
  2750. return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
  2751. (checkContext = context).nodeType ?
  2752. matchContext( elem, context, xml ) :
  2753. matchAnyContext( elem, context, xml ) );
  2754. } ];
  2755. for ( ; i < len; i++ ) {
  2756. if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
  2757. matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
  2758. } else {
  2759. matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
  2760. // Return special upon seeing a positional matcher
  2761. if ( matcher[ expando ] ) {
  2762. // Find the next relative operator (if any) for proper handling
  2763. j = ++i;
  2764. for ( ; j < len; j++ ) {
  2765. if ( Expr.relative[ tokens[j].type ] ) {
  2766. break;
  2767. }
  2768. }
  2769. return setMatcher(
  2770. i > 1 && elementMatcher( matchers ),
  2771. i > 1 && toSelector(
  2772. // If the preceding token was a descendant combinator, insert an implicit any-element `*`
  2773. tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
  2774. ).replace( rtrim, "$1" ),
  2775. matcher,
  2776. i < j && matcherFromTokens( tokens.slice( i, j ) ),
  2777. j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
  2778. j < len && toSelector( tokens )
  2779. );
  2780. }
  2781. matchers.push( matcher );
  2782. }
  2783. }
  2784. return elementMatcher( matchers );
  2785. }
  2786. function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
  2787. var bySet = setMatchers.length > 0,
  2788. byElement = elementMatchers.length > 0,
  2789. superMatcher = function( seed, context, xml, results, outermost ) {
  2790. var elem, j, matcher,
  2791. matchedCount = 0,
  2792. i = "0",
  2793. unmatched = seed && [],
  2794. setMatched = [],
  2795. contextBackup = outermostContext,
  2796. // We must always have either seed elements or outermost context
  2797. elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
  2798. // Use integer dirruns iff this is the outermost matcher
  2799. dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
  2800. len = elems.length;
  2801. if ( outermost ) {
  2802. outermostContext = context !== document && context;
  2803. }
  2804. // Add elements passing elementMatchers directly to results
  2805. // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
  2806. // Support: IE<9, Safari
  2807. // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
  2808. for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
  2809. if ( byElement && elem ) {
  2810. j = 0;
  2811. while ( (matcher = elementMatchers[j++]) ) {
  2812. if ( matcher( elem, context, xml ) ) {
  2813. results.push( elem );
  2814. break;
  2815. }
  2816. }
  2817. if ( outermost ) {
  2818. dirruns = dirrunsUnique;
  2819. }
  2820. }
  2821. // Track unmatched elements for set filters
  2822. if ( bySet ) {
  2823. // They will have gone through all possible matchers
  2824. if ( (elem = !matcher && elem) ) {
  2825. matchedCount--;
  2826. }
  2827. // Lengthen the array for every element, matched or not
  2828. if ( seed ) {
  2829. unmatched.push( elem );
  2830. }
  2831. }
  2832. }
  2833. // Apply set filters to unmatched elements
  2834. matchedCount += i;
  2835. if ( bySet && i !== matchedCount ) {
  2836. j = 0;
  2837. while ( (matcher = setMatchers[j++]) ) {
  2838. matcher( unmatched, setMatched, context, xml );
  2839. }
  2840. if ( seed ) {
  2841. // Reintegrate element matches to eliminate the need for sorting
  2842. if ( matchedCount > 0 ) {
  2843. while ( i-- ) {
  2844. if ( !(unmatched[i] || setMatched[i]) ) {
  2845. setMatched[i] = pop.call( results );
  2846. }
  2847. }
  2848. }
  2849. // Discard index placeholder values to get only actual matches
  2850. setMatched = condense( setMatched );
  2851. }
  2852. // Add matches to results
  2853. push.apply( results, setMatched );
  2854. // Seedless set matches succeeding multiple successful matchers stipulate sorting
  2855. if ( outermost && !seed && setMatched.length > 0 &&
  2856. ( matchedCount + setMatchers.length ) > 1 ) {
  2857. Sizzle.uniqueSort( results );
  2858. }
  2859. }
  2860. // Override manipulation of globals by nested matchers
  2861. if ( outermost ) {
  2862. dirruns = dirrunsUnique;
  2863. outermostContext = contextBackup;
  2864. }
  2865. return unmatched;
  2866. };
  2867. return bySet ?
  2868. markFunction( superMatcher ) :
  2869. superMatcher;
  2870. }
  2871. compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
  2872. var i,
  2873. setMatchers = [],
  2874. elementMatchers = [],
  2875. cached = compilerCache[ selector + " " ];
  2876. if ( !cached ) {
  2877. // Generate a function of recursive functions that can be used to check each element
  2878. if ( !match ) {
  2879. match = tokenize( selector );
  2880. }
  2881. i = match.length;
  2882. while ( i-- ) {
  2883. cached = matcherFromTokens( match[i] );
  2884. if ( cached[ expando ] ) {
  2885. setMatchers.push( cached );
  2886. } else {
  2887. elementMatchers.push( cached );
  2888. }
  2889. }
  2890. // Cache the compiled function
  2891. cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
  2892. // Save selector and tokenization
  2893. cached.selector = selector;
  2894. }
  2895. return cached;
  2896. };
  2897. /**
  2898. * A low-level selection function that works with Sizzle's compiled
  2899. * selector functions
  2900. * @param {String|Function} selector A selector or a pre-compiled
  2901. * selector function built with Sizzle.compile
  2902. * @param {Element} context
  2903. * @param {Array} [results]
  2904. * @param {Array} [seed] A set of elements to match against
  2905. */
  2906. select = Sizzle.select = function( selector, context, results, seed ) {
  2907. var i, tokens, token, type, find,
  2908. compiled = typeof selector === "function" && selector,
  2909. match = !seed && tokenize( (selector = compiled.selector || selector) );
  2910. results = results || [];
  2911. // Try to minimize operations if there is no seed and only one group
  2912. if ( match.length === 1 ) {
  2913. // Take a shortcut and set the context if the root selector is an ID
  2914. tokens = match[0] = match[0].slice( 0 );
  2915. if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
  2916. support.getById && context.nodeType === 9 && documentIsHTML &&
  2917. Expr.relative[ tokens[1].type ] ) {
  2918. context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
  2919. if ( !context ) {
  2920. return results;
  2921. // Precompiled matchers will still verify ancestry, so step up a level
  2922. } else if ( compiled ) {
  2923. context = context.parentNode;
  2924. }
  2925. selector = selector.slice( tokens.shift().value.length );
  2926. }
  2927. // Fetch a seed set for right-to-left matching
  2928. i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
  2929. while ( i-- ) {
  2930. token = tokens[i];
  2931. // Abort if we hit a combinator
  2932. if ( Expr.relative[ (type = token.type) ] ) {
  2933. break;
  2934. }
  2935. if ( (find = Expr.find[ type ]) ) {
  2936. // Search, expanding context for leading sibling combinators
  2937. if ( (seed = find(
  2938. token.matches[0].replace( runescape, funescape ),
  2939. rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
  2940. )) ) {
  2941. // If seed is empty or no tokens remain, we can return early
  2942. tokens.splice( i, 1 );
  2943. selector = seed.length && toSelector( tokens );
  2944. if ( !selector ) {
  2945. push.apply( results, seed );
  2946. return results;
  2947. }
  2948. break;
  2949. }
  2950. }
  2951. }
  2952. }
  2953. // Compile and execute a filtering function if one is not provided
  2954. // Provide `match` to avoid retokenization if we modified the selector above
  2955. ( compiled || compile( selector, match ) )(
  2956. seed,
  2957. context,
  2958. !documentIsHTML,
  2959. results,
  2960. rsibling.test( selector ) && testContext( context.parentNode ) || context
  2961. );
  2962. return results;
  2963. };
  2964. // One-time assignments
  2965. // Sort stability
  2966. support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
  2967. // Support: Chrome 14-35+
  2968. // Always assume duplicates if they aren't passed to the comparison function
  2969. support.detectDuplicates = !!hasDuplicate;
  2970. // Initialize against the default document
  2971. setDocument();
  2972. // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
  2973. // Detached nodes confoundingly follow *each other*
  2974. support.sortDetached = assert(function( div1 ) {
  2975. // Should return 1, but returns 4 (following)
  2976. return div1.compareDocumentPosition( document.createElement("div") ) & 1;
  2977. });
  2978. // Support: IE<8
  2979. // Prevent attribute/property "interpolation"
  2980. // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
  2981. if ( !assert(function( div ) {
  2982. div.innerHTML = "<a href='#'></a>";
  2983. return div.firstChild.getAttribute("href") === "#" ;
  2984. }) ) {
  2985. addHandle( "type|href|height|width", function( elem, name, isXML ) {
  2986. if ( !isXML ) {
  2987. return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
  2988. }
  2989. });
  2990. }
  2991. // Support: IE<9
  2992. // Use defaultValue in place of getAttribute("value")
  2993. if ( !support.attributes || !assert(function( div ) {
  2994. div.innerHTML = "<input/>";
  2995. div.firstChild.setAttribute( "value", "" );
  2996. return div.firstChild.getAttribute( "value" ) === "";
  2997. }) ) {
  2998. addHandle( "value", function( elem, name, isXML ) {
  2999. if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
  3000. return elem.defaultValue;
  3001. }
  3002. });
  3003. }
  3004. // Support: IE<9
  3005. // Use getAttributeNode to fetch booleans when getAttribute lies
  3006. if ( !assert(function( div ) {
  3007. return div.getAttribute("disabled") == null;
  3008. }) ) {
  3009. addHandle( booleans, function( elem, name, isXML ) {
  3010. var val;
  3011. if ( !isXML ) {
  3012. return elem[ name ] === true ? name.toLowerCase() :
  3013. (val = elem.getAttributeNode( name )) && val.specified ?
  3014. val.value :
  3015. null;
  3016. }
  3017. });
  3018. }
  3019. // EXPOSE
  3020. return Sizzle;
  3021. });
  3022. /*eslint-enable */
  3023. // Included from: js/tinymce/classes/util/Arr.js
  3024. /**
  3025. * Arr.js
  3026. *
  3027. * Released under LGPL License.
  3028. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  3029. *
  3030. * License: http://www.tinymce.com/license
  3031. * Contributing: http://www.tinymce.com/contributing
  3032. */
  3033. /**
  3034. * Array utility class.
  3035. *
  3036. * @private
  3037. * @class tinymce.util.Arr
  3038. */
  3039. define("tinymce/util/Arr", [], function() {
  3040. var isArray = Array.isArray || function(obj) {
  3041. return Object.prototype.toString.call(obj) === "[object Array]";
  3042. };
  3043. function toArray(obj) {
  3044. var array = obj, i, l;
  3045. if (!isArray(obj)) {
  3046. array = [];
  3047. for (i = 0, l = obj.length; i < l; i++) {
  3048. array[i] = obj[i];
  3049. }
  3050. }
  3051. return array;
  3052. }
  3053. function each(o, cb, s) {
  3054. var n, l;
  3055. if (!o) {
  3056. return 0;
  3057. }
  3058. s = s || o;
  3059. if (o.length !== undefined) {
  3060. // Indexed arrays, needed for Safari
  3061. for (n = 0, l = o.length; n < l; n++) {
  3062. if (cb.call(s, o[n], n, o) === false) {
  3063. return 0;
  3064. }
  3065. }
  3066. } else {
  3067. // Hashtables
  3068. for (n in o) {
  3069. if (o.hasOwnProperty(n)) {
  3070. if (cb.call(s, o[n], n, o) === false) {
  3071. return 0;
  3072. }
  3073. }
  3074. }
  3075. }
  3076. return 1;
  3077. }
  3078. function map(array, callback) {
  3079. var out = [];
  3080. each(array, function(item, index) {
  3081. out.push(callback(item, index, array));
  3082. });
  3083. return out;
  3084. }
  3085. function filter(a, f) {
  3086. var o = [];
  3087. each(a, function(v, index) {
  3088. if (!f || f(v, index, a)) {
  3089. o.push(v);
  3090. }
  3091. });
  3092. return o;
  3093. }
  3094. function indexOf(a, v) {
  3095. var i, l;
  3096. if (a) {
  3097. for (i = 0, l = a.length; i < l; i++) {
  3098. if (a[i] === v) {
  3099. return i;
  3100. }
  3101. }
  3102. }
  3103. return -1;
  3104. }
  3105. function reduce(collection, iteratee, accumulator, thisArg) {
  3106. var i = 0;
  3107. if (arguments.length < 3) {
  3108. accumulator = collection[0];
  3109. }
  3110. for (; i < collection.length; i++) {
  3111. accumulator = iteratee.call(thisArg, accumulator, collection[i], i);
  3112. }
  3113. return accumulator;
  3114. }
  3115. function findIndex(array, predicate, thisArg) {
  3116. var i, l;
  3117. for (i = 0, l = array.length; i < l; i++) {
  3118. if (predicate.call(thisArg, array[i], i, array)) {
  3119. return i;
  3120. }
  3121. }
  3122. return -1;
  3123. }
  3124. function find(array, predicate, thisArg) {
  3125. var idx = findIndex(array, predicate, thisArg);
  3126. if (idx !== -1) {
  3127. return array[idx];
  3128. }
  3129. return undefined;
  3130. }
  3131. function last(collection) {
  3132. return collection[collection.length - 1];
  3133. }
  3134. return {
  3135. isArray: isArray,
  3136. toArray: toArray,
  3137. each: each,
  3138. map: map,
  3139. filter: filter,
  3140. indexOf: indexOf,
  3141. reduce: reduce,
  3142. findIndex: findIndex,
  3143. find: find,
  3144. last: last
  3145. };
  3146. });
  3147. // Included from: js/tinymce/classes/util/Tools.js
  3148. /**
  3149. * Tools.js
  3150. *
  3151. * Released under LGPL License.
  3152. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  3153. *
  3154. * License: http://www.tinymce.com/license
  3155. * Contributing: http://www.tinymce.com/contributing
  3156. */
  3157. /**
  3158. * This class contains various utlity functions. These are also exposed
  3159. * directly on the tinymce namespace.
  3160. *
  3161. * @class tinymce.util.Tools
  3162. */
  3163. define("tinymce/util/Tools", [
  3164. "tinymce/Env",
  3165. "tinymce/util/Arr"
  3166. ], function(Env, Arr) {
  3167. /**
  3168. * Removes whitespace from the beginning and end of a string.
  3169. *
  3170. * @method trim
  3171. * @param {String} s String to remove whitespace from.
  3172. * @return {String} New string with removed whitespace.
  3173. */
  3174. var whiteSpaceRegExp = /^\s*|\s*$/g;
  3175. function trim(str) {
  3176. return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
  3177. }
  3178. /**
  3179. * Checks if a object is of a specific type for example an array.
  3180. *
  3181. * @method is
  3182. * @param {Object} obj Object to check type of.
  3183. * @param {string} type Optional type to check for.
  3184. * @return {Boolean} true/false if the object is of the specified type.
  3185. */
  3186. function is(obj, type) {
  3187. if (!type) {
  3188. return obj !== undefined;
  3189. }
  3190. if (type == 'array' && Arr.isArray(obj)) {
  3191. return true;
  3192. }
  3193. return typeof obj == type;
  3194. }
  3195. /**
  3196. * Makes a name/object map out of an array with names.
  3197. *
  3198. * @method makeMap
  3199. * @param {Array/String} items Items to make map out of.
  3200. * @param {String} delim Optional delimiter to split string by.
  3201. * @param {Object} map Optional map to add items to.
  3202. * @return {Object} Name/value map of items.
  3203. */
  3204. function makeMap(items, delim, map) {
  3205. var i;
  3206. items = items || [];
  3207. delim = delim || ',';
  3208. if (typeof items == "string") {
  3209. items = items.split(delim);
  3210. }
  3211. map = map || {};
  3212. i = items.length;
  3213. while (i--) {
  3214. map[items[i]] = {};
  3215. }
  3216. return map;
  3217. }
  3218. /**
  3219. * Creates a class, subclass or static singleton.
  3220. * More details on this method can be found in the Wiki.
  3221. *
  3222. * @method create
  3223. * @param {String} s Class name, inheritance and prefix.
  3224. * @param {Object} p Collection of methods to add to the class.
  3225. * @param {Object} root Optional root object defaults to the global window object.
  3226. * @example
  3227. * // Creates a basic class
  3228. * tinymce.create('tinymce.somepackage.SomeClass', {
  3229. * SomeClass: function() {
  3230. * // Class constructor
  3231. * },
  3232. *
  3233. * method: function() {
  3234. * // Some method
  3235. * }
  3236. * });
  3237. *
  3238. * // Creates a basic subclass class
  3239. * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', {
  3240. * SomeSubClass: function() {
  3241. * // Class constructor
  3242. * this.parent(); // Call parent constructor
  3243. * },
  3244. *
  3245. * method: function() {
  3246. * // Some method
  3247. * this.parent(); // Call parent method
  3248. * },
  3249. *
  3250. * 'static': {
  3251. * staticMethod: function() {
  3252. * // Static method
  3253. * }
  3254. * }
  3255. * });
  3256. *
  3257. * // Creates a singleton/static class
  3258. * tinymce.create('static tinymce.somepackage.SomeSingletonClass', {
  3259. * method: function() {
  3260. * // Some method
  3261. * }
  3262. * });
  3263. */
  3264. function create(s, p, root) {
  3265. var self = this, sp, ns, cn, scn, c, de = 0;
  3266. // Parse : <prefix> <class>:<super class>
  3267. s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
  3268. cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
  3269. // Create namespace for new class
  3270. ns = self.createNS(s[3].replace(/\.\w+$/, ''), root);
  3271. // Class already exists
  3272. if (ns[cn]) {
  3273. return;
  3274. }
  3275. // Make pure static class
  3276. if (s[2] == 'static') {
  3277. ns[cn] = p;
  3278. if (this.onCreate) {
  3279. this.onCreate(s[2], s[3], ns[cn]);
  3280. }
  3281. return;
  3282. }
  3283. // Create default constructor
  3284. if (!p[cn]) {
  3285. p[cn] = function() {};
  3286. de = 1;
  3287. }
  3288. // Add constructor and methods
  3289. ns[cn] = p[cn];
  3290. self.extend(ns[cn].prototype, p);
  3291. // Extend
  3292. if (s[5]) {
  3293. sp = self.resolve(s[5]).prototype;
  3294. scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
  3295. // Extend constructor
  3296. c = ns[cn];
  3297. if (de) {
  3298. // Add passthrough constructor
  3299. ns[cn] = function() {
  3300. return sp[scn].apply(this, arguments);
  3301. };
  3302. } else {
  3303. // Add inherit constructor
  3304. ns[cn] = function() {
  3305. this.parent = sp[scn];
  3306. return c.apply(this, arguments);
  3307. };
  3308. }
  3309. ns[cn].prototype[cn] = ns[cn];
  3310. // Add super methods
  3311. self.each(sp, function(f, n) {
  3312. ns[cn].prototype[n] = sp[n];
  3313. });
  3314. // Add overridden methods
  3315. self.each(p, function(f, n) {
  3316. // Extend methods if needed
  3317. if (sp[n]) {
  3318. ns[cn].prototype[n] = function() {
  3319. this.parent = sp[n];
  3320. return f.apply(this, arguments);
  3321. };
  3322. } else {
  3323. if (n != cn) {
  3324. ns[cn].prototype[n] = f;
  3325. }
  3326. }
  3327. });
  3328. }
  3329. // Add static methods
  3330. /*jshint sub:true*/
  3331. /*eslint dot-notation:0*/
  3332. self.each(p['static'], function(f, n) {
  3333. ns[cn][n] = f;
  3334. });
  3335. }
  3336. function extend(obj, ext) {
  3337. var i, l, name, args = arguments, value;
  3338. for (i = 1, l = args.length; i < l; i++) {
  3339. ext = args[i];
  3340. for (name in ext) {
  3341. if (ext.hasOwnProperty(name)) {
  3342. value = ext[name];
  3343. if (value !== undefined) {
  3344. obj[name] = value;
  3345. }
  3346. }
  3347. }
  3348. }
  3349. return obj;
  3350. }
  3351. /**
  3352. * Executed the specified function for each item in a object tree.
  3353. *
  3354. * @method walk
  3355. * @param {Object} o Object tree to walk though.
  3356. * @param {function} f Function to call for each item.
  3357. * @param {String} n Optional name of collection inside the objects to walk for example childNodes.
  3358. * @param {String} s Optional scope to execute the function in.
  3359. */
  3360. function walk(o, f, n, s) {
  3361. s = s || this;
  3362. if (o) {
  3363. if (n) {
  3364. o = o[n];
  3365. }
  3366. Arr.each(o, function(o, i) {
  3367. if (f.call(s, o, i, n) === false) {
  3368. return false;
  3369. }
  3370. walk(o, f, n, s);
  3371. });
  3372. }
  3373. }
  3374. /**
  3375. * Creates a namespace on a specific object.
  3376. *
  3377. * @method createNS
  3378. * @param {String} n Namespace to create for example a.b.c.d.
  3379. * @param {Object} o Optional object to add namespace to, defaults to window.
  3380. * @return {Object} New namespace object the last item in path.
  3381. * @example
  3382. * // Create some namespace
  3383. * tinymce.createNS('tinymce.somepackage.subpackage');
  3384. *
  3385. * // Add a singleton
  3386. * var tinymce.somepackage.subpackage.SomeSingleton = {
  3387. * method: function() {
  3388. * // Some method
  3389. * }
  3390. * };
  3391. */
  3392. function createNS(n, o) {
  3393. var i, v;
  3394. o = o || window;
  3395. n = n.split('.');
  3396. for (i = 0; i < n.length; i++) {
  3397. v = n[i];
  3398. if (!o[v]) {
  3399. o[v] = {};
  3400. }
  3401. o = o[v];
  3402. }
  3403. return o;
  3404. }
  3405. /**
  3406. * Resolves a string and returns the object from a specific structure.
  3407. *
  3408. * @method resolve
  3409. * @param {String} n Path to resolve for example a.b.c.d.
  3410. * @param {Object} o Optional object to search though, defaults to window.
  3411. * @return {Object} Last object in path or null if it couldn't be resolved.
  3412. * @example
  3413. * // Resolve a path into an object reference
  3414. * var obj = tinymce.resolve('a.b.c.d');
  3415. */
  3416. function resolve(n, o) {
  3417. var i, l;
  3418. o = o || window;
  3419. n = n.split('.');
  3420. for (i = 0, l = n.length; i < l; i++) {
  3421. o = o[n[i]];
  3422. if (!o) {
  3423. break;
  3424. }
  3425. }
  3426. return o;
  3427. }
  3428. /**
  3429. * Splits a string but removes the whitespace before and after each value.
  3430. *
  3431. * @method explode
  3432. * @param {string} s String to split.
  3433. * @param {string} d Delimiter to split by.
  3434. * @example
  3435. * // Split a string into an array with a,b,c
  3436. * var arr = tinymce.explode('a, b, c');
  3437. */
  3438. function explode(s, d) {
  3439. if (!s || is(s, 'array')) {
  3440. return s;
  3441. }
  3442. return Arr.map(s.split(d || ','), trim);
  3443. }
  3444. function _addCacheSuffix(url) {
  3445. var cacheSuffix = Env.cacheSuffix;
  3446. if (cacheSuffix) {
  3447. url += (url.indexOf('?') === -1 ? '?' : '&') + cacheSuffix;
  3448. }
  3449. return url;
  3450. }
  3451. return {
  3452. trim: trim,
  3453. /**
  3454. * Returns true/false if the object is an array or not.
  3455. *
  3456. * @method isArray
  3457. * @param {Object} obj Object to check.
  3458. * @return {boolean} true/false state if the object is an array or not.
  3459. */
  3460. isArray: Arr.isArray,
  3461. is: is,
  3462. /**
  3463. * Converts the specified object into a real JavaScript array.
  3464. *
  3465. * @method toArray
  3466. * @param {Object} obj Object to convert into array.
  3467. * @return {Array} Array object based in input.
  3468. */
  3469. toArray: Arr.toArray,
  3470. makeMap: makeMap,
  3471. /**
  3472. * Performs an iteration of all items in a collection such as an object or array. This method will execure the
  3473. * callback function for each item in the collection, if the callback returns false the iteration will terminate.
  3474. * The callback has the following format: cb(value, key_or_index).
  3475. *
  3476. * @method each
  3477. * @param {Object} o Collection to iterate.
  3478. * @param {function} cb Callback function to execute for each item.
  3479. * @param {Object} s Optional scope to execute the callback in.
  3480. * @example
  3481. * // Iterate an array
  3482. * tinymce.each([1,2,3], function(v, i) {
  3483. * console.debug("Value: " + v + ", Index: " + i);
  3484. * });
  3485. *
  3486. * // Iterate an object
  3487. * tinymce.each({a: 1, b: 2, c: 3], function(v, k) {
  3488. * console.debug("Value: " + v + ", Key: " + k);
  3489. * });
  3490. */
  3491. each: Arr.each,
  3492. /**
  3493. * Creates a new array by the return value of each iteration function call. This enables you to convert
  3494. * one array list into another.
  3495. *
  3496. * @method map
  3497. * @param {Array} array Array of items to iterate.
  3498. * @param {function} callback Function to call for each item. It's return value will be the new value.
  3499. * @return {Array} Array with new values based on function return values.
  3500. */
  3501. map: Arr.map,
  3502. /**
  3503. * Filters out items from the input array by calling the specified function for each item.
  3504. * If the function returns false the item will be excluded if it returns true it will be included.
  3505. *
  3506. * @method grep
  3507. * @param {Array} a Array of items to loop though.
  3508. * @param {function} f Function to call for each item. Include/exclude depends on it's return value.
  3509. * @return {Array} New array with values imported and filtered based in input.
  3510. * @example
  3511. * // Filter out some items, this will return an array with 4 and 5
  3512. * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;});
  3513. */
  3514. grep: Arr.filter,
  3515. /**
  3516. * Returns true/false if the object is an array or not.
  3517. *
  3518. * @method isArray
  3519. * @param {Object} obj Object to check.
  3520. * @return {boolean} true/false state if the object is an array or not.
  3521. */
  3522. inArray: Arr.indexOf,
  3523. extend: extend,
  3524. create: create,
  3525. walk: walk,
  3526. createNS: createNS,
  3527. resolve: resolve,
  3528. explode: explode,
  3529. _addCacheSuffix: _addCacheSuffix
  3530. };
  3531. });
  3532. // Included from: js/tinymce/classes/dom/DomQuery.js
  3533. /**
  3534. * DomQuery.js
  3535. *
  3536. * Released under LGPL License.
  3537. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  3538. *
  3539. * License: http://www.tinymce.com/license
  3540. * Contributing: http://www.tinymce.com/contributing
  3541. */
  3542. /**
  3543. * This class mimics most of the jQuery API:
  3544. *
  3545. * This is whats currently implemented:
  3546. * - Utility functions
  3547. * - DOM traversial
  3548. * - DOM manipulation
  3549. * - Event binding
  3550. *
  3551. * This is not currently implemented:
  3552. * - Dimension
  3553. * - Ajax
  3554. * - Animation
  3555. * - Advanced chaining
  3556. *
  3557. * @example
  3558. * var $ = tinymce.dom.DomQuery;
  3559. * $('p').attr('attr', 'value').addClass('class');
  3560. *
  3561. * @class tinymce.dom.DomQuery
  3562. */
  3563. define("tinymce/dom/DomQuery", [
  3564. "tinymce/dom/EventUtils",
  3565. "tinymce/dom/Sizzle",
  3566. "tinymce/util/Tools",
  3567. "tinymce/Env"
  3568. ], function(EventUtils, Sizzle, Tools, Env) {
  3569. var doc = document, push = Array.prototype.push, slice = Array.prototype.slice;
  3570. var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
  3571. var Event = EventUtils.Event, undef;
  3572. var skipUniques = Tools.makeMap('children,contents,next,prev');
  3573. function isDefined(obj) {
  3574. return typeof obj !== 'undefined';
  3575. }
  3576. function isString(obj) {
  3577. return typeof obj === 'string';
  3578. }
  3579. function isWindow(obj) {
  3580. return obj && obj == obj.window;
  3581. }
  3582. function createFragment(html, fragDoc) {
  3583. var frag, node, container;
  3584. fragDoc = fragDoc || doc;
  3585. container = fragDoc.createElement('div');
  3586. frag = fragDoc.createDocumentFragment();
  3587. container.innerHTML = html;
  3588. while ((node = container.firstChild)) {
  3589. frag.appendChild(node);
  3590. }
  3591. return frag;
  3592. }
  3593. function domManipulate(targetNodes, sourceItem, callback, reverse) {
  3594. var i;
  3595. if (isString(sourceItem)) {
  3596. sourceItem = createFragment(sourceItem, getElementDocument(targetNodes[0]));
  3597. } else if (sourceItem.length && !sourceItem.nodeType) {
  3598. sourceItem = DomQuery.makeArray(sourceItem);
  3599. if (reverse) {
  3600. for (i = sourceItem.length - 1; i >= 0; i--) {
  3601. domManipulate(targetNodes, sourceItem[i], callback, reverse);
  3602. }
  3603. } else {
  3604. for (i = 0; i < sourceItem.length; i++) {
  3605. domManipulate(targetNodes, sourceItem[i], callback, reverse);
  3606. }
  3607. }
  3608. return targetNodes;
  3609. }
  3610. if (sourceItem.nodeType) {
  3611. i = targetNodes.length;
  3612. while (i--) {
  3613. callback.call(targetNodes[i], sourceItem);
  3614. }
  3615. }
  3616. return targetNodes;
  3617. }
  3618. function hasClass(node, className) {
  3619. return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1;
  3620. }
  3621. function wrap(elements, wrapper, all) {
  3622. var lastParent, newWrapper;
  3623. wrapper = DomQuery(wrapper)[0];
  3624. elements.each(function() {
  3625. var self = this;
  3626. if (!all || lastParent != self.parentNode) {
  3627. lastParent = self.parentNode;
  3628. newWrapper = wrapper.cloneNode(false);
  3629. self.parentNode.insertBefore(newWrapper, self);
  3630. newWrapper.appendChild(self);
  3631. } else {
  3632. newWrapper.appendChild(self);
  3633. }
  3634. });
  3635. return elements;
  3636. }
  3637. var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
  3638. var booleanMap = Tools.makeMap('checked compact declare defer disabled ismap multiple nohref noshade nowrap readonly selected', ' ');
  3639. var propFix = {
  3640. 'for': 'htmlFor',
  3641. 'class': 'className',
  3642. 'readonly': 'readOnly'
  3643. };
  3644. var cssFix = {
  3645. 'float': 'cssFloat'
  3646. };
  3647. var attrHooks = {}, cssHooks = {};
  3648. function DomQuery(selector, context) {
  3649. /*eslint new-cap:0 */
  3650. return new DomQuery.fn.init(selector, context);
  3651. }
  3652. function inArray(item, array) {
  3653. var i;
  3654. if (array.indexOf) {
  3655. return array.indexOf(item);
  3656. }
  3657. i = array.length;
  3658. while (i--) {
  3659. if (array[i] === item) {
  3660. return i;
  3661. }
  3662. }
  3663. return -1;
  3664. }
  3665. var whiteSpaceRegExp = /^\s*|\s*$/g;
  3666. function trim(str) {
  3667. return (str === null || str === undef) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
  3668. }
  3669. function each(obj, callback) {
  3670. var length, key, i, undef, value;
  3671. if (obj) {
  3672. length = obj.length;
  3673. if (length === undef) {
  3674. // Loop object items
  3675. for (key in obj) {
  3676. if (obj.hasOwnProperty(key)) {
  3677. value = obj[key];
  3678. if (callback.call(value, key, value) === false) {
  3679. break;
  3680. }
  3681. }
  3682. }
  3683. } else {
  3684. // Loop array items
  3685. for (i = 0; i < length; i++) {
  3686. value = obj[i];
  3687. if (callback.call(value, i, value) === false) {
  3688. break;
  3689. }
  3690. }
  3691. }
  3692. }
  3693. return obj;
  3694. }
  3695. function grep(array, callback) {
  3696. var out = [];
  3697. each(array, function(i, item) {
  3698. if (callback(item, i)) {
  3699. out.push(item);
  3700. }
  3701. });
  3702. return out;
  3703. }
  3704. function getElementDocument(element) {
  3705. if (!element) {
  3706. return doc;
  3707. }
  3708. if (element.nodeType == 9) {
  3709. return element;
  3710. }
  3711. return element.ownerDocument;
  3712. }
  3713. DomQuery.fn = DomQuery.prototype = {
  3714. constructor: DomQuery,
  3715. /**
  3716. * Selector for the current set.
  3717. *
  3718. * @property selector
  3719. * @type String
  3720. */
  3721. selector: "",
  3722. /**
  3723. * Context used to create the set.
  3724. *
  3725. * @property context
  3726. * @type Element
  3727. */
  3728. context: null,
  3729. /**
  3730. * Number of items in the current set.
  3731. *
  3732. * @property length
  3733. * @type Number
  3734. */
  3735. length: 0,
  3736. /**
  3737. * Constructs a new DomQuery instance with the specified selector or context.
  3738. *
  3739. * @constructor
  3740. * @method init
  3741. * @param {String/Array/DomQuery} selector Optional CSS selector/Array or array like object or HTML string.
  3742. * @param {Document/Element} context Optional context to search in.
  3743. */
  3744. init: function(selector, context) {
  3745. var self = this, match, node;
  3746. if (!selector) {
  3747. return self;
  3748. }
  3749. if (selector.nodeType) {
  3750. self.context = self[0] = selector;
  3751. self.length = 1;
  3752. return self;
  3753. }
  3754. if (context && context.nodeType) {
  3755. self.context = context;
  3756. } else {
  3757. if (context) {
  3758. return DomQuery(selector).attr(context);
  3759. }
  3760. self.context = context = document;
  3761. }
  3762. if (isString(selector)) {
  3763. self.selector = selector;
  3764. if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
  3765. match = [null, selector, null];
  3766. } else {
  3767. match = rquickExpr.exec(selector);
  3768. }
  3769. if (match) {
  3770. if (match[1]) {
  3771. node = createFragment(selector, getElementDocument(context)).firstChild;
  3772. while (node) {
  3773. push.call(self, node);
  3774. node = node.nextSibling;
  3775. }
  3776. } else {
  3777. node = getElementDocument(context).getElementById(match[2]);
  3778. if (!node) {
  3779. return self;
  3780. }
  3781. if (node.id !== match[2]) {
  3782. return self.find(selector);
  3783. }
  3784. self.length = 1;
  3785. self[0] = node;
  3786. }
  3787. } else {
  3788. return DomQuery(context).find(selector);
  3789. }
  3790. } else {
  3791. this.add(selector, false);
  3792. }
  3793. return self;
  3794. },
  3795. /**
  3796. * Converts the current set to an array.
  3797. *
  3798. * @method toArray
  3799. * @return {Array} Array of all nodes in set.
  3800. */
  3801. toArray: function() {
  3802. return Tools.toArray(this);
  3803. },
  3804. /**
  3805. * Adds new nodes to the set.
  3806. *
  3807. * @method add
  3808. * @param {Array/tinymce.dom.DomQuery} items Array of all nodes to add to set.
  3809. * @param {Boolean} sort Optional sort flag that enables sorting of elements.
  3810. * @return {tinymce.dom.DomQuery} New instance with nodes added.
  3811. */
  3812. add: function(items, sort) {
  3813. var self = this, nodes, i;
  3814. if (isString(items)) {
  3815. return self.add(DomQuery(items));
  3816. }
  3817. if (sort !== false) {
  3818. nodes = DomQuery.unique(self.toArray().concat(DomQuery.makeArray(items)));
  3819. self.length = nodes.length;
  3820. for (i = 0; i < nodes.length; i++) {
  3821. self[i] = nodes[i];
  3822. }
  3823. } else {
  3824. push.apply(self, DomQuery.makeArray(items));
  3825. }
  3826. return self;
  3827. },
  3828. /**
  3829. * Sets/gets attributes on the elements in the current set.
  3830. *
  3831. * @method attr
  3832. * @param {String/Object} name Name of attribute to get or an object with attributes to set.
  3833. * @param {String} value Optional value to set.
  3834. * @return {tinymce.dom.DomQuery/String} Current set or the specified attribute when only the name is specified.
  3835. */
  3836. attr: function(name, value) {
  3837. var self = this, hook;
  3838. if (typeof name === "object") {
  3839. each(name, function(name, value) {
  3840. self.attr(name, value);
  3841. });
  3842. } else if (isDefined(value)) {
  3843. this.each(function() {
  3844. var hook;
  3845. if (this.nodeType === 1) {
  3846. hook = attrHooks[name];
  3847. if (hook && hook.set) {
  3848. hook.set(this, value);
  3849. return;
  3850. }
  3851. if (value === null) {
  3852. this.removeAttribute(name, 2);
  3853. } else {
  3854. this.setAttribute(name, value, 2);
  3855. }
  3856. }
  3857. });
  3858. } else {
  3859. if (self[0] && self[0].nodeType === 1) {
  3860. hook = attrHooks[name];
  3861. if (hook && hook.get) {
  3862. return hook.get(self[0], name);
  3863. }
  3864. if (booleanMap[name]) {
  3865. return self.prop(name) ? name : undef;
  3866. }
  3867. value = self[0].getAttribute(name, 2);
  3868. if (value === null) {
  3869. value = undef;
  3870. }
  3871. }
  3872. return value;
  3873. }
  3874. return self;
  3875. },
  3876. /**
  3877. * Removes attributse on the elements in the current set.
  3878. *
  3879. * @method removeAttr
  3880. * @param {String/Object} name Name of attribute to remove.
  3881. * @return {tinymce.dom.DomQuery/String} Current set.
  3882. */
  3883. removeAttr: function(name) {
  3884. return this.attr(name, null);
  3885. },
  3886. /**
  3887. * Sets/gets properties on the elements in the current set.
  3888. *
  3889. * @method attr
  3890. * @param {String/Object} name Name of property to get or an object with properties to set.
  3891. * @param {String} value Optional value to set.
  3892. * @return {tinymce.dom.DomQuery/String} Current set or the specified property when only the name is specified.
  3893. */
  3894. prop: function(name, value) {
  3895. var self = this;
  3896. name = propFix[name] || name;
  3897. if (typeof name === "object") {
  3898. each(name, function(name, value) {
  3899. self.prop(name, value);
  3900. });
  3901. } else if (isDefined(value)) {
  3902. this.each(function() {
  3903. if (this.nodeType == 1) {
  3904. this[name] = value;
  3905. }
  3906. });
  3907. } else {
  3908. if (self[0] && self[0].nodeType && name in self[0]) {
  3909. return self[0][name];
  3910. }
  3911. return value;
  3912. }
  3913. return self;
  3914. },
  3915. /**
  3916. * Sets/gets styles on the elements in the current set.
  3917. *
  3918. * @method css
  3919. * @param {String/Object} name Name of style to get or an object with styles to set.
  3920. * @param {String} value Optional value to set.
  3921. * @return {tinymce.dom.DomQuery/String} Current set or the specified style when only the name is specified.
  3922. */
  3923. css: function(name, value) {
  3924. var self = this, elm, hook;
  3925. function camel(name) {
  3926. return name.replace(/-(\D)/g, function(a, b) {
  3927. return b.toUpperCase();
  3928. });
  3929. }
  3930. function dashed(name) {
  3931. return name.replace(/[A-Z]/g, function(a) {
  3932. return '-' + a;
  3933. });
  3934. }
  3935. if (typeof name === "object") {
  3936. each(name, function(name, value) {
  3937. self.css(name, value);
  3938. });
  3939. } else {
  3940. if (isDefined(value)) {
  3941. name = camel(name);
  3942. // Default px suffix on these
  3943. if (typeof value === 'number' && !numericCssMap[name]) {
  3944. value += 'px';
  3945. }
  3946. self.each(function() {
  3947. var style = this.style;
  3948. hook = cssHooks[name];
  3949. if (hook && hook.set) {
  3950. hook.set(this, value);
  3951. return;
  3952. }
  3953. try {
  3954. this.style[cssFix[name] || name] = value;
  3955. } catch (ex) {
  3956. // Ignore
  3957. }
  3958. if (value === null || value === '') {
  3959. if (style.removeProperty) {
  3960. style.removeProperty(dashed(name));
  3961. } else {
  3962. style.removeAttribute(name);
  3963. }
  3964. }
  3965. });
  3966. } else {
  3967. elm = self[0];
  3968. hook = cssHooks[name];
  3969. if (hook && hook.get) {
  3970. return hook.get(elm);
  3971. }
  3972. if (elm.ownerDocument.defaultView) {
  3973. try {
  3974. return elm.ownerDocument.defaultView.getComputedStyle(elm, null).getPropertyValue(dashed(name));
  3975. } catch (ex) {
  3976. return undef;
  3977. }
  3978. } else if (elm.currentStyle) {
  3979. return elm.currentStyle[camel(name)];
  3980. }
  3981. }
  3982. }
  3983. return self;
  3984. },
  3985. /**
  3986. * Removes all nodes in set from the document.
  3987. *
  3988. * @method remove
  3989. * @return {tinymce.dom.DomQuery} Current set with the removed nodes.
  3990. */
  3991. remove: function() {
  3992. var self = this, node, i = this.length;
  3993. while (i--) {
  3994. node = self[i];
  3995. Event.clean(node);
  3996. if (node.parentNode) {
  3997. node.parentNode.removeChild(node);
  3998. }
  3999. }
  4000. return this;
  4001. },
  4002. /**
  4003. * Empties all elements in set.
  4004. *
  4005. * @method empty
  4006. * @return {tinymce.dom.DomQuery} Current set with the empty nodes.
  4007. */
  4008. empty: function() {
  4009. var self = this, node, i = this.length;
  4010. while (i--) {
  4011. node = self[i];
  4012. while (node.firstChild) {
  4013. node.removeChild(node.firstChild);
  4014. }
  4015. }
  4016. return this;
  4017. },
  4018. /**
  4019. * Sets or gets the HTML of the current set or first set node.
  4020. *
  4021. * @method html
  4022. * @param {String} value Optional innerHTML value to set on each element.
  4023. * @return {tinymce.dom.DomQuery/String} Current set or the innerHTML of the first element.
  4024. */
  4025. html: function(value) {
  4026. var self = this, i;
  4027. if (isDefined(value)) {
  4028. i = self.length;
  4029. try {
  4030. while (i--) {
  4031. self[i].innerHTML = value;
  4032. }
  4033. } catch (ex) {
  4034. // Workaround for "Unknown runtime error" when DIV is added to P on IE
  4035. DomQuery(self[i]).empty().append(value);
  4036. }
  4037. return self;
  4038. }
  4039. return self[0] ? self[0].innerHTML : '';
  4040. },
  4041. /**
  4042. * Sets or gets the text of the current set or first set node.
  4043. *
  4044. * @method text
  4045. * @param {String} value Optional innerText value to set on each element.
  4046. * @return {tinymce.dom.DomQuery/String} Current set or the innerText of the first element.
  4047. */
  4048. text: function(value) {
  4049. var self = this, i;
  4050. if (isDefined(value)) {
  4051. i = self.length;
  4052. while (i--) {
  4053. if ("innerText" in self[i]) {
  4054. self[i].innerText = value;
  4055. } else {
  4056. self[0].textContent = value;
  4057. }
  4058. }
  4059. return self;
  4060. }
  4061. return self[0] ? (self[0].innerText || self[0].textContent) : '';
  4062. },
  4063. /**
  4064. * Appends the specified node/html or node set to the current set nodes.
  4065. *
  4066. * @method append
  4067. * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to append to each element in set.
  4068. * @return {tinymce.dom.DomQuery} Current set.
  4069. */
  4070. append: function() {
  4071. return domManipulate(this, arguments, function(node) {
  4072. // Either element or Shadow Root
  4073. if (this.nodeType === 1 || (this.host && this.host.nodeType === 1)) {
  4074. this.appendChild(node);
  4075. }
  4076. });
  4077. },
  4078. /**
  4079. * Prepends the specified node/html or node set to the current set nodes.
  4080. *
  4081. * @method prepend
  4082. * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to prepend to each element in set.
  4083. * @return {tinymce.dom.DomQuery} Current set.
  4084. */
  4085. prepend: function() {
  4086. return domManipulate(this, arguments, function(node) {
  4087. // Either element or Shadow Root
  4088. if (this.nodeType === 1 || (this.host && this.host.nodeType === 1)) {
  4089. this.insertBefore(node, this.firstChild);
  4090. }
  4091. }, true);
  4092. },
  4093. /**
  4094. * Adds the specified elements before current set nodes.
  4095. *
  4096. * @method before
  4097. * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add before to each element in set.
  4098. * @return {tinymce.dom.DomQuery} Current set.
  4099. */
  4100. before: function() {
  4101. var self = this;
  4102. if (self[0] && self[0].parentNode) {
  4103. return domManipulate(self, arguments, function(node) {
  4104. this.parentNode.insertBefore(node, this);
  4105. });
  4106. }
  4107. return self;
  4108. },
  4109. /**
  4110. * Adds the specified elements after current set nodes.
  4111. *
  4112. * @method after
  4113. * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add after to each element in set.
  4114. * @return {tinymce.dom.DomQuery} Current set.
  4115. */
  4116. after: function() {
  4117. var self = this;
  4118. if (self[0] && self[0].parentNode) {
  4119. return domManipulate(self, arguments, function(node) {
  4120. this.parentNode.insertBefore(node, this.nextSibling);
  4121. }, true);
  4122. }
  4123. return self;
  4124. },
  4125. /**
  4126. * Appends the specified set nodes to the specified selector/instance.
  4127. *
  4128. * @method appendTo
  4129. * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to append the current set to.
  4130. * @return {tinymce.dom.DomQuery} Current set with the appended nodes.
  4131. */
  4132. appendTo: function(val) {
  4133. DomQuery(val).append(this);
  4134. return this;
  4135. },
  4136. /**
  4137. * Prepends the specified set nodes to the specified selector/instance.
  4138. *
  4139. * @method prependTo
  4140. * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to prepend the current set to.
  4141. * @return {tinymce.dom.DomQuery} Current set with the prepended nodes.
  4142. */
  4143. prependTo: function(val) {
  4144. DomQuery(val).prepend(this);
  4145. return this;
  4146. },
  4147. /**
  4148. * Replaces the nodes in set with the specified content.
  4149. *
  4150. * @method replaceWith
  4151. * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to replace nodes with.
  4152. * @return {tinymce.dom.DomQuery} Set with replaced nodes.
  4153. */
  4154. replaceWith: function(content) {
  4155. return this.before(content).remove();
  4156. },
  4157. /**
  4158. * Wraps all elements in set with the specified wrapper.
  4159. *
  4160. * @method wrap
  4161. * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
  4162. * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
  4163. */
  4164. wrap: function(content) {
  4165. return wrap(this, content);
  4166. },
  4167. /**
  4168. * Wraps all nodes in set with the specified wrapper. If the nodes are siblings all of them
  4169. * will be wrapped in the same wrapper.
  4170. *
  4171. * @method wrapAll
  4172. * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
  4173. * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
  4174. */
  4175. wrapAll: function(content) {
  4176. return wrap(this, content, true);
  4177. },
  4178. /**
  4179. * Wraps all elements inner contents in set with the specified wrapper.
  4180. *
  4181. * @method wrapInner
  4182. * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
  4183. * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
  4184. */
  4185. wrapInner: function(content) {
  4186. this.each(function() {
  4187. DomQuery(this).contents().wrapAll(content);
  4188. });
  4189. return this;
  4190. },
  4191. /**
  4192. * Unwraps all elements by removing the parent element of each item in set.
  4193. *
  4194. * @method unwrap
  4195. * @return {tinymce.dom.DomQuery} Set with unwrapped nodes.
  4196. */
  4197. unwrap: function() {
  4198. return this.parent().each(function() {
  4199. DomQuery(this).replaceWith(this.childNodes);
  4200. });
  4201. },
  4202. /**
  4203. * Clones all nodes in set.
  4204. *
  4205. * @method clone
  4206. * @return {tinymce.dom.DomQuery} Set with cloned nodes.
  4207. */
  4208. clone: function() {
  4209. var result = [];
  4210. this.each(function() {
  4211. result.push(this.cloneNode(true));
  4212. });
  4213. return DomQuery(result);
  4214. },
  4215. /**
  4216. * Adds the specified class name to the current set elements.
  4217. *
  4218. * @method addClass
  4219. * @param {String} className Class name to add.
  4220. * @return {tinymce.dom.DomQuery} Current set.
  4221. */
  4222. addClass: function(className) {
  4223. return this.toggleClass(className, true);
  4224. },
  4225. /**
  4226. * Removes the specified class name to the current set elements.
  4227. *
  4228. * @method removeClass
  4229. * @param {String} className Class name to remove.
  4230. * @return {tinymce.dom.DomQuery} Current set.
  4231. */
  4232. removeClass: function(className) {
  4233. return this.toggleClass(className, false);
  4234. },
  4235. /**
  4236. * Toggles the specified class name on the current set elements.
  4237. *
  4238. * @method toggleClass
  4239. * @param {String} className Class name to add/remove.
  4240. * @param {Boolean} state Optional state to toggle on/off.
  4241. * @return {tinymce.dom.DomQuery} Current set.
  4242. */
  4243. toggleClass: function(className, state) {
  4244. var self = this;
  4245. // Functions are not supported
  4246. if (typeof className != 'string') {
  4247. return self;
  4248. }
  4249. if (className.indexOf(' ') !== -1) {
  4250. each(className.split(' '), function() {
  4251. self.toggleClass(this, state);
  4252. });
  4253. } else {
  4254. self.each(function(index, node) {
  4255. var existingClassName, classState;
  4256. classState = hasClass(node, className);
  4257. if (classState !== state) {
  4258. existingClassName = node.className;
  4259. if (classState) {
  4260. node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' '));
  4261. } else {
  4262. node.className += existingClassName ? ' ' + className : className;
  4263. }
  4264. }
  4265. });
  4266. }
  4267. return self;
  4268. },
  4269. /**
  4270. * Returns true/false if the first item in set has the specified class.
  4271. *
  4272. * @method hasClass
  4273. * @param {String} className Class name to check for.
  4274. * @return {Boolean} True/false if the set has the specified class.
  4275. */
  4276. hasClass: function(className) {
  4277. return hasClass(this[0], className);
  4278. },
  4279. /**
  4280. * Executes the callback function for each item DomQuery collection. If you return false in the
  4281. * callback it will break the loop.
  4282. *
  4283. * @method each
  4284. * @param {function} callback Callback function to execute for each item.
  4285. * @return {tinymce.dom.DomQuery} Current set.
  4286. */
  4287. each: function(callback) {
  4288. return each(this, callback);
  4289. },
  4290. /**
  4291. * Binds an event with callback function to the elements in set.
  4292. *
  4293. * @method on
  4294. * @param {String} name Name of the event to bind.
  4295. * @param {function} callback Callback function to execute when the event occurs.
  4296. * @return {tinymce.dom.DomQuery} Current set.
  4297. */
  4298. on: function(name, callback) {
  4299. return this.each(function() {
  4300. Event.bind(this, name, callback);
  4301. });
  4302. },
  4303. /**
  4304. * Unbinds an event with callback function to the elements in set.
  4305. *
  4306. * @method off
  4307. * @param {String} name Optional name of the event to bind.
  4308. * @param {function} callback Optional callback function to execute when the event occurs.
  4309. * @return {tinymce.dom.DomQuery} Current set.
  4310. */
  4311. off: function(name, callback) {
  4312. return this.each(function() {
  4313. Event.unbind(this, name, callback);
  4314. });
  4315. },
  4316. /**
  4317. * Triggers the specified event by name or event object.
  4318. *
  4319. * @method trigger
  4320. * @param {String/Object} name Name of the event to trigger or event object.
  4321. * @return {tinymce.dom.DomQuery} Current set.
  4322. */
  4323. trigger: function(name) {
  4324. return this.each(function() {
  4325. if (typeof name == 'object') {
  4326. Event.fire(this, name.type, name);
  4327. } else {
  4328. Event.fire(this, name);
  4329. }
  4330. });
  4331. },
  4332. /**
  4333. * Shows all elements in set.
  4334. *
  4335. * @method show
  4336. * @return {tinymce.dom.DomQuery} Current set.
  4337. */
  4338. show: function() {
  4339. return this.css('display', '');
  4340. },
  4341. /**
  4342. * Hides all elements in set.
  4343. *
  4344. * @method hide
  4345. * @return {tinymce.dom.DomQuery} Current set.
  4346. */
  4347. hide: function() {
  4348. return this.css('display', 'none');
  4349. },
  4350. /**
  4351. * Slices the current set.
  4352. *
  4353. * @method slice
  4354. * @param {Number} start Start index to slice at.
  4355. * @param {Number} end Optional end index to end slice at.
  4356. * @return {tinymce.dom.DomQuery} Sliced set.
  4357. */
  4358. slice: function() {
  4359. return new DomQuery(slice.apply(this, arguments));
  4360. },
  4361. /**
  4362. * Makes the set equal to the specified index.
  4363. *
  4364. * @method eq
  4365. * @param {Number} index Index to set it equal to.
  4366. * @return {tinymce.dom.DomQuery} Single item set.
  4367. */
  4368. eq: function(index) {
  4369. return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
  4370. },
  4371. /**
  4372. * Makes the set equal to first element in set.
  4373. *
  4374. * @method first
  4375. * @return {tinymce.dom.DomQuery} Single item set.
  4376. */
  4377. first: function() {
  4378. return this.eq(0);
  4379. },
  4380. /**
  4381. * Makes the set equal to last element in set.
  4382. *
  4383. * @method last
  4384. * @return {tinymce.dom.DomQuery} Single item set.
  4385. */
  4386. last: function() {
  4387. return this.eq(-1);
  4388. },
  4389. /**
  4390. * Finds elements by the specified selector for each element in set.
  4391. *
  4392. * @method find
  4393. * @param {String} selector Selector to find elements by.
  4394. * @return {tinymce.dom.DomQuery} Set with matches elements.
  4395. */
  4396. find: function(selector) {
  4397. var i, l, ret = [];
  4398. for (i = 0, l = this.length; i < l; i++) {
  4399. DomQuery.find(selector, this[i], ret);
  4400. }
  4401. return DomQuery(ret);
  4402. },
  4403. /**
  4404. * Filters the current set with the specified selector.
  4405. *
  4406. * @method filter
  4407. * @param {String/function} selector Selector to filter elements by.
  4408. * @return {tinymce.dom.DomQuery} Set with filtered elements.
  4409. */
  4410. filter: function(selector) {
  4411. if (typeof selector == 'function') {
  4412. return DomQuery(grep(this.toArray(), function(item, i) {
  4413. return selector(i, item);
  4414. }));
  4415. }
  4416. return DomQuery(DomQuery.filter(selector, this.toArray()));
  4417. },
  4418. /**
  4419. * Gets the current node or any parent matching the specified selector.
  4420. *
  4421. * @method closest
  4422. * @param {String/Element/tinymce.dom.DomQuery} selector Selector or element to find.
  4423. * @return {tinymce.dom.DomQuery} Set with closest elements.
  4424. */
  4425. closest: function(selector) {
  4426. var result = [];
  4427. if (selector instanceof DomQuery) {
  4428. selector = selector[0];
  4429. }
  4430. this.each(function(i, node) {
  4431. while (node) {
  4432. if (typeof selector == 'string' && DomQuery(node).is(selector)) {
  4433. result.push(node);
  4434. break;
  4435. } else if (node == selector) {
  4436. result.push(node);
  4437. break;
  4438. }
  4439. node = node.parentNode;
  4440. }
  4441. });
  4442. return DomQuery(result);
  4443. },
  4444. /**
  4445. * Returns the offset of the first element in set or sets the top/left css properties of all elements in set.
  4446. *
  4447. * @method offset
  4448. * @param {Object} offset Optional offset object to set on each item.
  4449. * @return {Object/tinymce.dom.DomQuery} Returns the first element offset or the current set if you specified an offset.
  4450. */
  4451. offset: function(offset) {
  4452. var elm, doc, docElm;
  4453. var x = 0, y = 0, pos;
  4454. if (!offset) {
  4455. elm = this[0];
  4456. if (elm) {
  4457. doc = elm.ownerDocument;
  4458. docElm = doc.documentElement;
  4459. if (elm.getBoundingClientRect) {
  4460. pos = elm.getBoundingClientRect();
  4461. x = pos.left + (docElm.scrollLeft || doc.body.scrollLeft) - docElm.clientLeft;
  4462. y = pos.top + (docElm.scrollTop || doc.body.scrollTop) - docElm.clientTop;
  4463. }
  4464. }
  4465. return {
  4466. left: x,
  4467. top: y
  4468. };
  4469. }
  4470. return this.css(offset);
  4471. },
  4472. push: push,
  4473. sort: [].sort,
  4474. splice: [].splice
  4475. };
  4476. // Static members
  4477. Tools.extend(DomQuery, {
  4478. /**
  4479. * Extends the specified object with one or more objects.
  4480. *
  4481. * @static
  4482. * @method extend
  4483. * @param {Object} target Target object to extend with new items.
  4484. * @param {Object..} object Object to extend the target with.
  4485. * @return {Object} Extended input object.
  4486. */
  4487. extend: Tools.extend,
  4488. /**
  4489. * Creates an array out of an array like object.
  4490. *
  4491. * @static
  4492. * @method makeArray
  4493. * @param {Object} object Object to convert to array.
  4494. * @return {Array} Array produced from object.
  4495. */
  4496. makeArray: function(object) {
  4497. if (isWindow(object) || object.nodeType) {
  4498. return [object];
  4499. }
  4500. return Tools.toArray(object);
  4501. },
  4502. /**
  4503. * Returns the index of the specified item inside the array.
  4504. *
  4505. * @static
  4506. * @method inArray
  4507. * @param {Object} item Item to look for.
  4508. * @param {Array} array Array to look for item in.
  4509. * @return {Number} Index of the item or -1.
  4510. */
  4511. inArray: inArray,
  4512. /**
  4513. * Returns true/false if the specified object is an array or not.
  4514. *
  4515. * @static
  4516. * @method isArray
  4517. * @param {Object} array Object to check if it's an array or not.
  4518. * @return {Boolean} True/false if the object is an array.
  4519. */
  4520. isArray: Tools.isArray,
  4521. /**
  4522. * Executes the callback function for each item in array/object. If you return false in the
  4523. * callback it will break the loop.
  4524. *
  4525. * @static
  4526. * @method each
  4527. * @param {Object} obj Object to iterate.
  4528. * @param {function} callback Callback function to execute for each item.
  4529. */
  4530. each: each,
  4531. /**
  4532. * Removes whitespace from the beginning and end of a string.
  4533. *
  4534. * @static
  4535. * @method trim
  4536. * @param {String} str String to remove whitespace from.
  4537. * @return {String} New string with removed whitespace.
  4538. */
  4539. trim: trim,
  4540. /**
  4541. * Filters out items from the input array by calling the specified function for each item.
  4542. * If the function returns false the item will be excluded if it returns true it will be included.
  4543. *
  4544. * @static
  4545. * @method grep
  4546. * @param {Array} array Array of items to loop though.
  4547. * @param {function} callback Function to call for each item. Include/exclude depends on it's return value.
  4548. * @return {Array} New array with values imported and filtered based in input.
  4549. * @example
  4550. * // Filter out some items, this will return an array with 4 and 5
  4551. * var items = DomQuery.grep([1, 2, 3, 4, 5], function(v) {return v > 3;});
  4552. */
  4553. grep: grep,
  4554. // Sizzle
  4555. find: Sizzle,
  4556. expr: Sizzle.selectors,
  4557. unique: Sizzle.uniqueSort,
  4558. text: Sizzle.getText,
  4559. contains: Sizzle.contains,
  4560. filter: function(expr, elems, not) {
  4561. var i = elems.length;
  4562. if (not) {
  4563. expr = ":not(" + expr + ")";
  4564. }
  4565. while (i--) {
  4566. if (elems[i].nodeType != 1) {
  4567. elems.splice(i, 1);
  4568. }
  4569. }
  4570. if (elems.length === 1) {
  4571. elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [];
  4572. } else {
  4573. elems = DomQuery.find.matches(expr, elems);
  4574. }
  4575. return elems;
  4576. }
  4577. });
  4578. function dir(el, prop, until) {
  4579. var matched = [], cur = el[prop];
  4580. if (typeof until != 'string' && until instanceof DomQuery) {
  4581. until = until[0];
  4582. }
  4583. while (cur && cur.nodeType !== 9) {
  4584. if (until !== undefined) {
  4585. if (cur === until) {
  4586. break;
  4587. }
  4588. if (typeof until == 'string' && DomQuery(cur).is(until)) {
  4589. break;
  4590. }
  4591. }
  4592. if (cur.nodeType === 1) {
  4593. matched.push(cur);
  4594. }
  4595. cur = cur[prop];
  4596. }
  4597. return matched;
  4598. }
  4599. function sibling(node, siblingName, nodeType, until) {
  4600. var result = [];
  4601. if (until instanceof DomQuery) {
  4602. until = until[0];
  4603. }
  4604. for (; node; node = node[siblingName]) {
  4605. if (nodeType && node.nodeType !== nodeType) {
  4606. continue;
  4607. }
  4608. if (until !== undefined) {
  4609. if (node === until) {
  4610. break;
  4611. }
  4612. if (typeof until == 'string' && DomQuery(node).is(until)) {
  4613. break;
  4614. }
  4615. }
  4616. result.push(node);
  4617. }
  4618. return result;
  4619. }
  4620. function firstSibling(node, siblingName, nodeType) {
  4621. for (node = node[siblingName]; node; node = node[siblingName]) {
  4622. if (node.nodeType == nodeType) {
  4623. return node;
  4624. }
  4625. }
  4626. return null;
  4627. }
  4628. each({
  4629. /**
  4630. * Returns a new collection with the parent of each item in current collection matching the optional selector.
  4631. *
  4632. * @method parent
  4633. * @param {Element/tinymce.dom.DomQuery} node Node to match parents against.
  4634. * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
  4635. */
  4636. parent: function(node) {
  4637. var parent = node.parentNode;
  4638. return parent && parent.nodeType !== 11 ? parent : null;
  4639. },
  4640. /**
  4641. * Returns a new collection with the all the parents of each item in current collection matching the optional selector.
  4642. *
  4643. * @method parents
  4644. * @param {Element/tinymce.dom.DomQuery} node Node to match parents against.
  4645. * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
  4646. */
  4647. parents: function(node) {
  4648. return dir(node, "parentNode");
  4649. },
  4650. /**
  4651. * Returns a new collection with next sibling of each item in current collection matching the optional selector.
  4652. *
  4653. * @method next
  4654. * @param {Element/tinymce.dom.DomQuery} node Node to match the next element against.
  4655. * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
  4656. */
  4657. next: function(node) {
  4658. return firstSibling(node, 'nextSibling', 1);
  4659. },
  4660. /**
  4661. * Returns a new collection with previous sibling of each item in current collection matching the optional selector.
  4662. *
  4663. * @method prev
  4664. * @param {Element/tinymce.dom.DomQuery} node Node to match the previous element against.
  4665. * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
  4666. */
  4667. prev: function(node) {
  4668. return firstSibling(node, 'previousSibling', 1);
  4669. },
  4670. /**
  4671. * Returns all child elements matching the optional selector.
  4672. *
  4673. * @method children
  4674. * @param {Element/tinymce.dom.DomQuery} node Node to match the elements against.
  4675. * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
  4676. */
  4677. children: function(node) {
  4678. return sibling(node.firstChild, 'nextSibling', 1);
  4679. },
  4680. /**
  4681. * Returns all child nodes matching the optional selector.
  4682. *
  4683. * @method contents
  4684. * @param {Element/tinymce.dom.DomQuery} node Node to get the contents of.
  4685. * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
  4686. */
  4687. contents: function(node) {
  4688. return Tools.toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
  4689. }
  4690. }, function(name, fn) {
  4691. DomQuery.fn[name] = function(selector) {
  4692. var self = this, result = [];
  4693. self.each(function() {
  4694. var nodes = fn.call(result, this, selector, result);
  4695. if (nodes) {
  4696. if (DomQuery.isArray(nodes)) {
  4697. result.push.apply(result, nodes);
  4698. } else {
  4699. result.push(nodes);
  4700. }
  4701. }
  4702. });
  4703. // If traversing on multiple elements we might get the same elements twice
  4704. if (this.length > 1) {
  4705. if (!skipUniques[name]) {
  4706. result = DomQuery.unique(result);
  4707. }
  4708. if (name.indexOf('parents') === 0) {
  4709. result = result.reverse();
  4710. }
  4711. }
  4712. result = DomQuery(result);
  4713. if (selector) {
  4714. return result.filter(selector);
  4715. }
  4716. return result;
  4717. };
  4718. });
  4719. each({
  4720. /**
  4721. * Returns a new collection with the all the parents until the matching selector/element
  4722. * of each item in current collection matching the optional selector.
  4723. *
  4724. * @method parentsUntil
  4725. * @param {Element/tinymce.dom.DomQuery} node Node to find parent of.
  4726. * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
  4727. * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
  4728. */
  4729. parentsUntil: function(node, until) {
  4730. return dir(node, "parentNode", until);
  4731. },
  4732. /**
  4733. * Returns a new collection with all next siblings of each item in current collection matching the optional selector.
  4734. *
  4735. * @method nextUntil
  4736. * @param {Element/tinymce.dom.DomQuery} node Node to find next siblings on.
  4737. * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
  4738. * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
  4739. */
  4740. nextUntil: function(node, until) {
  4741. return sibling(node, 'nextSibling', 1, until).slice(1);
  4742. },
  4743. /**
  4744. * Returns a new collection with all previous siblings of each item in current collection matching the optional selector.
  4745. *
  4746. * @method prevUntil
  4747. * @param {Element/tinymce.dom.DomQuery} node Node to find previous siblings on.
  4748. * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
  4749. * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
  4750. */
  4751. prevUntil: function(node, until) {
  4752. return sibling(node, 'previousSibling', 1, until).slice(1);
  4753. }
  4754. }, function(name, fn) {
  4755. DomQuery.fn[name] = function(selector, filter) {
  4756. var self = this, result = [];
  4757. self.each(function() {
  4758. var nodes = fn.call(result, this, selector, result);
  4759. if (nodes) {
  4760. if (DomQuery.isArray(nodes)) {
  4761. result.push.apply(result, nodes);
  4762. } else {
  4763. result.push(nodes);
  4764. }
  4765. }
  4766. });
  4767. // If traversing on multiple elements we might get the same elements twice
  4768. if (this.length > 1) {
  4769. result = DomQuery.unique(result);
  4770. if (name.indexOf('parents') === 0 || name === 'prevUntil') {
  4771. result = result.reverse();
  4772. }
  4773. }
  4774. result = DomQuery(result);
  4775. if (filter) {
  4776. return result.filter(filter);
  4777. }
  4778. return result;
  4779. };
  4780. });
  4781. /**
  4782. * Returns true/false if the current set items matches the selector.
  4783. *
  4784. * @method is
  4785. * @param {String} selector Selector to match the elements against.
  4786. * @return {Boolean} True/false if the current set matches the selector.
  4787. */
  4788. DomQuery.fn.is = function(selector) {
  4789. return !!selector && this.filter(selector).length > 0;
  4790. };
  4791. DomQuery.fn.init.prototype = DomQuery.fn;
  4792. DomQuery.overrideDefaults = function(callback) {
  4793. var defaults;
  4794. function sub(selector, context) {
  4795. defaults = defaults || callback();
  4796. if (arguments.length === 0) {
  4797. selector = defaults.element;
  4798. }
  4799. if (!context) {
  4800. context = defaults.context;
  4801. }
  4802. return new sub.fn.init(selector, context);
  4803. }
  4804. DomQuery.extend(sub, this);
  4805. return sub;
  4806. };
  4807. function appendHooks(targetHooks, prop, hooks) {
  4808. each(hooks, function(name, func) {
  4809. targetHooks[name] = targetHooks[name] || {};
  4810. targetHooks[name][prop] = func;
  4811. });
  4812. }
  4813. if (Env.ie && Env.ie < 8) {
  4814. appendHooks(attrHooks, 'get', {
  4815. maxlength: function(elm) {
  4816. var value = elm.maxLength;
  4817. if (value === 0x7fffffff) {
  4818. return undef;
  4819. }
  4820. return value;
  4821. },
  4822. size: function(elm) {
  4823. var value = elm.size;
  4824. if (value === 20) {
  4825. return undef;
  4826. }
  4827. return value;
  4828. },
  4829. 'class': function(elm) {
  4830. return elm.className;
  4831. },
  4832. style: function(elm) {
  4833. var value = elm.style.cssText;
  4834. if (value.length === 0) {
  4835. return undef;
  4836. }
  4837. return value;
  4838. }
  4839. });
  4840. appendHooks(attrHooks, 'set', {
  4841. 'class': function(elm, value) {
  4842. elm.className = value;
  4843. },
  4844. style: function(elm, value) {
  4845. elm.style.cssText = value;
  4846. }
  4847. });
  4848. }
  4849. if (Env.ie && Env.ie < 9) {
  4850. /*jshint sub:true */
  4851. /*eslint dot-notation: 0*/
  4852. cssFix['float'] = 'styleFloat';
  4853. appendHooks(cssHooks, 'set', {
  4854. opacity: function(elm, value) {
  4855. var style = elm.style;
  4856. if (value === null || value === '') {
  4857. style.removeAttribute('filter');
  4858. } else {
  4859. style.zoom = 1;
  4860. style.filter = 'alpha(opacity=' + (value * 100) + ')';
  4861. }
  4862. }
  4863. });
  4864. }
  4865. DomQuery.attrHooks = attrHooks;
  4866. DomQuery.cssHooks = cssHooks;
  4867. return DomQuery;
  4868. });
  4869. // Included from: js/tinymce/classes/html/Styles.js
  4870. /**
  4871. * Styles.js
  4872. *
  4873. * Released under LGPL License.
  4874. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  4875. *
  4876. * License: http://www.tinymce.com/license
  4877. * Contributing: http://www.tinymce.com/contributing
  4878. */
  4879. /**
  4880. * This class is used to parse CSS styles it also compresses styles to reduce the output size.
  4881. *
  4882. * @example
  4883. * var Styles = new tinymce.html.Styles({
  4884. * url_converter: function(url) {
  4885. * return url;
  4886. * }
  4887. * });
  4888. *
  4889. * styles = Styles.parse('border: 1px solid red');
  4890. * styles.color = 'red';
  4891. *
  4892. * console.log(new tinymce.html.StyleSerializer().serialize(styles));
  4893. *
  4894. * @class tinymce.html.Styles
  4895. * @version 3.4
  4896. */
  4897. define("tinymce/html/Styles", [], function() {
  4898. return function(settings, schema) {
  4899. /*jshint maxlen:255 */
  4900. /*eslint max-len:0 */
  4901. var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
  4902. urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
  4903. styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
  4904. trimRightRegExp = /\s+$/,
  4905. undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF';
  4906. settings = settings || {};
  4907. if (schema) {
  4908. validStyles = schema.getValidStyles();
  4909. invalidStyles = schema.getInvalidStyles();
  4910. }
  4911. encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
  4912. for (i = 0; i < encodingItems.length; i++) {
  4913. encodingLookup[encodingItems[i]] = invisibleChar + i;
  4914. encodingLookup[invisibleChar + i] = encodingItems[i];
  4915. }
  4916. function toHex(match, r, g, b) {
  4917. function hex(val) {
  4918. val = parseInt(val, 10).toString(16);
  4919. return val.length > 1 ? val : '0' + val; // 0 -> 00
  4920. }
  4921. return '#' + hex(r) + hex(g) + hex(b);
  4922. }
  4923. return {
  4924. /**
  4925. * Parses the specified RGB color value and returns a hex version of that color.
  4926. *
  4927. * @method toHex
  4928. * @param {String} color RGB string value like rgb(1,2,3)
  4929. * @return {String} Hex version of that RGB value like #FF00FF.
  4930. */
  4931. toHex: function(color) {
  4932. return color.replace(rgbRegExp, toHex);
  4933. },
  4934. /**
  4935. * Parses the specified style value into an object collection. This parser will also
  4936. * merge and remove any redundant items that browsers might have added. It will also convert non hex
  4937. * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
  4938. *
  4939. * @method parse
  4940. * @param {String} css Style value to parse for example: border:1px solid red;.
  4941. * @return {Object} Object representation of that style like {border: '1px solid red'}
  4942. */
  4943. parse: function(css) {
  4944. var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter;
  4945. var urlConverterScope = settings.url_converter_scope || this;
  4946. function compress(prefix, suffix, noJoin) {
  4947. var top, right, bottom, left;
  4948. top = styles[prefix + '-top' + suffix];
  4949. if (!top) {
  4950. return;
  4951. }
  4952. right = styles[prefix + '-right' + suffix];
  4953. if (!right) {
  4954. return;
  4955. }
  4956. bottom = styles[prefix + '-bottom' + suffix];
  4957. if (!bottom) {
  4958. return;
  4959. }
  4960. left = styles[prefix + '-left' + suffix];
  4961. if (!left) {
  4962. return;
  4963. }
  4964. var box = [top, right, bottom, left];
  4965. i = box.length - 1;
  4966. while (i--) {
  4967. if (box[i] !== box[i + 1]) {
  4968. break;
  4969. }
  4970. }
  4971. if (i > -1 && noJoin) {
  4972. return;
  4973. }
  4974. styles[prefix + suffix] = i == -1 ? box[0] : box.join(' ');
  4975. delete styles[prefix + '-top' + suffix];
  4976. delete styles[prefix + '-right' + suffix];
  4977. delete styles[prefix + '-bottom' + suffix];
  4978. delete styles[prefix + '-left' + suffix];
  4979. }
  4980. /**
  4981. * Checks if the specific style can be compressed in other words if all border-width are equal.
  4982. */
  4983. function canCompress(key) {
  4984. var value = styles[key], i;
  4985. if (!value) {
  4986. return;
  4987. }
  4988. value = value.split(' ');
  4989. i = value.length;
  4990. while (i--) {
  4991. if (value[i] !== value[0]) {
  4992. return false;
  4993. }
  4994. }
  4995. styles[key] = value[0];
  4996. return true;
  4997. }
  4998. /**
  4999. * Compresses multiple styles into one style.
  5000. */
  5001. function compress2(target, a, b, c) {
  5002. if (!canCompress(a)) {
  5003. return;
  5004. }
  5005. if (!canCompress(b)) {
  5006. return;
  5007. }
  5008. if (!canCompress(c)) {
  5009. return;
  5010. }
  5011. // Compress
  5012. styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
  5013. delete styles[a];
  5014. delete styles[b];
  5015. delete styles[c];
  5016. }
  5017. // Encodes the specified string by replacing all \" \' ; : with _<num>
  5018. function encode(str) {
  5019. isEncoded = true;
  5020. return encodingLookup[str];
  5021. }
  5022. // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
  5023. // It will also decode the \" \' if keep_slashes is set to fale or omitted
  5024. function decode(str, keep_slashes) {
  5025. if (isEncoded) {
  5026. str = str.replace(/\uFEFF[0-9]/g, function(str) {
  5027. return encodingLookup[str];
  5028. });
  5029. }
  5030. if (!keep_slashes) {
  5031. str = str.replace(/\\([\'\";:])/g, "$1");
  5032. }
  5033. return str;
  5034. }
  5035. function processUrl(match, url, url2, url3, str, str2) {
  5036. str = str || str2;
  5037. if (str) {
  5038. str = decode(str);
  5039. // Force strings into single quote format
  5040. return "'" + str.replace(/\'/g, "\\'") + "'";
  5041. }
  5042. url = decode(url || url2 || url3);
  5043. if (!settings.allow_script_urls) {
  5044. var scriptUrl = url.replace(/[\s\r\n]+/, '');
  5045. if (/(java|vb)script:/i.test(scriptUrl)) {
  5046. return "";
  5047. }
  5048. if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
  5049. return "";
  5050. }
  5051. }
  5052. // Convert the URL to relative/absolute depending on config
  5053. if (urlConverter) {
  5054. url = urlConverter.call(urlConverterScope, url, 'style');
  5055. }
  5056. // Output new URL format
  5057. return "url('" + url.replace(/\'/g, "\\'") + "')";
  5058. }
  5059. if (css) {
  5060. css = css.replace(/[\u0000-\u001F]/g, '');
  5061. // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
  5062. css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
  5063. return str.replace(/[;:]/g, encode);
  5064. });
  5065. // Parse styles
  5066. while ((matches = styleRegExp.exec(css))) {
  5067. name = matches[1].replace(trimRightRegExp, '').toLowerCase();
  5068. value = matches[2].replace(trimRightRegExp, '');
  5069. // Decode escaped sequences like \65 -> e
  5070. /*jshint loopfunc:true*/
  5071. /*eslint no-loop-func:0 */
  5072. value = value.replace(/\\[0-9a-f]+/g, function(e) {
  5073. return String.fromCharCode(parseInt(e.substr(1), 16));
  5074. });
  5075. if (name && value.length > 0) {
  5076. // Don't allow behavior name or expression/comments within the values
  5077. if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) {
  5078. continue;
  5079. }
  5080. // Opera will produce 700 instead of bold in their style values
  5081. if (name === 'font-weight' && value === '700') {
  5082. value = 'bold';
  5083. } else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
  5084. value = value.toLowerCase();
  5085. }
  5086. // Convert RGB colors to HEX
  5087. value = value.replace(rgbRegExp, toHex);
  5088. // Convert URLs and force them into url('value') format
  5089. value = value.replace(urlOrStrRegExp, processUrl);
  5090. styles[name] = isEncoded ? decode(value, true) : value;
  5091. }
  5092. styleRegExp.lastIndex = matches.index + matches[0].length;
  5093. }
  5094. // Compress the styles to reduce it's size for example IE will expand styles
  5095. compress("border", "", true);
  5096. compress("border", "-width");
  5097. compress("border", "-color");
  5098. compress("border", "-style");
  5099. compress("padding", "");
  5100. compress("margin", "");
  5101. compress2('border', 'border-width', 'border-style', 'border-color');
  5102. // Remove pointless border, IE produces these
  5103. if (styles.border === 'medium none') {
  5104. delete styles.border;
  5105. }
  5106. // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
  5107. // So let us assume it shouldn't be there
  5108. if (styles['border-image'] === 'none') {
  5109. delete styles['border-image'];
  5110. }
  5111. }
  5112. return styles;
  5113. },
  5114. /**
  5115. * Serializes the specified style object into a string.
  5116. *
  5117. * @method serialize
  5118. * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
  5119. * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
  5120. * @return {String} String representation of the style object for example: border: 1px solid red.
  5121. */
  5122. serialize: function(styles, elementName) {
  5123. var css = '', name, value;
  5124. function serializeStyles(name) {
  5125. var styleList, i, l, value;
  5126. styleList = validStyles[name];
  5127. if (styleList) {
  5128. for (i = 0, l = styleList.length; i < l; i++) {
  5129. name = styleList[i];
  5130. value = styles[name];
  5131. if (value !== undef && value.length > 0) {
  5132. css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
  5133. }
  5134. }
  5135. }
  5136. }
  5137. function isValid(name, elementName) {
  5138. var styleMap;
  5139. styleMap = invalidStyles['*'];
  5140. if (styleMap && styleMap[name]) {
  5141. return false;
  5142. }
  5143. styleMap = invalidStyles[elementName];
  5144. if (styleMap && styleMap[name]) {
  5145. return false;
  5146. }
  5147. return true;
  5148. }
  5149. // Serialize styles according to schema
  5150. if (elementName && validStyles) {
  5151. // Serialize global styles and element specific styles
  5152. serializeStyles('*');
  5153. serializeStyles(elementName);
  5154. } else {
  5155. // Output the styles in the order they are inside the object
  5156. for (name in styles) {
  5157. value = styles[name];
  5158. if (value !== undef && value.length > 0) {
  5159. if (!invalidStyles || isValid(name, elementName)) {
  5160. css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
  5161. }
  5162. }
  5163. }
  5164. }
  5165. return css;
  5166. }
  5167. };
  5168. };
  5169. });
  5170. // Included from: js/tinymce/classes/dom/TreeWalker.js
  5171. /**
  5172. * TreeWalker.js
  5173. *
  5174. * Released under LGPL License.
  5175. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  5176. *
  5177. * License: http://www.tinymce.com/license
  5178. * Contributing: http://www.tinymce.com/contributing
  5179. */
  5180. /**
  5181. * TreeWalker class enables you to walk the DOM in a linear manner.
  5182. *
  5183. * @class tinymce.dom.TreeWalker
  5184. * @example
  5185. * var walker = new tinymce.dom.TreeWalker(startNode);
  5186. *
  5187. * do {
  5188. * console.log(walker.current());
  5189. * } while (walker.next());
  5190. */
  5191. define("tinymce/dom/TreeWalker", [], function() {
  5192. /**
  5193. * Constructs a new TreeWalker instance.
  5194. *
  5195. * @constructor
  5196. * @method TreeWalker
  5197. * @param {Node} startNode Node to start walking from.
  5198. * @param {node} rootNode Optional root node to never walk out of.
  5199. */
  5200. return function(startNode, rootNode) {
  5201. var node = startNode;
  5202. function findSibling(node, startName, siblingName, shallow) {
  5203. var sibling, parent;
  5204. if (node) {
  5205. // Walk into nodes if it has a start
  5206. if (!shallow && node[startName]) {
  5207. return node[startName];
  5208. }
  5209. // Return the sibling if it has one
  5210. if (node != rootNode) {
  5211. sibling = node[siblingName];
  5212. if (sibling) {
  5213. return sibling;
  5214. }
  5215. // Walk up the parents to look for siblings
  5216. for (parent = node.parentNode; parent && parent != rootNode; parent = parent.parentNode) {
  5217. sibling = parent[siblingName];
  5218. if (sibling) {
  5219. return sibling;
  5220. }
  5221. }
  5222. }
  5223. }
  5224. }
  5225. function findPreviousNode(node, startName, siblingName, shallow) {
  5226. var sibling, parent, child;
  5227. if (node) {
  5228. sibling = node[siblingName];
  5229. if (rootNode && sibling === rootNode) {
  5230. return;
  5231. }
  5232. if (sibling) {
  5233. if (!shallow) {
  5234. // Walk up the parents to look for siblings
  5235. for (child = sibling[startName]; child; child = child[startName]) {
  5236. if (!child[startName]) {
  5237. return child;
  5238. }
  5239. }
  5240. }
  5241. return sibling;
  5242. }
  5243. parent = node.parentNode;
  5244. if (parent && parent !== rootNode) {
  5245. return parent;
  5246. }
  5247. }
  5248. }
  5249. /**
  5250. * Returns the current node.
  5251. *
  5252. * @method current
  5253. * @return {Node} Current node where the walker is.
  5254. */
  5255. this.current = function() {
  5256. return node;
  5257. };
  5258. /**
  5259. * Walks to the next node in tree.
  5260. *
  5261. * @method next
  5262. * @return {Node} Current node where the walker is after moving to the next node.
  5263. */
  5264. this.next = function(shallow) {
  5265. node = findSibling(node, 'firstChild', 'nextSibling', shallow);
  5266. return node;
  5267. };
  5268. /**
  5269. * Walks to the previous node in tree.
  5270. *
  5271. * @method prev
  5272. * @return {Node} Current node where the walker is after moving to the previous node.
  5273. */
  5274. this.prev = function(shallow) {
  5275. node = findSibling(node, 'lastChild', 'previousSibling', shallow);
  5276. return node;
  5277. };
  5278. this.prev2 = function(shallow) {
  5279. node = findPreviousNode(node, 'lastChild', 'previousSibling', shallow);
  5280. return node;
  5281. };
  5282. };
  5283. });
  5284. // Included from: js/tinymce/classes/dom/Range.js
  5285. /**
  5286. * Range.js
  5287. *
  5288. * Released under LGPL License.
  5289. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  5290. *
  5291. * License: http://www.tinymce.com/license
  5292. * Contributing: http://www.tinymce.com/contributing
  5293. */
  5294. /**
  5295. * Old IE Range.
  5296. *
  5297. * @private
  5298. * @class tinymce.dom.Range
  5299. */
  5300. define("tinymce/dom/Range", [
  5301. "tinymce/util/Tools"
  5302. ], function(Tools) {
  5303. // Range constructor
  5304. function Range(dom) {
  5305. var self = this,
  5306. doc = dom.doc,
  5307. EXTRACT = 0,
  5308. CLONE = 1,
  5309. DELETE = 2,
  5310. TRUE = true,
  5311. FALSE = false,
  5312. START_OFFSET = 'startOffset',
  5313. START_CONTAINER = 'startContainer',
  5314. END_CONTAINER = 'endContainer',
  5315. END_OFFSET = 'endOffset',
  5316. extend = Tools.extend,
  5317. nodeIndex = dom.nodeIndex;
  5318. function createDocumentFragment() {
  5319. return doc.createDocumentFragment();
  5320. }
  5321. function setStart(n, o) {
  5322. _setEndPoint(TRUE, n, o);
  5323. }
  5324. function setEnd(n, o) {
  5325. _setEndPoint(FALSE, n, o);
  5326. }
  5327. function setStartBefore(n) {
  5328. setStart(n.parentNode, nodeIndex(n));
  5329. }
  5330. function setStartAfter(n) {
  5331. setStart(n.parentNode, nodeIndex(n) + 1);
  5332. }
  5333. function setEndBefore(n) {
  5334. setEnd(n.parentNode, nodeIndex(n));
  5335. }
  5336. function setEndAfter(n) {
  5337. setEnd(n.parentNode, nodeIndex(n) + 1);
  5338. }
  5339. function collapse(ts) {
  5340. if (ts) {
  5341. self[END_CONTAINER] = self[START_CONTAINER];
  5342. self[END_OFFSET] = self[START_OFFSET];
  5343. } else {
  5344. self[START_CONTAINER] = self[END_CONTAINER];
  5345. self[START_OFFSET] = self[END_OFFSET];
  5346. }
  5347. self.collapsed = TRUE;
  5348. }
  5349. function selectNode(n) {
  5350. setStartBefore(n);
  5351. setEndAfter(n);
  5352. }
  5353. function selectNodeContents(n) {
  5354. setStart(n, 0);
  5355. setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
  5356. }
  5357. function compareBoundaryPoints(h, r) {
  5358. var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET],
  5359. rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
  5360. // Check START_TO_START
  5361. if (h === 0) {
  5362. return _compareBoundaryPoints(sc, so, rsc, rso);
  5363. }
  5364. // Check START_TO_END
  5365. if (h === 1) {
  5366. return _compareBoundaryPoints(ec, eo, rsc, rso);
  5367. }
  5368. // Check END_TO_END
  5369. if (h === 2) {
  5370. return _compareBoundaryPoints(ec, eo, rec, reo);
  5371. }
  5372. // Check END_TO_START
  5373. if (h === 3) {
  5374. return _compareBoundaryPoints(sc, so, rec, reo);
  5375. }
  5376. }
  5377. function deleteContents() {
  5378. _traverse(DELETE);
  5379. }
  5380. function extractContents() {
  5381. return _traverse(EXTRACT);
  5382. }
  5383. function cloneContents() {
  5384. return _traverse(CLONE);
  5385. }
  5386. function insertNode(n) {
  5387. var startContainer = this[START_CONTAINER],
  5388. startOffset = this[START_OFFSET], nn, o;
  5389. // Node is TEXT_NODE or CDATA
  5390. if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
  5391. if (!startOffset) {
  5392. // At the start of text
  5393. startContainer.parentNode.insertBefore(n, startContainer);
  5394. } else if (startOffset >= startContainer.nodeValue.length) {
  5395. // At the end of text
  5396. dom.insertAfter(n, startContainer);
  5397. } else {
  5398. // Middle, need to split
  5399. nn = startContainer.splitText(startOffset);
  5400. startContainer.parentNode.insertBefore(n, nn);
  5401. }
  5402. } else {
  5403. // Insert element node
  5404. if (startContainer.childNodes.length > 0) {
  5405. o = startContainer.childNodes[startOffset];
  5406. }
  5407. if (o) {
  5408. startContainer.insertBefore(n, o);
  5409. } else {
  5410. if (startContainer.nodeType == 3) {
  5411. dom.insertAfter(n, startContainer);
  5412. } else {
  5413. startContainer.appendChild(n);
  5414. }
  5415. }
  5416. }
  5417. }
  5418. function surroundContents(n) {
  5419. var f = self.extractContents();
  5420. self.insertNode(n);
  5421. n.appendChild(f);
  5422. self.selectNode(n);
  5423. }
  5424. function cloneRange() {
  5425. return extend(new Range(dom), {
  5426. startContainer: self[START_CONTAINER],
  5427. startOffset: self[START_OFFSET],
  5428. endContainer: self[END_CONTAINER],
  5429. endOffset: self[END_OFFSET],
  5430. collapsed: self.collapsed,
  5431. commonAncestorContainer: self.commonAncestorContainer
  5432. });
  5433. }
  5434. // Private methods
  5435. function _getSelectedNode(container, offset) {
  5436. var child;
  5437. // TEXT_NODE
  5438. if (container.nodeType == 3) {
  5439. return container;
  5440. }
  5441. if (offset < 0) {
  5442. return container;
  5443. }
  5444. child = container.firstChild;
  5445. while (child && offset > 0) {
  5446. --offset;
  5447. child = child.nextSibling;
  5448. }
  5449. if (child) {
  5450. return child;
  5451. }
  5452. return container;
  5453. }
  5454. function _isCollapsed() {
  5455. return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]);
  5456. }
  5457. function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
  5458. var c, offsetC, n, cmnRoot, childA, childB;
  5459. // In the first case the boundary-points have the same container. A is before B
  5460. // if its offset is less than the offset of B, A is equal to B if its offset is
  5461. // equal to the offset of B, and A is after B if its offset is greater than the
  5462. // offset of B.
  5463. if (containerA == containerB) {
  5464. if (offsetA == offsetB) {
  5465. return 0; // equal
  5466. }
  5467. if (offsetA < offsetB) {
  5468. return -1; // before
  5469. }
  5470. return 1; // after
  5471. }
  5472. // In the second case a child node C of the container of A is an ancestor
  5473. // container of B. In this case, A is before B if the offset of A is less than or
  5474. // equal to the index of the child node C and A is after B otherwise.
  5475. c = containerB;
  5476. while (c && c.parentNode != containerA) {
  5477. c = c.parentNode;
  5478. }
  5479. if (c) {
  5480. offsetC = 0;
  5481. n = containerA.firstChild;
  5482. while (n != c && offsetC < offsetA) {
  5483. offsetC++;
  5484. n = n.nextSibling;
  5485. }
  5486. if (offsetA <= offsetC) {
  5487. return -1; // before
  5488. }
  5489. return 1; // after
  5490. }
  5491. // In the third case a child node C of the container of B is an ancestor container
  5492. // of A. In this case, A is before B if the index of the child node C is less than
  5493. // the offset of B and A is after B otherwise.
  5494. c = containerA;
  5495. while (c && c.parentNode != containerB) {
  5496. c = c.parentNode;
  5497. }
  5498. if (c) {
  5499. offsetC = 0;
  5500. n = containerB.firstChild;
  5501. while (n != c && offsetC < offsetB) {
  5502. offsetC++;
  5503. n = n.nextSibling;
  5504. }
  5505. if (offsetC < offsetB) {
  5506. return -1; // before
  5507. }
  5508. return 1; // after
  5509. }
  5510. // In the fourth case, none of three other cases hold: the containers of A and B
  5511. // are siblings or descendants of sibling nodes. In this case, A is before B if
  5512. // the container of A is before the container of B in a pre-order traversal of the
  5513. // Ranges' context tree and A is after B otherwise.
  5514. cmnRoot = dom.findCommonAncestor(containerA, containerB);
  5515. childA = containerA;
  5516. while (childA && childA.parentNode != cmnRoot) {
  5517. childA = childA.parentNode;
  5518. }
  5519. if (!childA) {
  5520. childA = cmnRoot;
  5521. }
  5522. childB = containerB;
  5523. while (childB && childB.parentNode != cmnRoot) {
  5524. childB = childB.parentNode;
  5525. }
  5526. if (!childB) {
  5527. childB = cmnRoot;
  5528. }
  5529. if (childA == childB) {
  5530. return 0; // equal
  5531. }
  5532. n = cmnRoot.firstChild;
  5533. while (n) {
  5534. if (n == childA) {
  5535. return -1; // before
  5536. }
  5537. if (n == childB) {
  5538. return 1; // after
  5539. }
  5540. n = n.nextSibling;
  5541. }
  5542. }
  5543. function _setEndPoint(st, n, o) {
  5544. var ec, sc;
  5545. if (st) {
  5546. self[START_CONTAINER] = n;
  5547. self[START_OFFSET] = o;
  5548. } else {
  5549. self[END_CONTAINER] = n;
  5550. self[END_OFFSET] = o;
  5551. }
  5552. // If one boundary-point of a Range is set to have a root container
  5553. // other than the current one for the Range, the Range is collapsed to
  5554. // the new position. This enforces the restriction that both boundary-
  5555. // points of a Range must have the same root container.
  5556. ec = self[END_CONTAINER];
  5557. while (ec.parentNode) {
  5558. ec = ec.parentNode;
  5559. }
  5560. sc = self[START_CONTAINER];
  5561. while (sc.parentNode) {
  5562. sc = sc.parentNode;
  5563. }
  5564. if (sc == ec) {
  5565. // The start position of a Range is guaranteed to never be after the
  5566. // end position. To enforce this restriction, if the start is set to
  5567. // be at a position after the end, the Range is collapsed to that
  5568. // position.
  5569. if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) {
  5570. self.collapse(st);
  5571. }
  5572. } else {
  5573. self.collapse(st);
  5574. }
  5575. self.collapsed = _isCollapsed();
  5576. self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]);
  5577. }
  5578. function _traverse(how) {
  5579. var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
  5580. if (self[START_CONTAINER] == self[END_CONTAINER]) {
  5581. return _traverseSameContainer(how);
  5582. }
  5583. for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
  5584. if (p == self[START_CONTAINER]) {
  5585. return _traverseCommonStartContainer(c, how);
  5586. }
  5587. ++endContainerDepth;
  5588. }
  5589. for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
  5590. if (p == self[END_CONTAINER]) {
  5591. return _traverseCommonEndContainer(c, how);
  5592. }
  5593. ++startContainerDepth;
  5594. }
  5595. depthDiff = startContainerDepth - endContainerDepth;
  5596. startNode = self[START_CONTAINER];
  5597. while (depthDiff > 0) {
  5598. startNode = startNode.parentNode;
  5599. depthDiff--;
  5600. }
  5601. endNode = self[END_CONTAINER];
  5602. while (depthDiff < 0) {
  5603. endNode = endNode.parentNode;
  5604. depthDiff++;
  5605. }
  5606. // ascend the ancestor hierarchy until we have a common parent.
  5607. for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
  5608. startNode = sp;
  5609. endNode = ep;
  5610. }
  5611. return _traverseCommonAncestors(startNode, endNode, how);
  5612. }
  5613. function _traverseSameContainer(how) {
  5614. var frag, s, sub, n, cnt, sibling, xferNode, start, len;
  5615. if (how != DELETE) {
  5616. frag = createDocumentFragment();
  5617. }
  5618. // If selection is empty, just return the fragment
  5619. if (self[START_OFFSET] == self[END_OFFSET]) {
  5620. return frag;
  5621. }
  5622. // Text node needs special case handling
  5623. if (self[START_CONTAINER].nodeType == 3) { // TEXT_NODE
  5624. // get the substring
  5625. s = self[START_CONTAINER].nodeValue;
  5626. sub = s.substring(self[START_OFFSET], self[END_OFFSET]);
  5627. // set the original text node to its new value
  5628. if (how != CLONE) {
  5629. n = self[START_CONTAINER];
  5630. start = self[START_OFFSET];
  5631. len = self[END_OFFSET] - self[START_OFFSET];
  5632. if (start === 0 && len >= n.nodeValue.length - 1) {
  5633. n.parentNode.removeChild(n);
  5634. } else {
  5635. n.deleteData(start, len);
  5636. }
  5637. // Nothing is partially selected, so collapse to start point
  5638. self.collapse(TRUE);
  5639. }
  5640. if (how == DELETE) {
  5641. return;
  5642. }
  5643. if (sub.length > 0) {
  5644. frag.appendChild(doc.createTextNode(sub));
  5645. }
  5646. return frag;
  5647. }
  5648. // Copy nodes between the start/end offsets.
  5649. n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]);
  5650. cnt = self[END_OFFSET] - self[START_OFFSET];
  5651. while (n && cnt > 0) {
  5652. sibling = n.nextSibling;
  5653. xferNode = _traverseFullySelected(n, how);
  5654. if (frag) {
  5655. frag.appendChild(xferNode);
  5656. }
  5657. --cnt;
  5658. n = sibling;
  5659. }
  5660. // Nothing is partially selected, so collapse to start point
  5661. if (how != CLONE) {
  5662. self.collapse(TRUE);
  5663. }
  5664. return frag;
  5665. }
  5666. function _traverseCommonStartContainer(endAncestor, how) {
  5667. var frag, n, endIdx, cnt, sibling, xferNode;
  5668. if (how != DELETE) {
  5669. frag = createDocumentFragment();
  5670. }
  5671. n = _traverseRightBoundary(endAncestor, how);
  5672. if (frag) {
  5673. frag.appendChild(n);
  5674. }
  5675. endIdx = nodeIndex(endAncestor);
  5676. cnt = endIdx - self[START_OFFSET];
  5677. if (cnt <= 0) {
  5678. // Collapse to just before the endAncestor, which
  5679. // is partially selected.
  5680. if (how != CLONE) {
  5681. self.setEndBefore(endAncestor);
  5682. self.collapse(FALSE);
  5683. }
  5684. return frag;
  5685. }
  5686. n = endAncestor.previousSibling;
  5687. while (cnt > 0) {
  5688. sibling = n.previousSibling;
  5689. xferNode = _traverseFullySelected(n, how);
  5690. if (frag) {
  5691. frag.insertBefore(xferNode, frag.firstChild);
  5692. }
  5693. --cnt;
  5694. n = sibling;
  5695. }
  5696. // Collapse to just before the endAncestor, which
  5697. // is partially selected.
  5698. if (how != CLONE) {
  5699. self.setEndBefore(endAncestor);
  5700. self.collapse(FALSE);
  5701. }
  5702. return frag;
  5703. }
  5704. function _traverseCommonEndContainer(startAncestor, how) {
  5705. var frag, startIdx, n, cnt, sibling, xferNode;
  5706. if (how != DELETE) {
  5707. frag = createDocumentFragment();
  5708. }
  5709. n = _traverseLeftBoundary(startAncestor, how);
  5710. if (frag) {
  5711. frag.appendChild(n);
  5712. }
  5713. startIdx = nodeIndex(startAncestor);
  5714. ++startIdx; // Because we already traversed it
  5715. cnt = self[END_OFFSET] - startIdx;
  5716. n = startAncestor.nextSibling;
  5717. while (n && cnt > 0) {
  5718. sibling = n.nextSibling;
  5719. xferNode = _traverseFullySelected(n, how);
  5720. if (frag) {
  5721. frag.appendChild(xferNode);
  5722. }
  5723. --cnt;
  5724. n = sibling;
  5725. }
  5726. if (how != CLONE) {
  5727. self.setStartAfter(startAncestor);
  5728. self.collapse(TRUE);
  5729. }
  5730. return frag;
  5731. }
  5732. function _traverseCommonAncestors(startAncestor, endAncestor, how) {
  5733. var n, frag, startOffset, endOffset, cnt, sibling, nextSibling;
  5734. if (how != DELETE) {
  5735. frag = createDocumentFragment();
  5736. }
  5737. n = _traverseLeftBoundary(startAncestor, how);
  5738. if (frag) {
  5739. frag.appendChild(n);
  5740. }
  5741. startOffset = nodeIndex(startAncestor);
  5742. endOffset = nodeIndex(endAncestor);
  5743. ++startOffset;
  5744. cnt = endOffset - startOffset;
  5745. sibling = startAncestor.nextSibling;
  5746. while (cnt > 0) {
  5747. nextSibling = sibling.nextSibling;
  5748. n = _traverseFullySelected(sibling, how);
  5749. if (frag) {
  5750. frag.appendChild(n);
  5751. }
  5752. sibling = nextSibling;
  5753. --cnt;
  5754. }
  5755. n = _traverseRightBoundary(endAncestor, how);
  5756. if (frag) {
  5757. frag.appendChild(n);
  5758. }
  5759. if (how != CLONE) {
  5760. self.setStartAfter(startAncestor);
  5761. self.collapse(TRUE);
  5762. }
  5763. return frag;
  5764. }
  5765. function _traverseRightBoundary(root, how) {
  5766. var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent;
  5767. var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER];
  5768. if (next == root) {
  5769. return _traverseNode(next, isFullySelected, FALSE, how);
  5770. }
  5771. parent = next.parentNode;
  5772. clonedParent = _traverseNode(parent, FALSE, FALSE, how);
  5773. while (parent) {
  5774. while (next) {
  5775. prevSibling = next.previousSibling;
  5776. clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
  5777. if (how != DELETE) {
  5778. clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
  5779. }
  5780. isFullySelected = TRUE;
  5781. next = prevSibling;
  5782. }
  5783. if (parent == root) {
  5784. return clonedParent;
  5785. }
  5786. next = parent.previousSibling;
  5787. parent = parent.parentNode;
  5788. clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
  5789. if (how != DELETE) {
  5790. clonedGrandParent.appendChild(clonedParent);
  5791. }
  5792. clonedParent = clonedGrandParent;
  5793. }
  5794. }
  5795. function _traverseLeftBoundary(root, how) {
  5796. var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER];
  5797. var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
  5798. if (next == root) {
  5799. return _traverseNode(next, isFullySelected, TRUE, how);
  5800. }
  5801. parent = next.parentNode;
  5802. clonedParent = _traverseNode(parent, FALSE, TRUE, how);
  5803. while (parent) {
  5804. while (next) {
  5805. nextSibling = next.nextSibling;
  5806. clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
  5807. if (how != DELETE) {
  5808. clonedParent.appendChild(clonedChild);
  5809. }
  5810. isFullySelected = TRUE;
  5811. next = nextSibling;
  5812. }
  5813. if (parent == root) {
  5814. return clonedParent;
  5815. }
  5816. next = parent.nextSibling;
  5817. parent = parent.parentNode;
  5818. clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
  5819. if (how != DELETE) {
  5820. clonedGrandParent.appendChild(clonedParent);
  5821. }
  5822. clonedParent = clonedGrandParent;
  5823. }
  5824. }
  5825. function _traverseNode(n, isFullySelected, isLeft, how) {
  5826. var txtValue, newNodeValue, oldNodeValue, offset, newNode;
  5827. if (isFullySelected) {
  5828. return _traverseFullySelected(n, how);
  5829. }
  5830. // TEXT_NODE
  5831. if (n.nodeType == 3) {
  5832. txtValue = n.nodeValue;
  5833. if (isLeft) {
  5834. offset = self[START_OFFSET];
  5835. newNodeValue = txtValue.substring(offset);
  5836. oldNodeValue = txtValue.substring(0, offset);
  5837. } else {
  5838. offset = self[END_OFFSET];
  5839. newNodeValue = txtValue.substring(0, offset);
  5840. oldNodeValue = txtValue.substring(offset);
  5841. }
  5842. if (how != CLONE) {
  5843. n.nodeValue = oldNodeValue;
  5844. }
  5845. if (how == DELETE) {
  5846. return;
  5847. }
  5848. newNode = dom.clone(n, FALSE);
  5849. newNode.nodeValue = newNodeValue;
  5850. return newNode;
  5851. }
  5852. if (how == DELETE) {
  5853. return;
  5854. }
  5855. return dom.clone(n, FALSE);
  5856. }
  5857. function _traverseFullySelected(n, how) {
  5858. if (how != DELETE) {
  5859. return how == CLONE ? dom.clone(n, TRUE) : n;
  5860. }
  5861. n.parentNode.removeChild(n);
  5862. }
  5863. function toStringIE() {
  5864. return dom.create('body', null, cloneContents()).outerText;
  5865. }
  5866. extend(self, {
  5867. // Initial states
  5868. startContainer: doc,
  5869. startOffset: 0,
  5870. endContainer: doc,
  5871. endOffset: 0,
  5872. collapsed: TRUE,
  5873. commonAncestorContainer: doc,
  5874. // Range constants
  5875. START_TO_START: 0,
  5876. START_TO_END: 1,
  5877. END_TO_END: 2,
  5878. END_TO_START: 3,
  5879. // Public methods
  5880. setStart: setStart,
  5881. setEnd: setEnd,
  5882. setStartBefore: setStartBefore,
  5883. setStartAfter: setStartAfter,
  5884. setEndBefore: setEndBefore,
  5885. setEndAfter: setEndAfter,
  5886. collapse: collapse,
  5887. selectNode: selectNode,
  5888. selectNodeContents: selectNodeContents,
  5889. compareBoundaryPoints: compareBoundaryPoints,
  5890. deleteContents: deleteContents,
  5891. extractContents: extractContents,
  5892. cloneContents: cloneContents,
  5893. insertNode: insertNode,
  5894. surroundContents: surroundContents,
  5895. cloneRange: cloneRange,
  5896. toStringIE: toStringIE
  5897. });
  5898. return self;
  5899. }
  5900. // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
  5901. Range.prototype.toString = function() {
  5902. return this.toStringIE();
  5903. };
  5904. return Range;
  5905. });
  5906. // Included from: js/tinymce/classes/html/Entities.js
  5907. /**
  5908. * Entities.js
  5909. *
  5910. * Released under LGPL License.
  5911. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  5912. *
  5913. * License: http://www.tinymce.com/license
  5914. * Contributing: http://www.tinymce.com/contributing
  5915. */
  5916. /*jshint bitwise:false */
  5917. /*eslint no-bitwise:0 */
  5918. /**
  5919. * Entity encoder class.
  5920. *
  5921. * @class tinymce.html.Entities
  5922. * @static
  5923. * @version 3.4
  5924. */
  5925. define("tinymce/html/Entities", [
  5926. "tinymce/util/Tools"
  5927. ], function(Tools) {
  5928. var makeMap = Tools.makeMap;
  5929. var namedEntities, baseEntities, reverseEntities,
  5930. attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
  5931. textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
  5932. rawCharsRegExp = /[<>&\"\']/g,
  5933. entityRegExp = /&#([a-z0-9]+);?|&([a-z0-9]+);/gi,
  5934. asciiMap = {
  5935. 128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020",
  5936. 135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152",
  5937. 142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022",
  5938. 150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A",
  5939. 156: "\u0153", 158: "\u017E", 159: "\u0178"
  5940. };
  5941. // Raw entities
  5942. baseEntities = {
  5943. '\"': '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
  5944. "'": '&#39;',
  5945. '<': '&lt;',
  5946. '>': '&gt;',
  5947. '&': '&amp;',
  5948. '\u0060': '&#96;'
  5949. };
  5950. // Reverse lookup table for raw entities
  5951. reverseEntities = {
  5952. '&lt;': '<',
  5953. '&gt;': '>',
  5954. '&amp;': '&',
  5955. '&quot;': '"',
  5956. '&apos;': "'"
  5957. };
  5958. // Decodes text by using the browser
  5959. function nativeDecode(text) {
  5960. var elm;
  5961. elm = document.createElement("div");
  5962. elm.innerHTML = text;
  5963. return elm.textContent || elm.innerText || text;
  5964. }
  5965. // Build a two way lookup table for the entities
  5966. function buildEntitiesLookup(items, radix) {
  5967. var i, chr, entity, lookup = {};
  5968. if (items) {
  5969. items = items.split(',');
  5970. radix = radix || 10;
  5971. // Build entities lookup table
  5972. for (i = 0; i < items.length; i += 2) {
  5973. chr = String.fromCharCode(parseInt(items[i], radix));
  5974. // Only add non base entities
  5975. if (!baseEntities[chr]) {
  5976. entity = '&' + items[i + 1] + ';';
  5977. lookup[chr] = entity;
  5978. lookup[entity] = chr;
  5979. }
  5980. }
  5981. return lookup;
  5982. }
  5983. }
  5984. // Unpack entities lookup where the numbers are in radix 32 to reduce the size
  5985. namedEntities = buildEntitiesLookup(
  5986. '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
  5987. '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
  5988. '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
  5989. '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
  5990. '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
  5991. '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
  5992. '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
  5993. '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
  5994. '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
  5995. '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
  5996. 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
  5997. 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
  5998. 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
  5999. 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
  6000. 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
  6001. '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
  6002. '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
  6003. '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
  6004. '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
  6005. '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
  6006. 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
  6007. 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
  6008. 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
  6009. '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
  6010. '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
  6011. var Entities = {
  6012. /**
  6013. * Encodes the specified string using raw entities. This means only the required XML base entities will be encoded.
  6014. *
  6015. * @method encodeRaw
  6016. * @param {String} text Text to encode.
  6017. * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  6018. * @return {String} Entity encoded text.
  6019. */
  6020. encodeRaw: function(text, attr) {
  6021. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  6022. return baseEntities[chr] || chr;
  6023. });
  6024. },
  6025. /**
  6026. * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
  6027. * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
  6028. * and is exposed as the DOMUtils.encode function.
  6029. *
  6030. * @method encodeAllRaw
  6031. * @param {String} text Text to encode.
  6032. * @return {String} Entity encoded text.
  6033. */
  6034. encodeAllRaw: function(text) {
  6035. return ('' + text).replace(rawCharsRegExp, function(chr) {
  6036. return baseEntities[chr] || chr;
  6037. });
  6038. },
  6039. /**
  6040. * Encodes the specified string using numeric entities. The core entities will be
  6041. * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
  6042. *
  6043. * @method encodeNumeric
  6044. * @param {String} text Text to encode.
  6045. * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  6046. * @return {String} Entity encoded text.
  6047. */
  6048. encodeNumeric: function(text, attr) {
  6049. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  6050. // Multi byte sequence convert it to a single entity
  6051. if (chr.length > 1) {
  6052. return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
  6053. }
  6054. return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
  6055. });
  6056. },
  6057. /**
  6058. * Encodes the specified string using named entities. The core entities will be encoded
  6059. * as named ones but all non lower ascii characters will be encoded into named entities.
  6060. *
  6061. * @method encodeNamed
  6062. * @param {String} text Text to encode.
  6063. * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  6064. * @param {Object} entities Optional parameter with entities to use.
  6065. * @return {String} Entity encoded text.
  6066. */
  6067. encodeNamed: function(text, attr, entities) {
  6068. entities = entities || namedEntities;
  6069. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  6070. return baseEntities[chr] || entities[chr] || chr;
  6071. });
  6072. },
  6073. /**
  6074. * Returns an encode function based on the name(s) and it's optional entities.
  6075. *
  6076. * @method getEncodeFunc
  6077. * @param {String} name Comma separated list of encoders for example named,numeric.
  6078. * @param {String} entities Optional parameter with entities to use instead of the built in set.
  6079. * @return {function} Encode function to be used.
  6080. */
  6081. getEncodeFunc: function(name, entities) {
  6082. entities = buildEntitiesLookup(entities) || namedEntities;
  6083. function encodeNamedAndNumeric(text, attr) {
  6084. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  6085. return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
  6086. });
  6087. }
  6088. function encodeCustomNamed(text, attr) {
  6089. return Entities.encodeNamed(text, attr, entities);
  6090. }
  6091. // Replace + with , to be compatible with previous TinyMCE versions
  6092. name = makeMap(name.replace(/\+/g, ','));
  6093. // Named and numeric encoder
  6094. if (name.named && name.numeric) {
  6095. return encodeNamedAndNumeric;
  6096. }
  6097. // Named encoder
  6098. if (name.named) {
  6099. // Custom names
  6100. if (entities) {
  6101. return encodeCustomNamed;
  6102. }
  6103. return Entities.encodeNamed;
  6104. }
  6105. // Numeric
  6106. if (name.numeric) {
  6107. return Entities.encodeNumeric;
  6108. }
  6109. // Raw encoder
  6110. return Entities.encodeRaw;
  6111. },
  6112. /**
  6113. * Decodes the specified string, this will replace entities with raw UTF characters.
  6114. *
  6115. * @method decode
  6116. * @param {String} text Text to entity decode.
  6117. * @return {String} Entity decoded string.
  6118. */
  6119. decode: function(text) {
  6120. return text.replace(entityRegExp, function(all, numeric) {
  6121. if (numeric) {
  6122. if (numeric.charAt(0).toLowerCase() === 'x') {
  6123. numeric = parseInt(numeric.substr(1), 16);
  6124. } else {
  6125. numeric = parseInt(numeric, 10);
  6126. }
  6127. // Support upper UTF
  6128. if (numeric > 0xFFFF) {
  6129. numeric -= 0x10000;
  6130. return String.fromCharCode(0xD800 + (numeric >> 10), 0xDC00 + (numeric & 0x3FF));
  6131. }
  6132. return asciiMap[numeric] || String.fromCharCode(numeric);
  6133. }
  6134. return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
  6135. });
  6136. }
  6137. };
  6138. return Entities;
  6139. });
  6140. // Included from: js/tinymce/classes/dom/StyleSheetLoader.js
  6141. /**
  6142. * StyleSheetLoader.js
  6143. *
  6144. * Released under LGPL License.
  6145. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  6146. *
  6147. * License: http://www.tinymce.com/license
  6148. * Contributing: http://www.tinymce.com/contributing
  6149. */
  6150. /**
  6151. * This class handles loading of external stylesheets and fires events when these are loaded.
  6152. *
  6153. * @class tinymce.dom.StyleSheetLoader
  6154. * @private
  6155. */
  6156. define("tinymce/dom/StyleSheetLoader", [
  6157. "tinymce/util/Tools",
  6158. "tinymce/util/Delay"
  6159. ], function(Tools, Delay) {
  6160. "use strict";
  6161. return function(document, settings) {
  6162. var idCount = 0, loadedStates = {}, maxLoadTime;
  6163. settings = settings || {};
  6164. maxLoadTime = settings.maxLoadTime || 5000;
  6165. function appendToHead(node) {
  6166. document.getElementsByTagName('head')[0].appendChild(node);
  6167. }
  6168. /**
  6169. * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
  6170. *
  6171. * @method load
  6172. * @param {String} url Url to be loaded.
  6173. * @param {Function} loadedCallback Callback to be executed when loaded.
  6174. * @param {Function} errorCallback Callback to be executed when failed loading.
  6175. */
  6176. function load(url, loadedCallback, errorCallback) {
  6177. var link, style, startTime, state;
  6178. function passed() {
  6179. var callbacks = state.passed, i = callbacks.length;
  6180. while (i--) {
  6181. callbacks[i]();
  6182. }
  6183. state.status = 2;
  6184. state.passed = [];
  6185. state.failed = [];
  6186. }
  6187. function failed() {
  6188. var callbacks = state.failed, i = callbacks.length;
  6189. while (i--) {
  6190. callbacks[i]();
  6191. }
  6192. state.status = 3;
  6193. state.passed = [];
  6194. state.failed = [];
  6195. }
  6196. // Sniffs for older WebKit versions that have the link.onload but a broken one
  6197. function isOldWebKit() {
  6198. var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
  6199. return !!(webKitChunks && webKitChunks[1] < 536);
  6200. }
  6201. // Calls the waitCallback until the test returns true or the timeout occurs
  6202. function wait(testCallback, waitCallback) {
  6203. if (!testCallback()) {
  6204. // Wait for timeout
  6205. if ((new Date().getTime()) - startTime < maxLoadTime) {
  6206. Delay.setTimeout(waitCallback);
  6207. } else {
  6208. failed();
  6209. }
  6210. }
  6211. }
  6212. // Workaround for WebKit that doesn't properly support the onload event for link elements
  6213. // Or WebKit that fires the onload event before the StyleSheet is added to the document
  6214. function waitForWebKitLinkLoaded() {
  6215. wait(function() {
  6216. var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
  6217. while (i--) {
  6218. styleSheet = styleSheets[i];
  6219. owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
  6220. if (owner && owner.id === link.id) {
  6221. passed();
  6222. return true;
  6223. }
  6224. }
  6225. }, waitForWebKitLinkLoaded);
  6226. }
  6227. // Workaround for older Geckos that doesn't have any onload event for StyleSheets
  6228. function waitForGeckoLinkLoaded() {
  6229. wait(function() {
  6230. try {
  6231. // Accessing the cssRules will throw an exception until the CSS file is loaded
  6232. var cssRules = style.sheet.cssRules;
  6233. passed();
  6234. return !!cssRules;
  6235. } catch (ex) {
  6236. // Ignore
  6237. }
  6238. }, waitForGeckoLinkLoaded);
  6239. }
  6240. url = Tools._addCacheSuffix(url);
  6241. if (!loadedStates[url]) {
  6242. state = {
  6243. passed: [],
  6244. failed: []
  6245. };
  6246. loadedStates[url] = state;
  6247. } else {
  6248. state = loadedStates[url];
  6249. }
  6250. if (loadedCallback) {
  6251. state.passed.push(loadedCallback);
  6252. }
  6253. if (errorCallback) {
  6254. state.failed.push(errorCallback);
  6255. }
  6256. // Is loading wait for it to pass
  6257. if (state.status == 1) {
  6258. return;
  6259. }
  6260. // Has finished loading and was success
  6261. if (state.status == 2) {
  6262. passed();
  6263. return;
  6264. }
  6265. // Has finished loading and was a failure
  6266. if (state.status == 3) {
  6267. failed();
  6268. return;
  6269. }
  6270. // Start loading
  6271. state.status = 1;
  6272. link = document.createElement('link');
  6273. link.rel = 'stylesheet';
  6274. link.type = 'text/css';
  6275. link.id = 'u' + (idCount++);
  6276. link.async = false;
  6277. link.defer = false;
  6278. startTime = new Date().getTime();
  6279. // Feature detect onload on link element and sniff older webkits since it has an broken onload event
  6280. if ("onload" in link && !isOldWebKit()) {
  6281. link.onload = waitForWebKitLinkLoaded;
  6282. link.onerror = failed;
  6283. } else {
  6284. // Sniff for old Firefox that doesn't support the onload event on link elements
  6285. // TODO: Remove this in the future when everyone uses modern browsers
  6286. if (navigator.userAgent.indexOf("Firefox") > 0) {
  6287. style = document.createElement('style');
  6288. style.textContent = '@import "' + url + '"';
  6289. waitForGeckoLinkLoaded();
  6290. appendToHead(style);
  6291. return;
  6292. }
  6293. // Use the id owner on older webkits
  6294. waitForWebKitLinkLoaded();
  6295. }
  6296. appendToHead(link);
  6297. link.href = url;
  6298. }
  6299. this.load = load;
  6300. };
  6301. });
  6302. // Included from: js/tinymce/classes/dom/DOMUtils.js
  6303. /**
  6304. * DOMUtils.js
  6305. *
  6306. * Released under LGPL License.
  6307. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  6308. *
  6309. * License: http://www.tinymce.com/license
  6310. * Contributing: http://www.tinymce.com/contributing
  6311. */
  6312. /**
  6313. * Utility class for various DOM manipulation and retrieval functions.
  6314. *
  6315. * @class tinymce.dom.DOMUtils
  6316. * @example
  6317. * // Add a class to an element by id in the page
  6318. * tinymce.DOM.addClass('someid', 'someclass');
  6319. *
  6320. * // Add a class to an element by id inside the editor
  6321. * tinymce.activeEditor.dom.addClass('someid', 'someclass');
  6322. */
  6323. define("tinymce/dom/DOMUtils", [
  6324. "tinymce/dom/Sizzle",
  6325. "tinymce/dom/DomQuery",
  6326. "tinymce/html/Styles",
  6327. "tinymce/dom/EventUtils",
  6328. "tinymce/dom/TreeWalker",
  6329. "tinymce/dom/Range",
  6330. "tinymce/html/Entities",
  6331. "tinymce/Env",
  6332. "tinymce/util/Tools",
  6333. "tinymce/dom/StyleSheetLoader"
  6334. ], function(Sizzle, $, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
  6335. // Shorten names
  6336. var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim;
  6337. var isIE = Env.ie;
  6338. var simpleSelectorRe = /^([a-z0-9],?)+$/i;
  6339. var whiteSpaceRegExp = /^[ \t\r\n]*$/;
  6340. function setupAttrHooks(domUtils, settings) {
  6341. var attrHooks = {}, keepValues = settings.keep_values, keepUrlHook;
  6342. keepUrlHook = {
  6343. set: function($elm, value, name) {
  6344. if (settings.url_converter) {
  6345. value = settings.url_converter.call(settings.url_converter_scope || domUtils, value, name, $elm[0]);
  6346. }
  6347. $elm.attr('data-mce-' + name, value).attr(name, value);
  6348. },
  6349. get: function($elm, name) {
  6350. return $elm.attr('data-mce-' + name) || $elm.attr(name);
  6351. }
  6352. };
  6353. attrHooks = {
  6354. style: {
  6355. set: function($elm, value) {
  6356. if (value !== null && typeof value === 'object') {
  6357. $elm.css(value);
  6358. return;
  6359. }
  6360. if (keepValues) {
  6361. $elm.attr('data-mce-style', value);
  6362. }
  6363. $elm.attr('style', value);
  6364. },
  6365. get: function($elm) {
  6366. var value = $elm.attr('data-mce-style') || $elm.attr('style');
  6367. value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName);
  6368. return value;
  6369. }
  6370. }
  6371. };
  6372. if (keepValues) {
  6373. attrHooks.href = attrHooks.src = keepUrlHook;
  6374. }
  6375. return attrHooks;
  6376. }
  6377. function updateInternalStyleAttr(domUtils, $elm) {
  6378. var value = $elm.attr('style');
  6379. value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName);
  6380. if (!value) {
  6381. value = null;
  6382. }
  6383. $elm.attr('data-mce-style', value);
  6384. }
  6385. function nodeIndex(node, normalized) {
  6386. var idx = 0, lastNodeType, nodeType;
  6387. if (node) {
  6388. for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) {
  6389. nodeType = node.nodeType;
  6390. // Normalize text nodes
  6391. if (normalized && nodeType == 3) {
  6392. if (nodeType == lastNodeType || !node.nodeValue.length) {
  6393. continue;
  6394. }
  6395. }
  6396. idx++;
  6397. lastNodeType = nodeType;
  6398. }
  6399. }
  6400. return idx;
  6401. }
  6402. /**
  6403. * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
  6404. *
  6405. * @constructor
  6406. * @method DOMUtils
  6407. * @param {Document} doc Document reference to bind the utility class to.
  6408. * @param {settings} settings Optional settings collection.
  6409. */
  6410. function DOMUtils(doc, settings) {
  6411. var self = this, blockElementsMap;
  6412. self.doc = doc;
  6413. self.win = window;
  6414. self.files = {};
  6415. self.counter = 0;
  6416. self.stdMode = !isIE || doc.documentMode >= 8;
  6417. self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
  6418. self.styleSheetLoader = new StyleSheetLoader(doc);
  6419. self.boundEvents = [];
  6420. self.settings = settings = settings || {};
  6421. self.schema = settings.schema;
  6422. self.styles = new Styles({
  6423. url_converter: settings.url_converter,
  6424. url_converter_scope: settings.url_converter_scope
  6425. }, settings.schema);
  6426. self.fixDoc(doc);
  6427. self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
  6428. self.attrHooks = setupAttrHooks(self, settings);
  6429. blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
  6430. self.$ = $.overrideDefaults(function() {
  6431. return {
  6432. context: doc,
  6433. element: self.getRoot()
  6434. };
  6435. });
  6436. /**
  6437. * Returns true/false if the specified element is a block element or not.
  6438. *
  6439. * @method isBlock
  6440. * @param {Node/String} node Element/Node to check.
  6441. * @return {Boolean} True/False state if the node is a block element or not.
  6442. */
  6443. self.isBlock = function(node) {
  6444. // Fix for #5446
  6445. if (!node) {
  6446. return false;
  6447. }
  6448. // This function is called in module pattern style since it might be executed with the wrong this scope
  6449. var type = node.nodeType;
  6450. // If it's a node then check the type and use the nodeName
  6451. if (type) {
  6452. return !!(type === 1 && blockElementsMap[node.nodeName]);
  6453. }
  6454. return !!blockElementsMap[node];
  6455. };
  6456. }
  6457. DOMUtils.prototype = {
  6458. $$: function(elm) {
  6459. if (typeof elm == 'string') {
  6460. elm = this.get(elm);
  6461. }
  6462. return this.$(elm);
  6463. },
  6464. root: null,
  6465. fixDoc: function(doc) {
  6466. var settings = this.settings, name;
  6467. if (isIE && settings.schema) {
  6468. // Add missing HTML 4/5 elements to IE
  6469. ('abbr article aside audio canvas ' +
  6470. 'details figcaption figure footer ' +
  6471. 'header hgroup mark menu meter nav ' +
  6472. 'output progress section summary ' +
  6473. 'time video').replace(/\w+/g, function(name) {
  6474. doc.createElement(name);
  6475. });
  6476. // Create all custom elements
  6477. for (name in settings.schema.getCustomElements()) {
  6478. doc.createElement(name);
  6479. }
  6480. }
  6481. },
  6482. clone: function(node, deep) {
  6483. var self = this, clone, doc;
  6484. // TODO: Add feature detection here in the future
  6485. if (!isIE || node.nodeType !== 1 || deep) {
  6486. return node.cloneNode(deep);
  6487. }
  6488. doc = self.doc;
  6489. // Make a HTML5 safe shallow copy
  6490. if (!deep) {
  6491. clone = doc.createElement(node.nodeName);
  6492. // Copy attribs
  6493. each(self.getAttribs(node), function(attr) {
  6494. self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
  6495. });
  6496. return clone;
  6497. }
  6498. return clone.firstChild;
  6499. },
  6500. /**
  6501. * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
  6502. * go above the point of this root node.
  6503. *
  6504. * @method getRoot
  6505. * @return {Element} Root element for the utility class.
  6506. */
  6507. getRoot: function() {
  6508. var self = this;
  6509. return self.settings.root_element || self.doc.body;
  6510. },
  6511. /**
  6512. * Returns the viewport of the window.
  6513. *
  6514. * @method getViewPort
  6515. * @param {Window} win Optional window to get viewport of.
  6516. * @return {Object} Viewport object with fields x, y, w and h.
  6517. */
  6518. getViewPort: function(win) {
  6519. var doc, rootElm;
  6520. win = !win ? this.win : win;
  6521. doc = win.document;
  6522. rootElm = this.boxModel ? doc.documentElement : doc.body;
  6523. // Returns viewport size excluding scrollbars
  6524. return {
  6525. x: win.pageXOffset || rootElm.scrollLeft,
  6526. y: win.pageYOffset || rootElm.scrollTop,
  6527. w: win.innerWidth || rootElm.clientWidth,
  6528. h: win.innerHeight || rootElm.clientHeight
  6529. };
  6530. },
  6531. /**
  6532. * Returns the rectangle for a specific element.
  6533. *
  6534. * @method getRect
  6535. * @param {Element/String} elm Element object or element ID to get rectangle from.
  6536. * @return {object} Rectangle for specified element object with x, y, w, h fields.
  6537. */
  6538. getRect: function(elm) {
  6539. var self = this, pos, size;
  6540. elm = self.get(elm);
  6541. pos = self.getPos(elm);
  6542. size = self.getSize(elm);
  6543. return {
  6544. x: pos.x, y: pos.y,
  6545. w: size.w, h: size.h
  6546. };
  6547. },
  6548. /**
  6549. * Returns the size dimensions of the specified element.
  6550. *
  6551. * @method getSize
  6552. * @param {Element/String} elm Element object or element ID to get rectangle from.
  6553. * @return {object} Rectangle for specified element object with w, h fields.
  6554. */
  6555. getSize: function(elm) {
  6556. var self = this, w, h;
  6557. elm = self.get(elm);
  6558. w = self.getStyle(elm, 'width');
  6559. h = self.getStyle(elm, 'height');
  6560. // Non pixel value, then force offset/clientWidth
  6561. if (w.indexOf('px') === -1) {
  6562. w = 0;
  6563. }
  6564. // Non pixel value, then force offset/clientWidth
  6565. if (h.indexOf('px') === -1) {
  6566. h = 0;
  6567. }
  6568. return {
  6569. w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth,
  6570. h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight
  6571. };
  6572. },
  6573. /**
  6574. * Returns a node by the specified selector function. This function will
  6575. * loop through all parent nodes and call the specified function for each node.
  6576. * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
  6577. * and the node it found will be returned.
  6578. *
  6579. * @method getParent
  6580. * @param {Node/String} node DOM node to search parents on or ID string.
  6581. * @param {function} selector Selection function or CSS selector to execute on each node.
  6582. * @param {Node} root Optional root element, never go below this point.
  6583. * @return {Node} DOM Node or null if it wasn't found.
  6584. */
  6585. getParent: function(node, selector, root) {
  6586. return this.getParents(node, selector, root, false);
  6587. },
  6588. /**
  6589. * Returns a node list of all parents matching the specified selector function or pattern.
  6590. * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
  6591. *
  6592. * @method getParents
  6593. * @param {Node/String} node DOM node to search parents on or ID string.
  6594. * @param {function} selector Selection function to execute on each node or CSS pattern.
  6595. * @param {Node} root Optional root element, never go below this point.
  6596. * @return {Array} Array of nodes or null if it wasn't found.
  6597. */
  6598. getParents: function(node, selector, root, collect) {
  6599. var self = this, selectorVal, result = [];
  6600. node = self.get(node);
  6601. collect = collect === undefined;
  6602. // Default root on inline mode
  6603. root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null);
  6604. // Wrap node name as func
  6605. if (is(selector, 'string')) {
  6606. selectorVal = selector;
  6607. if (selector === '*') {
  6608. selector = function(node) {
  6609. return node.nodeType == 1;
  6610. };
  6611. } else {
  6612. selector = function(node) {
  6613. return self.is(node, selectorVal);
  6614. };
  6615. }
  6616. }
  6617. while (node) {
  6618. if (node == root || !node.nodeType || node.nodeType === 9) {
  6619. break;
  6620. }
  6621. if (!selector || selector(node)) {
  6622. if (collect) {
  6623. result.push(node);
  6624. } else {
  6625. return node;
  6626. }
  6627. }
  6628. node = node.parentNode;
  6629. }
  6630. return collect ? result : null;
  6631. },
  6632. /**
  6633. * Returns the specified element by ID or the input element if it isn't a string.
  6634. *
  6635. * @method get
  6636. * @param {String/Element} n Element id to look for or element to just pass though.
  6637. * @return {Element} Element matching the specified id or null if it wasn't found.
  6638. */
  6639. get: function(elm) {
  6640. var name;
  6641. if (elm && this.doc && typeof elm == 'string') {
  6642. name = elm;
  6643. elm = this.doc.getElementById(elm);
  6644. // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
  6645. if (elm && elm.id !== name) {
  6646. return this.doc.getElementsByName(name)[1];
  6647. }
  6648. }
  6649. return elm;
  6650. },
  6651. /**
  6652. * Returns the next node that matches selector or function
  6653. *
  6654. * @method getNext
  6655. * @param {Node} node Node to find siblings from.
  6656. * @param {String/function} selector Selector CSS expression or function.
  6657. * @return {Node} Next node item matching the selector or null if it wasn't found.
  6658. */
  6659. getNext: function(node, selector) {
  6660. return this._findSib(node, selector, 'nextSibling');
  6661. },
  6662. /**
  6663. * Returns the previous node that matches selector or function
  6664. *
  6665. * @method getPrev
  6666. * @param {Node} node Node to find siblings from.
  6667. * @param {String/function} selector Selector CSS expression or function.
  6668. * @return {Node} Previous node item matching the selector or null if it wasn't found.
  6669. */
  6670. getPrev: function(node, selector) {
  6671. return this._findSib(node, selector, 'previousSibling');
  6672. },
  6673. // #ifndef jquery
  6674. /**
  6675. * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
  6676. * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough
  6677. * on more complex patterns.
  6678. *
  6679. * @method select
  6680. * @param {String} selector CSS level 3 pattern to select/find elements by.
  6681. * @param {Object} scope Optional root element/scope element to search in.
  6682. * @return {Array} Array with all matched elements.
  6683. * @example
  6684. * // Adds a class to all paragraphs in the currently active editor
  6685. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  6686. *
  6687. * // Adds a class to all spans that have the test class in the currently active editor
  6688. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass')
  6689. */
  6690. select: function(selector, scope) {
  6691. var self = this;
  6692. /*eslint new-cap:0 */
  6693. return Sizzle(selector, self.get(scope) || self.settings.root_element || self.doc, []);
  6694. },
  6695. /**
  6696. * Returns true/false if the specified element matches the specified css pattern.
  6697. *
  6698. * @method is
  6699. * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
  6700. * @param {String} selector CSS pattern to match the element against.
  6701. */
  6702. is: function(elm, selector) {
  6703. var i;
  6704. // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
  6705. if (elm.length === undefined) {
  6706. // Simple all selector
  6707. if (selector === '*') {
  6708. return elm.nodeType == 1;
  6709. }
  6710. // Simple selector just elements
  6711. if (simpleSelectorRe.test(selector)) {
  6712. selector = selector.toLowerCase().split(/,/);
  6713. elm = elm.nodeName.toLowerCase();
  6714. for (i = selector.length - 1; i >= 0; i--) {
  6715. if (selector[i] == elm) {
  6716. return true;
  6717. }
  6718. }
  6719. return false;
  6720. }
  6721. }
  6722. // Is non element
  6723. if (elm.nodeType && elm.nodeType != 1) {
  6724. return false;
  6725. }
  6726. var elms = elm.nodeType ? [elm] : elm;
  6727. /*eslint new-cap:0 */
  6728. return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
  6729. },
  6730. // #endif
  6731. /**
  6732. * Adds the specified element to another element or elements.
  6733. *
  6734. * @method add
  6735. * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
  6736. * @param {String/Element} name Name of new element to add or existing element to add.
  6737. * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
  6738. * @param {String} html Optional inner HTML contents to add for each element.
  6739. * @param {Boolean} create Optional flag if the element should be created or added.
  6740. * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
  6741. * were passed in.
  6742. * @example
  6743. * // Adds a new paragraph to the end of the active editor
  6744. * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content');
  6745. */
  6746. add: function(parentElm, name, attrs, html, create) {
  6747. var self = this;
  6748. return this.run(parentElm, function(parentElm) {
  6749. var newElm;
  6750. newElm = is(name, 'string') ? self.doc.createElement(name) : name;
  6751. self.setAttribs(newElm, attrs);
  6752. if (html) {
  6753. if (html.nodeType) {
  6754. newElm.appendChild(html);
  6755. } else {
  6756. self.setHTML(newElm, html);
  6757. }
  6758. }
  6759. return !create ? parentElm.appendChild(newElm) : newElm;
  6760. });
  6761. },
  6762. /**
  6763. * Creates a new element.
  6764. *
  6765. * @method create
  6766. * @param {String} name Name of new element.
  6767. * @param {Object} attrs Optional object name/value collection with element attributes.
  6768. * @param {String} html Optional HTML string to set as inner HTML of the element.
  6769. * @return {Element} HTML DOM node element that got created.
  6770. * @example
  6771. * // Adds an element where the caret/selection is in the active editor
  6772. * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content');
  6773. * tinymce.activeEditor.selection.setNode(el);
  6774. */
  6775. create: function(name, attrs, html) {
  6776. return this.add(this.doc.createElement(name), name, attrs, html, 1);
  6777. },
  6778. /**
  6779. * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
  6780. *
  6781. * @method createHTML
  6782. * @param {String} name Name of new element.
  6783. * @param {Object} attrs Optional object name/value collection with element attributes.
  6784. * @param {String} html Optional HTML string to set as inner HTML of the element.
  6785. * @return {String} String with new HTML element, for example: <a href="#">test</a>.
  6786. * @example
  6787. * // Creates a html chunk and inserts it at the current selection/caret location
  6788. * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line'));
  6789. */
  6790. createHTML: function(name, attrs, html) {
  6791. var outHtml = '', key;
  6792. outHtml += '<' + name;
  6793. for (key in attrs) {
  6794. if (attrs.hasOwnProperty(key) && attrs[key] !== null && typeof attrs[key] != 'undefined') {
  6795. outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"';
  6796. }
  6797. }
  6798. // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
  6799. if (typeof html != "undefined") {
  6800. return outHtml + '>' + html + '</' + name + '>';
  6801. }
  6802. return outHtml + ' />';
  6803. },
  6804. /**
  6805. * Creates a document fragment out of the specified HTML string.
  6806. *
  6807. * @method createFragment
  6808. * @param {String} html Html string to create fragment from.
  6809. * @return {DocumentFragment} Document fragment node.
  6810. */
  6811. createFragment: function(html) {
  6812. var frag, node, doc = this.doc, container;
  6813. container = doc.createElement("div");
  6814. frag = doc.createDocumentFragment();
  6815. if (html) {
  6816. container.innerHTML = html;
  6817. }
  6818. while ((node = container.firstChild)) {
  6819. frag.appendChild(node);
  6820. }
  6821. return frag;
  6822. },
  6823. /**
  6824. * Removes/deletes the specified element(s) from the DOM.
  6825. *
  6826. * @method remove
  6827. * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
  6828. * @param {Boolean} keepChildren Optional state to keep children or not. If set to true all children will be
  6829. * placed at the location of the removed element.
  6830. * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
  6831. * were passed in.
  6832. * @example
  6833. * // Removes all paragraphs in the active editor
  6834. * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p'));
  6835. *
  6836. * // Removes an element by id in the document
  6837. * tinymce.DOM.remove('mydiv');
  6838. */
  6839. remove: function(node, keepChildren) {
  6840. node = this.$$(node);
  6841. if (keepChildren) {
  6842. node.each(function() {
  6843. var child;
  6844. while ((child = this.firstChild)) {
  6845. if (child.nodeType == 3 && child.data.length === 0) {
  6846. this.removeChild(child);
  6847. } else {
  6848. this.parentNode.insertBefore(child, this);
  6849. }
  6850. }
  6851. }).remove();
  6852. } else {
  6853. node.remove();
  6854. }
  6855. return node.length > 1 ? node.toArray() : node[0];
  6856. },
  6857. /**
  6858. * Sets the CSS style value on a HTML element. The name can be a camelcase string
  6859. * or the CSS style name like background-color.
  6860. *
  6861. * @method setStyle
  6862. * @param {String/Element/Array} elm HTML element/Array of elements to set CSS style value on.
  6863. * @param {String} name Name of the style value to set.
  6864. * @param {String} value Value to set on the style.
  6865. * @example
  6866. * // Sets a style value on all paragraphs in the currently active editor
  6867. * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red');
  6868. *
  6869. * // Sets a style value to an element by id in the current document
  6870. * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
  6871. */
  6872. setStyle: function(elm, name, value) {
  6873. elm = this.$$(elm).css(name, value);
  6874. if (this.settings.update_styles) {
  6875. updateInternalStyleAttr(this, elm);
  6876. }
  6877. },
  6878. /**
  6879. * Returns the current style or runtime/computed value of an element.
  6880. *
  6881. * @method getStyle
  6882. * @param {String/Element} elm HTML element or element id string to get style from.
  6883. * @param {String} name Style name to return.
  6884. * @param {Boolean} computed Computed style.
  6885. * @return {String} Current style or computed style value of an element.
  6886. */
  6887. getStyle: function(elm, name, computed) {
  6888. elm = this.$$(elm);
  6889. if (computed) {
  6890. return elm.css(name);
  6891. }
  6892. // Camelcase it, if needed
  6893. name = name.replace(/-(\D)/g, function(a, b) {
  6894. return b.toUpperCase();
  6895. });
  6896. if (name == 'float') {
  6897. name = Env.ie && Env.ie < 12 ? 'styleFloat' : 'cssFloat';
  6898. }
  6899. return elm[0] && elm[0].style ? elm[0].style[name] : undefined;
  6900. },
  6901. /**
  6902. * Sets multiple styles on the specified element(s).
  6903. *
  6904. * @method setStyles
  6905. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set styles on.
  6906. * @param {Object} styles Name/Value collection of style items to add to the element(s).
  6907. * @example
  6908. * // Sets styles on all paragraphs in the currently active editor
  6909. * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'});
  6910. *
  6911. * // Sets styles to an element by id in the current document
  6912. * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
  6913. */
  6914. setStyles: function(elm, styles) {
  6915. elm = this.$$(elm).css(styles);
  6916. if (this.settings.update_styles) {
  6917. updateInternalStyleAttr(this, elm);
  6918. }
  6919. },
  6920. /**
  6921. * Removes all attributes from an element or elements.
  6922. *
  6923. * @method removeAllAttribs
  6924. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
  6925. */
  6926. removeAllAttribs: function(e) {
  6927. return this.run(e, function(e) {
  6928. var i, attrs = e.attributes;
  6929. for (i = attrs.length - 1; i >= 0; i--) {
  6930. e.removeAttributeNode(attrs.item(i));
  6931. }
  6932. });
  6933. },
  6934. /**
  6935. * Sets the specified attribute of an element or elements.
  6936. *
  6937. * @method setAttrib
  6938. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attribute on.
  6939. * @param {String} name Name of attribute to set.
  6940. * @param {String} value Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove
  6941. * the attribute instead.
  6942. * @example
  6943. * // Sets class attribute on all paragraphs in the active editor
  6944. * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass');
  6945. *
  6946. * // Sets class attribute on a specific element in the current page
  6947. * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
  6948. */
  6949. setAttrib: function(elm, name, value) {
  6950. var self = this, originalValue, hook, settings = self.settings;
  6951. if (value === '') {
  6952. value = null;
  6953. }
  6954. elm = self.$$(elm);
  6955. originalValue = elm.attr(name);
  6956. if (!elm.length) {
  6957. return;
  6958. }
  6959. hook = self.attrHooks[name];
  6960. if (hook && hook.set) {
  6961. hook.set(elm, value, name);
  6962. } else {
  6963. elm.attr(name, value);
  6964. }
  6965. if (originalValue != value && settings.onSetAttrib) {
  6966. settings.onSetAttrib({
  6967. attrElm: elm,
  6968. attrName: name,
  6969. attrValue: value
  6970. });
  6971. }
  6972. },
  6973. /**
  6974. * Sets two or more specified attributes of an element or elements.
  6975. *
  6976. * @method setAttribs
  6977. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
  6978. * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
  6979. * @example
  6980. * // Sets class and title attributes on all paragraphs in the active editor
  6981. * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'});
  6982. *
  6983. * // Sets class and title attributes on a specific element in the current page
  6984. * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'});
  6985. */
  6986. setAttribs: function(elm, attrs) {
  6987. var self = this;
  6988. self.$$(elm).each(function(i, node) {
  6989. each(attrs, function(value, name) {
  6990. self.setAttrib(node, name, value);
  6991. });
  6992. });
  6993. },
  6994. /**
  6995. * Returns the specified attribute by name.
  6996. *
  6997. * @method getAttrib
  6998. * @param {String/Element} elm Element string id or DOM element to get attribute from.
  6999. * @param {String} name Name of attribute to get.
  7000. * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
  7001. * @return {String} Attribute value string, default value or null if the attribute wasn't found.
  7002. */
  7003. getAttrib: function(elm, name, defaultVal) {
  7004. var self = this, hook, value;
  7005. elm = self.$$(elm);
  7006. if (elm.length) {
  7007. hook = self.attrHooks[name];
  7008. if (hook && hook.get) {
  7009. value = hook.get(elm, name);
  7010. } else {
  7011. value = elm.attr(name);
  7012. }
  7013. }
  7014. if (typeof value == 'undefined') {
  7015. value = defaultVal || '';
  7016. }
  7017. return value;
  7018. },
  7019. /**
  7020. * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
  7021. *
  7022. * @method getPos
  7023. * @param {Element/String} elm HTML element or element id to get x, y position from.
  7024. * @param {Element} rootElm Optional root element to stop calculations at.
  7025. * @return {object} Absolute position of the specified element object with x, y fields.
  7026. */
  7027. getPos: function(elm, rootElm) {
  7028. var self = this, x = 0, y = 0, offsetParent, doc = self.doc, body = doc.body, pos;
  7029. elm = self.get(elm);
  7030. rootElm = rootElm || body;
  7031. if (elm) {
  7032. // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
  7033. // Fallback to offsetParent calculations if the body isn't static better since it stops at the body root
  7034. if (rootElm === body && elm.getBoundingClientRect && $(body).css('position') === 'static') {
  7035. pos = elm.getBoundingClientRect();
  7036. rootElm = self.boxModel ? doc.documentElement : body;
  7037. // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
  7038. // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
  7039. x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - rootElm.clientLeft;
  7040. y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - rootElm.clientTop;
  7041. return {x: x, y: y};
  7042. }
  7043. offsetParent = elm;
  7044. while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
  7045. x += offsetParent.offsetLeft || 0;
  7046. y += offsetParent.offsetTop || 0;
  7047. offsetParent = offsetParent.offsetParent;
  7048. }
  7049. offsetParent = elm.parentNode;
  7050. while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
  7051. x -= offsetParent.scrollLeft || 0;
  7052. y -= offsetParent.scrollTop || 0;
  7053. offsetParent = offsetParent.parentNode;
  7054. }
  7055. }
  7056. return {x: x, y: y};
  7057. },
  7058. /**
  7059. * Parses the specified style value into an object collection. This parser will also
  7060. * merge and remove any redundant items that browsers might have added. It will also convert non-hex
  7061. * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
  7062. *
  7063. * @method parseStyle
  7064. * @param {String} cssText Style value to parse, for example: border:1px solid red;.
  7065. * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
  7066. */
  7067. parseStyle: function(cssText) {
  7068. return this.styles.parse(cssText);
  7069. },
  7070. /**
  7071. * Serializes the specified style object into a string.
  7072. *
  7073. * @method serializeStyle
  7074. * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
  7075. * @param {String} name Optional element name.
  7076. * @return {String} String representation of the style object, for example: border: 1px solid red.
  7077. */
  7078. serializeStyle: function(styles, name) {
  7079. return this.styles.serialize(styles, name);
  7080. },
  7081. /**
  7082. * Adds a style element at the top of the document with the specified cssText content.
  7083. *
  7084. * @method addStyle
  7085. * @param {String} cssText CSS Text style to add to top of head of document.
  7086. */
  7087. addStyle: function(cssText) {
  7088. var self = this, doc = self.doc, head, styleElm;
  7089. // Prevent inline from loading the same styles twice
  7090. if (self !== DOMUtils.DOM && doc === document) {
  7091. var addedStyles = DOMUtils.DOM.addedStyles;
  7092. addedStyles = addedStyles || [];
  7093. if (addedStyles[cssText]) {
  7094. return;
  7095. }
  7096. addedStyles[cssText] = true;
  7097. DOMUtils.DOM.addedStyles = addedStyles;
  7098. }
  7099. // Create style element if needed
  7100. styleElm = doc.getElementById('mceDefaultStyles');
  7101. if (!styleElm) {
  7102. styleElm = doc.createElement('style');
  7103. styleElm.id = 'mceDefaultStyles';
  7104. styleElm.type = 'text/css';
  7105. head = doc.getElementsByTagName('head')[0];
  7106. if (head.firstChild) {
  7107. head.insertBefore(styleElm, head.firstChild);
  7108. } else {
  7109. head.appendChild(styleElm);
  7110. }
  7111. }
  7112. // Append style data to old or new style element
  7113. if (styleElm.styleSheet) {
  7114. styleElm.styleSheet.cssText += cssText;
  7115. } else {
  7116. styleElm.appendChild(doc.createTextNode(cssText));
  7117. }
  7118. },
  7119. /**
  7120. * Imports/loads the specified CSS file into the document bound to the class.
  7121. *
  7122. * @method loadCSS
  7123. * @param {String} url URL to CSS file to load.
  7124. * @example
  7125. * // Loads a CSS file dynamically into the current document
  7126. * tinymce.DOM.loadCSS('somepath/some.css');
  7127. *
  7128. * // Loads a CSS file into the currently active editor instance
  7129. * tinymce.activeEditor.dom.loadCSS('somepath/some.css');
  7130. *
  7131. * // Loads a CSS file into an editor instance by id
  7132. * tinymce.get('someid').dom.loadCSS('somepath/some.css');
  7133. *
  7134. * // Loads multiple CSS files into the current document
  7135. * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
  7136. */
  7137. loadCSS: function(url) {
  7138. var self = this, doc = self.doc, head;
  7139. // Prevent inline from loading the same CSS file twice
  7140. if (self !== DOMUtils.DOM && doc === document) {
  7141. DOMUtils.DOM.loadCSS(url);
  7142. return;
  7143. }
  7144. if (!url) {
  7145. url = '';
  7146. }
  7147. head = doc.getElementsByTagName('head')[0];
  7148. each(url.split(','), function(url) {
  7149. var link;
  7150. url = Tools._addCacheSuffix(url);
  7151. if (self.files[url]) {
  7152. return;
  7153. }
  7154. self.files[url] = true;
  7155. link = self.create('link', {rel: 'stylesheet', href: url});
  7156. // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
  7157. // This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading
  7158. // It's ugly but it seems to work fine.
  7159. if (isIE && doc.documentMode && doc.recalc) {
  7160. link.onload = function() {
  7161. if (doc.recalc) {
  7162. doc.recalc();
  7163. }
  7164. link.onload = null;
  7165. };
  7166. }
  7167. head.appendChild(link);
  7168. });
  7169. },
  7170. /**
  7171. * Adds a class to the specified element or elements.
  7172. *
  7173. * @method addClass
  7174. * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
  7175. * @param {String} cls Class name to add to each element.
  7176. * @return {String/Array} String with new class value or array with new class values for all elements.
  7177. * @example
  7178. * // Adds a class to all paragraphs in the active editor
  7179. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass');
  7180. *
  7181. * // Adds a class to a specific element in the current page
  7182. * tinymce.DOM.addClass('mydiv', 'myclass');
  7183. */
  7184. addClass: function(elm, cls) {
  7185. this.$$(elm).addClass(cls);
  7186. },
  7187. /**
  7188. * Removes a class from the specified element or elements.
  7189. *
  7190. * @method removeClass
  7191. * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
  7192. * @param {String} cls Class name to remove from each element.
  7193. * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
  7194. * were passed in.
  7195. * @example
  7196. * // Removes a class from all paragraphs in the active editor
  7197. * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass');
  7198. *
  7199. * // Removes a class from a specific element in the current page
  7200. * tinymce.DOM.removeClass('mydiv', 'myclass');
  7201. */
  7202. removeClass: function(elm, cls) {
  7203. this.toggleClass(elm, cls, false);
  7204. },
  7205. /**
  7206. * Returns true if the specified element has the specified class.
  7207. *
  7208. * @method hasClass
  7209. * @param {String/Element} elm HTML element or element id string to check CSS class on.
  7210. * @param {String} cls CSS class to check for.
  7211. * @return {Boolean} true/false if the specified element has the specified class.
  7212. */
  7213. hasClass: function(elm, cls) {
  7214. return this.$$(elm).hasClass(cls);
  7215. },
  7216. /**
  7217. * Toggles the specified class on/off.
  7218. *
  7219. * @method toggleClass
  7220. * @param {Element} elm Element to toggle class on.
  7221. * @param {[type]} cls Class to toggle on/off.
  7222. * @param {[type]} state Optional state to set.
  7223. */
  7224. toggleClass: function(elm, cls, state) {
  7225. this.$$(elm).toggleClass(cls, state).each(function() {
  7226. if (this.className === '') {
  7227. $(this).attr('class', null);
  7228. }
  7229. });
  7230. },
  7231. /**
  7232. * Shows the specified element(s) by ID by setting the "display" style.
  7233. *
  7234. * @method show
  7235. * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
  7236. */
  7237. show: function(elm) {
  7238. this.$$(elm).show();
  7239. },
  7240. /**
  7241. * Hides the specified element(s) by ID by setting the "display" style.
  7242. *
  7243. * @method hide
  7244. * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to hide.
  7245. * @example
  7246. * // Hides an element by id in the document
  7247. * tinymce.DOM.hide('myid');
  7248. */
  7249. hide: function(elm) {
  7250. this.$$(elm).hide();
  7251. },
  7252. /**
  7253. * Returns true/false if the element is hidden or not by checking the "display" style.
  7254. *
  7255. * @method isHidden
  7256. * @param {String/Element} elm Id or element to check display state on.
  7257. * @return {Boolean} true/false if the element is hidden or not.
  7258. */
  7259. isHidden: function(elm) {
  7260. return this.$$(elm).css('display') == 'none';
  7261. },
  7262. /**
  7263. * Returns a unique id. This can be useful when generating elements on the fly.
  7264. * This method will not check if the element already exists.
  7265. *
  7266. * @method uniqueId
  7267. * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
  7268. * @return {String} Unique id.
  7269. */
  7270. uniqueId: function(prefix) {
  7271. return (!prefix ? 'mce_' : prefix) + (this.counter++);
  7272. },
  7273. /**
  7274. * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
  7275. * URLs will get converted, hex color values fixed etc. Check processHTML for details.
  7276. *
  7277. * @method setHTML
  7278. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set HTML inside of.
  7279. * @param {String} html HTML content to set as inner HTML of the element.
  7280. * @example
  7281. * // Sets the inner HTML of all paragraphs in the active editor
  7282. * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html');
  7283. *
  7284. * // Sets the inner HTML of an element by id in the document
  7285. * tinymce.DOM.setHTML('mydiv', 'some inner html');
  7286. */
  7287. setHTML: function(elm, html) {
  7288. elm = this.$$(elm);
  7289. if (isIE) {
  7290. elm.each(function(i, target) {
  7291. if (target.canHaveHTML === false) {
  7292. return;
  7293. }
  7294. // Remove all child nodes, IE keeps empty text nodes in DOM
  7295. while (target.firstChild) {
  7296. target.removeChild(target.firstChild);
  7297. }
  7298. try {
  7299. // IE will remove comments from the beginning
  7300. // unless you padd the contents with something
  7301. target.innerHTML = '<br>' + html;
  7302. target.removeChild(target.firstChild);
  7303. } catch (ex) {
  7304. // IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p
  7305. $('<div>').html('<br>' + html).contents().slice(1).appendTo(target);
  7306. }
  7307. return html;
  7308. });
  7309. } else {
  7310. elm.html(html);
  7311. }
  7312. },
  7313. /**
  7314. * Returns the outer HTML of an element.
  7315. *
  7316. * @method getOuterHTML
  7317. * @param {String/Element} elm Element ID or element object to get outer HTML from.
  7318. * @return {String} Outer HTML string.
  7319. * @example
  7320. * tinymce.DOM.getOuterHTML(editorElement);
  7321. * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
  7322. */
  7323. getOuterHTML: function(elm) {
  7324. elm = this.get(elm);
  7325. // Older FF doesn't have outerHTML 3.6 is still used by some orgaizations
  7326. return elm.nodeType == 1 && "outerHTML" in elm ? elm.outerHTML : $('<div>').append($(elm).clone()).html();
  7327. },
  7328. /**
  7329. * Sets the specified outer HTML on an element or elements.
  7330. *
  7331. * @method setOuterHTML
  7332. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
  7333. * @param {Object} html HTML code to set as outer value for the element.
  7334. * @example
  7335. * // Sets the outer HTML of all paragraphs in the active editor
  7336. * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>');
  7337. *
  7338. * // Sets the outer HTML of an element by id in the document
  7339. * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
  7340. */
  7341. setOuterHTML: function(elm, html) {
  7342. var self = this;
  7343. self.$$(elm).each(function() {
  7344. try {
  7345. // Older FF doesn't have outerHTML 3.6 is still used by some organizations
  7346. if ("outerHTML" in this) {
  7347. this.outerHTML = html;
  7348. return;
  7349. }
  7350. } catch (ex) {
  7351. // Ignore
  7352. }
  7353. // OuterHTML for IE it sometimes produces an "unknown runtime error"
  7354. self.remove($(this).html(html), true);
  7355. });
  7356. },
  7357. /**
  7358. * Entity decodes a string. This method decodes any HTML entities, such as &aring;.
  7359. *
  7360. * @method decode
  7361. * @param {String} s String to decode entities on.
  7362. * @return {String} Entity decoded string.
  7363. */
  7364. decode: Entities.decode,
  7365. /**
  7366. * Entity encodes a string. This method encodes the most common entities, such as <>"&.
  7367. *
  7368. * @method encode
  7369. * @param {String} text String to encode with entities.
  7370. * @return {String} Entity encoded string.
  7371. */
  7372. encode: Entities.encodeAllRaw,
  7373. /**
  7374. * Inserts an element after the reference element.
  7375. *
  7376. * @method insertAfter
  7377. * @param {Element} node Element to insert after the reference.
  7378. * @param {Element/String/Array} referenceNode Reference element, element id or array of elements to insert after.
  7379. * @return {Element/Array} Element that got added or an array with elements.
  7380. */
  7381. insertAfter: function(node, referenceNode) {
  7382. referenceNode = this.get(referenceNode);
  7383. return this.run(node, function(node) {
  7384. var parent, nextSibling;
  7385. parent = referenceNode.parentNode;
  7386. nextSibling = referenceNode.nextSibling;
  7387. if (nextSibling) {
  7388. parent.insertBefore(node, nextSibling);
  7389. } else {
  7390. parent.appendChild(node);
  7391. }
  7392. return node;
  7393. });
  7394. },
  7395. /**
  7396. * Replaces the specified element or elements with the new element specified. The new element will
  7397. * be cloned if multiple input elements are passed in.
  7398. *
  7399. * @method replace
  7400. * @param {Element} newElm New element to replace old ones with.
  7401. * @param {Element/String/Array} oldElm Element DOM node, element id or array of elements or ids to replace.
  7402. * @param {Boolean} keepChildren Optional keep children state, if set to true child nodes from the old object will be added
  7403. * to new ones.
  7404. */
  7405. replace: function(newElm, oldElm, keepChildren) {
  7406. var self = this;
  7407. return self.run(oldElm, function(oldElm) {
  7408. if (is(oldElm, 'array')) {
  7409. newElm = newElm.cloneNode(true);
  7410. }
  7411. if (keepChildren) {
  7412. each(grep(oldElm.childNodes), function(node) {
  7413. newElm.appendChild(node);
  7414. });
  7415. }
  7416. return oldElm.parentNode.replaceChild(newElm, oldElm);
  7417. });
  7418. },
  7419. /**
  7420. * Renames the specified element and keeps its attributes and children.
  7421. *
  7422. * @method rename
  7423. * @param {Element} elm Element to rename.
  7424. * @param {String} name Name of the new element.
  7425. * @return {Element} New element or the old element if it needed renaming.
  7426. */
  7427. rename: function(elm, name) {
  7428. var self = this, newElm;
  7429. if (elm.nodeName != name.toUpperCase()) {
  7430. // Rename block element
  7431. newElm = self.create(name);
  7432. // Copy attribs to new block
  7433. each(self.getAttribs(elm), function(attrNode) {
  7434. self.setAttrib(newElm, attrNode.nodeName, self.getAttrib(elm, attrNode.nodeName));
  7435. });
  7436. // Replace block
  7437. self.replace(newElm, elm, 1);
  7438. }
  7439. return newElm || elm;
  7440. },
  7441. /**
  7442. * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
  7443. *
  7444. * @method findCommonAncestor
  7445. * @param {Element} a Element to find common ancestor of.
  7446. * @param {Element} b Element to find common ancestor of.
  7447. * @return {Element} Common ancestor element of the two input elements.
  7448. */
  7449. findCommonAncestor: function(a, b) {
  7450. var ps = a, pe;
  7451. while (ps) {
  7452. pe = b;
  7453. while (pe && ps != pe) {
  7454. pe = pe.parentNode;
  7455. }
  7456. if (ps == pe) {
  7457. break;
  7458. }
  7459. ps = ps.parentNode;
  7460. }
  7461. if (!ps && a.ownerDocument) {
  7462. return a.ownerDocument.documentElement;
  7463. }
  7464. return ps;
  7465. },
  7466. /**
  7467. * Parses the specified RGB color value and returns a hex version of that color.
  7468. *
  7469. * @method toHex
  7470. * @param {String} rgbVal RGB string value like rgb(1,2,3)
  7471. * @return {String} Hex version of that RGB value like #FF00FF.
  7472. */
  7473. toHex: function(rgbVal) {
  7474. return this.styles.toHex(Tools.trim(rgbVal));
  7475. },
  7476. /**
  7477. * Executes the specified function on the element by id or dom element node or array of elements/id.
  7478. *
  7479. * @method run
  7480. * @param {String/Element/Array} elm ID or DOM element object or array with ids or elements.
  7481. * @param {function} func Function to execute for each item.
  7482. * @param {Object} scope Optional scope to execute the function in.
  7483. * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
  7484. */
  7485. run: function(elm, func, scope) {
  7486. var self = this, result;
  7487. if (typeof elm === 'string') {
  7488. elm = self.get(elm);
  7489. }
  7490. if (!elm) {
  7491. return false;
  7492. }
  7493. scope = scope || this;
  7494. if (!elm.nodeType && (elm.length || elm.length === 0)) {
  7495. result = [];
  7496. each(elm, function(elm, i) {
  7497. if (elm) {
  7498. if (typeof elm == 'string') {
  7499. elm = self.get(elm);
  7500. }
  7501. result.push(func.call(scope, elm, i));
  7502. }
  7503. });
  7504. return result;
  7505. }
  7506. return func.call(scope, elm);
  7507. },
  7508. /**
  7509. * Returns a NodeList with attributes for the element.
  7510. *
  7511. * @method getAttribs
  7512. * @param {HTMLElement/string} elm Element node or string id to get attributes from.
  7513. * @return {NodeList} NodeList with attributes.
  7514. */
  7515. getAttribs: function(elm) {
  7516. var attrs;
  7517. elm = this.get(elm);
  7518. if (!elm) {
  7519. return [];
  7520. }
  7521. if (isIE) {
  7522. attrs = [];
  7523. // Object will throw exception in IE
  7524. if (elm.nodeName == 'OBJECT') {
  7525. return elm.attributes;
  7526. }
  7527. // IE doesn't keep the selected attribute if you clone option elements
  7528. if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) {
  7529. attrs.push({specified: 1, nodeName: 'selected'});
  7530. }
  7531. // It's crazy that this is faster in IE but it's because it returns all attributes all the time
  7532. var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;
  7533. elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) {
  7534. attrs.push({specified: 1, nodeName: a});
  7535. });
  7536. return attrs;
  7537. }
  7538. return elm.attributes;
  7539. },
  7540. /**
  7541. * Returns true/false if the specified node is to be considered empty or not.
  7542. *
  7543. * @example
  7544. * tinymce.DOM.isEmpty(node, {img: true});
  7545. * @method isEmpty
  7546. * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
  7547. * @return {Boolean} true/false if the node is empty or not.
  7548. */
  7549. isEmpty: function(node, elements) {
  7550. var self = this, i, attributes, type, walker, name, brCount = 0;
  7551. node = node.firstChild;
  7552. if (node) {
  7553. walker = new TreeWalker(node, node.parentNode);
  7554. elements = elements || (self.schema ? self.schema.getNonEmptyElements() : null);
  7555. do {
  7556. type = node.nodeType;
  7557. if (type === 1) {
  7558. // Ignore bogus elements
  7559. if (node.getAttribute('data-mce-bogus')) {
  7560. continue;
  7561. }
  7562. // Keep empty elements like <img />
  7563. name = node.nodeName.toLowerCase();
  7564. if (elements && elements[name]) {
  7565. // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
  7566. if (name === 'br') {
  7567. brCount++;
  7568. continue;
  7569. }
  7570. return false;
  7571. }
  7572. // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
  7573. attributes = self.getAttribs(node);
  7574. i = attributes.length;
  7575. while (i--) {
  7576. name = attributes[i].nodeName;
  7577. if (name === "name" || name === 'data-mce-bookmark') {
  7578. return false;
  7579. }
  7580. }
  7581. }
  7582. // Keep comment nodes
  7583. if (type == 8) {
  7584. return false;
  7585. }
  7586. // Keep non whitespace text nodes
  7587. if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) {
  7588. return false;
  7589. }
  7590. } while ((node = walker.next()));
  7591. }
  7592. return brCount <= 1;
  7593. },
  7594. /**
  7595. * Creates a new DOM Range object. This will use the native DOM Range API if it's
  7596. * available. If it's not, it will fall back to the custom TinyMCE implementation.
  7597. *
  7598. * @method createRng
  7599. * @return {DOMRange} DOM Range object.
  7600. * @example
  7601. * var rng = tinymce.DOM.createRng();
  7602. * alert(rng.startContainer + "," + rng.startOffset);
  7603. */
  7604. createRng: function() {
  7605. var doc = this.doc;
  7606. return doc.createRange ? doc.createRange() : new Range(this);
  7607. },
  7608. /**
  7609. * Returns the index of the specified node within its parent.
  7610. *
  7611. * @method nodeIndex
  7612. * @param {Node} node Node to look for.
  7613. * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
  7614. * @return {Number} Index of the specified node.
  7615. */
  7616. nodeIndex: nodeIndex,
  7617. /**
  7618. * Splits an element into two new elements and places the specified split
  7619. * element or elements between the new ones. For example splitting the paragraph at the bold element in
  7620. * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
  7621. *
  7622. * @method split
  7623. * @param {Element} parentElm Parent element to split.
  7624. * @param {Element} splitElm Element to split at.
  7625. * @param {Element} replacementElm Optional replacement element to replace the split element with.
  7626. * @return {Element} Returns the split element or the replacement element if that is specified.
  7627. */
  7628. split: function(parentElm, splitElm, replacementElm) {
  7629. var self = this, r = self.createRng(), bef, aft, pa;
  7630. // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
  7631. // but we don't want that in our code since it serves no purpose for the end user
  7632. // For example splitting this html at the bold element:
  7633. // <p>text 1<span><b>CHOP</b></span>text 2</p>
  7634. // would produce:
  7635. // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
  7636. // this function will then trim off empty edges and produce:
  7637. // <p>text 1</p><b>CHOP</b><p>text 2</p>
  7638. function trimNode(node) {
  7639. var i, children = node.childNodes, type = node.nodeType;
  7640. function surroundedBySpans(node) {
  7641. var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
  7642. var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
  7643. return previousIsSpan && nextIsSpan;
  7644. }
  7645. if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') {
  7646. return;
  7647. }
  7648. for (i = children.length - 1; i >= 0; i--) {
  7649. trimNode(children[i]);
  7650. }
  7651. if (type != 9) {
  7652. // Keep non whitespace text nodes
  7653. if (type == 3 && node.nodeValue.length > 0) {
  7654. // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
  7655. // Also keep text nodes with only spaces if surrounded by spans.
  7656. // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
  7657. var trimmedLength = trim(node.nodeValue).length;
  7658. if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) {
  7659. return;
  7660. }
  7661. } else if (type == 1) {
  7662. // If the only child is a bookmark then move it up
  7663. children = node.childNodes;
  7664. // TODO fix this complex if
  7665. if (children.length == 1 && children[0] && children[0].nodeType == 1 &&
  7666. children[0].getAttribute('data-mce-type') == 'bookmark') {
  7667. node.parentNode.insertBefore(children[0], node);
  7668. }
  7669. // Keep non empty elements or img, hr etc
  7670. if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) {
  7671. return;
  7672. }
  7673. }
  7674. self.remove(node);
  7675. }
  7676. return node;
  7677. }
  7678. if (parentElm && splitElm) {
  7679. // Get before chunk
  7680. r.setStart(parentElm.parentNode, self.nodeIndex(parentElm));
  7681. r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm));
  7682. bef = r.extractContents();
  7683. // Get after chunk
  7684. r = self.createRng();
  7685. r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1);
  7686. r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1);
  7687. aft = r.extractContents();
  7688. // Insert before chunk
  7689. pa = parentElm.parentNode;
  7690. pa.insertBefore(trimNode(bef), parentElm);
  7691. // Insert middle chunk
  7692. if (replacementElm) {
  7693. pa.insertBefore(replacementElm, parentElm);
  7694. //pa.replaceChild(replacementElm, splitElm);
  7695. } else {
  7696. pa.insertBefore(splitElm, parentElm);
  7697. }
  7698. // Insert after chunk
  7699. pa.insertBefore(trimNode(aft), parentElm);
  7700. self.remove(parentElm);
  7701. return replacementElm || splitElm;
  7702. }
  7703. },
  7704. /**
  7705. * Adds an event handler to the specified object.
  7706. *
  7707. * @method bind
  7708. * @param {Element/Document/Window/Array} target Target element to bind events to.
  7709. * handler to or an array of elements/ids/documents.
  7710. * @param {String} name Name of event handler to add, for example: click.
  7711. * @param {function} func Function to execute when the event occurs.
  7712. * @param {Object} scope Optional scope to execute the function in.
  7713. * @return {function} Function callback handler the same as the one passed in.
  7714. */
  7715. bind: function(target, name, func, scope) {
  7716. var self = this;
  7717. if (Tools.isArray(target)) {
  7718. var i = target.length;
  7719. while (i--) {
  7720. target[i] = self.bind(target[i], name, func, scope);
  7721. }
  7722. return target;
  7723. }
  7724. // Collect all window/document events bound by editor instance
  7725. if (self.settings.collect && (target === self.doc || target === self.win)) {
  7726. self.boundEvents.push([target, name, func, scope]);
  7727. }
  7728. return self.events.bind(target, name, func, scope || self);
  7729. },
  7730. /**
  7731. * Removes the specified event handler by name and function from an element or collection of elements.
  7732. *
  7733. * @method unbind
  7734. * @param {Element/Document/Window/Array} target Target element to unbind events on.
  7735. * @param {String} name Event handler name, for example: "click"
  7736. * @param {function} func Function to remove.
  7737. * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
  7738. * were passed in.
  7739. */
  7740. unbind: function(target, name, func) {
  7741. var self = this, i;
  7742. if (Tools.isArray(target)) {
  7743. i = target.length;
  7744. while (i--) {
  7745. target[i] = self.unbind(target[i], name, func);
  7746. }
  7747. return target;
  7748. }
  7749. // Remove any bound events matching the input
  7750. if (self.boundEvents && (target === self.doc || target === self.win)) {
  7751. i = self.boundEvents.length;
  7752. while (i--) {
  7753. var item = self.boundEvents[i];
  7754. if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) {
  7755. this.events.unbind(item[0], item[1], item[2]);
  7756. }
  7757. }
  7758. }
  7759. return this.events.unbind(target, name, func);
  7760. },
  7761. /**
  7762. * Fires the specified event name with object on target.
  7763. *
  7764. * @method fire
  7765. * @param {Node/Document/Window} target Target element or object to fire event on.
  7766. * @param {String} name Name of the event to fire.
  7767. * @param {Object} evt Event object to send.
  7768. * @return {Event} Event object.
  7769. */
  7770. fire: function(target, name, evt) {
  7771. return this.events.fire(target, name, evt);
  7772. },
  7773. // Returns the content editable state of a node
  7774. getContentEditable: function(node) {
  7775. var contentEditable;
  7776. // Check type
  7777. if (!node || node.nodeType != 1) {
  7778. return null;
  7779. }
  7780. // Check for fake content editable
  7781. contentEditable = node.getAttribute("data-mce-contenteditable");
  7782. if (contentEditable && contentEditable !== "inherit") {
  7783. return contentEditable;
  7784. }
  7785. // Check for real content editable
  7786. return node.contentEditable !== "inherit" ? node.contentEditable : null;
  7787. },
  7788. getContentEditableParent: function(node) {
  7789. var root = this.getRoot(), state = null;
  7790. for (; node && node !== root; node = node.parentNode) {
  7791. state = this.getContentEditable(node);
  7792. if (state !== null) {
  7793. break;
  7794. }
  7795. }
  7796. return state;
  7797. },
  7798. /**
  7799. * Destroys all internal references to the DOM to solve IE leak issues.
  7800. *
  7801. * @method destroy
  7802. */
  7803. destroy: function() {
  7804. var self = this;
  7805. // Unbind all events bound to window/document by editor instance
  7806. if (self.boundEvents) {
  7807. var i = self.boundEvents.length;
  7808. while (i--) {
  7809. var item = self.boundEvents[i];
  7810. this.events.unbind(item[0], item[1], item[2]);
  7811. }
  7812. self.boundEvents = null;
  7813. }
  7814. // Restore sizzle document to window.document
  7815. // Since the current document might be removed producing "Permission denied" on IE see #6325
  7816. if (Sizzle.setDocument) {
  7817. Sizzle.setDocument();
  7818. }
  7819. self.win = self.doc = self.root = self.events = self.frag = null;
  7820. },
  7821. isChildOf: function(node, parent) {
  7822. while (node) {
  7823. if (parent === node) {
  7824. return true;
  7825. }
  7826. node = node.parentNode;
  7827. }
  7828. return false;
  7829. },
  7830. // #ifdef debug
  7831. dumpRng: function(r) {
  7832. return (
  7833. 'startContainer: ' + r.startContainer.nodeName +
  7834. ', startOffset: ' + r.startOffset +
  7835. ', endContainer: ' + r.endContainer.nodeName +
  7836. ', endOffset: ' + r.endOffset
  7837. );
  7838. },
  7839. // #endif
  7840. _findSib: function(node, selector, name) {
  7841. var self = this, func = selector;
  7842. if (node) {
  7843. // If expression make a function of it using is
  7844. if (typeof func == 'string') {
  7845. func = function(node) {
  7846. return self.is(node, selector);
  7847. };
  7848. }
  7849. // Loop all siblings
  7850. for (node = node[name]; node; node = node[name]) {
  7851. if (func(node)) {
  7852. return node;
  7853. }
  7854. }
  7855. }
  7856. return null;
  7857. }
  7858. };
  7859. /**
  7860. * Instance of DOMUtils for the current document.
  7861. *
  7862. * @static
  7863. * @property DOM
  7864. * @type tinymce.dom.DOMUtils
  7865. * @example
  7866. * // Example of how to add a class to some element by id
  7867. * tinymce.DOM.addClass('someid', 'someclass');
  7868. */
  7869. DOMUtils.DOM = new DOMUtils(document);
  7870. DOMUtils.nodeIndex = nodeIndex;
  7871. return DOMUtils;
  7872. });
  7873. // Included from: js/tinymce/classes/dom/ScriptLoader.js
  7874. /**
  7875. * ScriptLoader.js
  7876. *
  7877. * Released under LGPL License.
  7878. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  7879. *
  7880. * License: http://www.tinymce.com/license
  7881. * Contributing: http://www.tinymce.com/contributing
  7882. */
  7883. /*globals console*/
  7884. /**
  7885. * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks
  7886. * when various items gets loaded. This class is useful to load external JavaScript files.
  7887. *
  7888. * @class tinymce.dom.ScriptLoader
  7889. * @example
  7890. * // Load a script from a specific URL using the global script loader
  7891. * tinymce.ScriptLoader.load('somescript.js');
  7892. *
  7893. * // Load a script using a unique instance of the script loader
  7894. * var scriptLoader = new tinymce.dom.ScriptLoader();
  7895. *
  7896. * scriptLoader.load('somescript.js');
  7897. *
  7898. * // Load multiple scripts
  7899. * var scriptLoader = new tinymce.dom.ScriptLoader();
  7900. *
  7901. * scriptLoader.add('somescript1.js');
  7902. * scriptLoader.add('somescript2.js');
  7903. * scriptLoader.add('somescript3.js');
  7904. *
  7905. * scriptLoader.loadQueue(function() {
  7906. * alert('All scripts are now loaded.');
  7907. * });
  7908. */
  7909. define("tinymce/dom/ScriptLoader", [
  7910. "tinymce/dom/DOMUtils",
  7911. "tinymce/util/Tools"
  7912. ], function(DOMUtils, Tools) {
  7913. var DOM = DOMUtils.DOM;
  7914. var each = Tools.each, grep = Tools.grep;
  7915. function ScriptLoader() {
  7916. var QUEUED = 0,
  7917. LOADING = 1,
  7918. LOADED = 2,
  7919. states = {},
  7920. queue = [],
  7921. scriptLoadedCallbacks = {},
  7922. queueLoadedCallbacks = [],
  7923. loading = 0,
  7924. undef;
  7925. /**
  7926. * Loads a specific script directly without adding it to the load queue.
  7927. *
  7928. * @method load
  7929. * @param {String} url Absolute URL to script to add.
  7930. * @param {function} callback Optional callback function to execute ones this script gets loaded.
  7931. */
  7932. function loadScript(url, callback) {
  7933. var dom = DOM, elm, id;
  7934. // Execute callback when script is loaded
  7935. function done() {
  7936. dom.remove(id);
  7937. if (elm) {
  7938. elm.onreadystatechange = elm.onload = elm = null;
  7939. }
  7940. callback();
  7941. }
  7942. function error() {
  7943. /*eslint no-console:0 */
  7944. // Report the error so it's easier for people to spot loading errors
  7945. if (typeof console !== "undefined" && console.log) {
  7946. console.log("Failed to load: " + url);
  7947. }
  7948. // We can't mark it as done if there is a load error since
  7949. // A) We don't want to produce 404 errors on the server and
  7950. // B) the onerror event won't fire on all browsers.
  7951. // done();
  7952. }
  7953. id = dom.uniqueId();
  7954. // Create new script element
  7955. elm = document.createElement('script');
  7956. elm.id = id;
  7957. elm.type = 'text/javascript';
  7958. elm.src = Tools._addCacheSuffix(url);
  7959. // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
  7960. if ("onreadystatechange" in elm) {
  7961. elm.onreadystatechange = function() {
  7962. if (/loaded|complete/.test(elm.readyState)) {
  7963. done();
  7964. }
  7965. };
  7966. } else {
  7967. elm.onload = done;
  7968. }
  7969. // Add onerror event will get fired on some browsers but not all of them
  7970. elm.onerror = error;
  7971. // Add script to document
  7972. (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
  7973. }
  7974. /**
  7975. * Returns true/false if a script has been loaded or not.
  7976. *
  7977. * @method isDone
  7978. * @param {String} url URL to check for.
  7979. * @return {Boolean} true/false if the URL is loaded.
  7980. */
  7981. this.isDone = function(url) {
  7982. return states[url] == LOADED;
  7983. };
  7984. /**
  7985. * Marks a specific script to be loaded. This can be useful if a script got loaded outside
  7986. * the script loader or to skip it from loading some script.
  7987. *
  7988. * @method markDone
  7989. * @param {string} url Absolute URL to the script to mark as loaded.
  7990. */
  7991. this.markDone = function(url) {
  7992. states[url] = LOADED;
  7993. };
  7994. /**
  7995. * Adds a specific script to the load queue of the script loader.
  7996. *
  7997. * @method add
  7998. * @param {String} url Absolute URL to script to add.
  7999. * @param {function} callback Optional callback function to execute ones this script gets loaded.
  8000. * @param {Object} scope Optional scope to execute callback in.
  8001. */
  8002. this.add = this.load = function(url, callback, scope) {
  8003. var state = states[url];
  8004. // Add url to load queue
  8005. if (state == undef) {
  8006. queue.push(url);
  8007. states[url] = QUEUED;
  8008. }
  8009. if (callback) {
  8010. // Store away callback for later execution
  8011. if (!scriptLoadedCallbacks[url]) {
  8012. scriptLoadedCallbacks[url] = [];
  8013. }
  8014. scriptLoadedCallbacks[url].push({
  8015. func: callback,
  8016. scope: scope || this
  8017. });
  8018. }
  8019. };
  8020. this.remove = function(url) {
  8021. delete states[url];
  8022. delete scriptLoadedCallbacks[url];
  8023. };
  8024. /**
  8025. * Starts the loading of the queue.
  8026. *
  8027. * @method loadQueue
  8028. * @param {function} callback Optional callback to execute when all queued items are loaded.
  8029. * @param {Object} scope Optional scope to execute the callback in.
  8030. */
  8031. this.loadQueue = function(callback, scope) {
  8032. this.loadScripts(queue, callback, scope);
  8033. };
  8034. /**
  8035. * Loads the specified queue of files and executes the callback ones they are loaded.
  8036. * This method is generally not used outside this class but it might be useful in some scenarios.
  8037. *
  8038. * @method loadScripts
  8039. * @param {Array} scripts Array of queue items to load.
  8040. * @param {function} callback Optional callback to execute ones all items are loaded.
  8041. * @param {Object} scope Optional scope to execute callback in.
  8042. */
  8043. this.loadScripts = function(scripts, callback, scope) {
  8044. var loadScripts;
  8045. function execScriptLoadedCallbacks(url) {
  8046. // Execute URL callback functions
  8047. each(scriptLoadedCallbacks[url], function(callback) {
  8048. callback.func.call(callback.scope);
  8049. });
  8050. scriptLoadedCallbacks[url] = undef;
  8051. }
  8052. queueLoadedCallbacks.push({
  8053. func: callback,
  8054. scope: scope || this
  8055. });
  8056. loadScripts = function() {
  8057. var loadingScripts = grep(scripts);
  8058. // Current scripts has been handled
  8059. scripts.length = 0;
  8060. // Load scripts that needs to be loaded
  8061. each(loadingScripts, function(url) {
  8062. // Script is already loaded then execute script callbacks directly
  8063. if (states[url] == LOADED) {
  8064. execScriptLoadedCallbacks(url);
  8065. return;
  8066. }
  8067. // Is script not loading then start loading it
  8068. if (states[url] != LOADING) {
  8069. states[url] = LOADING;
  8070. loading++;
  8071. loadScript(url, function() {
  8072. states[url] = LOADED;
  8073. loading--;
  8074. execScriptLoadedCallbacks(url);
  8075. // Load more scripts if they where added by the recently loaded script
  8076. loadScripts();
  8077. });
  8078. }
  8079. });
  8080. // No scripts are currently loading then execute all pending queue loaded callbacks
  8081. if (!loading) {
  8082. each(queueLoadedCallbacks, function(callback) {
  8083. callback.func.call(callback.scope);
  8084. });
  8085. queueLoadedCallbacks.length = 0;
  8086. }
  8087. };
  8088. loadScripts();
  8089. };
  8090. }
  8091. ScriptLoader.ScriptLoader = new ScriptLoader();
  8092. return ScriptLoader;
  8093. });
  8094. // Included from: js/tinymce/classes/AddOnManager.js
  8095. /**
  8096. * AddOnManager.js
  8097. *
  8098. * Released under LGPL License.
  8099. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  8100. *
  8101. * License: http://www.tinymce.com/license
  8102. * Contributing: http://www.tinymce.com/contributing
  8103. */
  8104. /**
  8105. * This class handles the loading of themes/plugins or other add-ons and their language packs.
  8106. *
  8107. * @class tinymce.AddOnManager
  8108. */
  8109. define("tinymce/AddOnManager", [
  8110. "tinymce/dom/ScriptLoader",
  8111. "tinymce/util/Tools"
  8112. ], function(ScriptLoader, Tools) {
  8113. var each = Tools.each;
  8114. function AddOnManager() {
  8115. var self = this;
  8116. self.items = [];
  8117. self.urls = {};
  8118. self.lookup = {};
  8119. }
  8120. AddOnManager.prototype = {
  8121. /**
  8122. * Returns the specified add on by the short name.
  8123. *
  8124. * @method get
  8125. * @param {String} name Add-on to look for.
  8126. * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined.
  8127. */
  8128. get: function(name) {
  8129. if (this.lookup[name]) {
  8130. return this.lookup[name].instance;
  8131. }
  8132. return undefined;
  8133. },
  8134. dependencies: function(name) {
  8135. var result;
  8136. if (this.lookup[name]) {
  8137. result = this.lookup[name].dependencies;
  8138. }
  8139. return result || [];
  8140. },
  8141. /**
  8142. * Loads a language pack for the specified add-on.
  8143. *
  8144. * @method requireLangPack
  8145. * @param {String} name Short name of the add-on.
  8146. * @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
  8147. */
  8148. requireLangPack: function(name, languages) {
  8149. var language = AddOnManager.language;
  8150. if (language && AddOnManager.languageLoad !== false) {
  8151. if (languages) {
  8152. languages = ',' + languages + ',';
  8153. // Load short form sv.js or long form sv_SE.js
  8154. if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) {
  8155. language = language.substr(0, 2);
  8156. } else if (languages.indexOf(',' + language + ',') == -1) {
  8157. return;
  8158. }
  8159. }
  8160. ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js');
  8161. }
  8162. },
  8163. /**
  8164. * Adds a instance of the add-on by it's short name.
  8165. *
  8166. * @method add
  8167. * @param {String} id Short name/id for the add-on.
  8168. * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add.
  8169. * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
  8170. * @example
  8171. * // Create a simple plugin
  8172. * tinymce.create('tinymce.plugins.TestPlugin', {
  8173. * TestPlugin: function(ed, url) {
  8174. * ed.on('click', function(e) {
  8175. * ed.windowManager.alert('Hello World!');
  8176. * });
  8177. * }
  8178. * });
  8179. *
  8180. * // Register plugin using the add method
  8181. * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin);
  8182. *
  8183. * // Initialize TinyMCE
  8184. * tinymce.init({
  8185. * ...
  8186. * plugins: '-test' // Init the plugin but don't try to load it
  8187. * });
  8188. */
  8189. add: function(id, addOn, dependencies) {
  8190. this.items.push(addOn);
  8191. this.lookup[id] = {instance: addOn, dependencies: dependencies};
  8192. return addOn;
  8193. },
  8194. remove: function(name) {
  8195. delete this.urls[name];
  8196. delete this.lookup[name];
  8197. },
  8198. createUrl: function(baseUrl, dep) {
  8199. if (typeof dep === "object") {
  8200. return dep;
  8201. }
  8202. return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
  8203. },
  8204. /**
  8205. * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url.
  8206. * This should be used in development mode. A new compressor/javascript munger process will ensure that the
  8207. * components are put together into the plugin.js file and compressed correctly.
  8208. *
  8209. * @method addComponents
  8210. * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins).
  8211. * @param {Array} scripts Array containing the names of the scripts to load.
  8212. */
  8213. addComponents: function(pluginName, scripts) {
  8214. var pluginUrl = this.urls[pluginName];
  8215. each(scripts, function(script) {
  8216. ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script);
  8217. });
  8218. },
  8219. /**
  8220. * Loads an add-on from a specific url.
  8221. *
  8222. * @method load
  8223. * @param {String} name Short name of the add-on that gets loaded.
  8224. * @param {String} addOnUrl URL to the add-on that will get loaded.
  8225. * @param {function} callback Optional callback to execute ones the add-on is loaded.
  8226. * @param {Object} scope Optional scope to execute the callback in.
  8227. * @example
  8228. * // Loads a plugin from an external URL
  8229. * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
  8230. *
  8231. * // Initialize TinyMCE
  8232. * tinymce.init({
  8233. * ...
  8234. * plugins: '-myplugin' // Don't try to load it again
  8235. * });
  8236. */
  8237. load: function(name, addOnUrl, callback, scope) {
  8238. var self = this, url = addOnUrl;
  8239. function loadDependencies() {
  8240. var dependencies = self.dependencies(name);
  8241. each(dependencies, function(dep) {
  8242. var newUrl = self.createUrl(addOnUrl, dep);
  8243. self.load(newUrl.resource, newUrl, undefined, undefined);
  8244. });
  8245. if (callback) {
  8246. if (scope) {
  8247. callback.call(scope);
  8248. } else {
  8249. callback.call(ScriptLoader);
  8250. }
  8251. }
  8252. }
  8253. if (self.urls[name]) {
  8254. return;
  8255. }
  8256. if (typeof addOnUrl === "object") {
  8257. url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
  8258. }
  8259. if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
  8260. url = AddOnManager.baseURL + '/' + url;
  8261. }
  8262. self.urls[name] = url.substring(0, url.lastIndexOf('/'));
  8263. if (self.lookup[name]) {
  8264. loadDependencies();
  8265. } else {
  8266. ScriptLoader.ScriptLoader.add(url, loadDependencies, scope);
  8267. }
  8268. }
  8269. };
  8270. AddOnManager.PluginManager = new AddOnManager();
  8271. AddOnManager.ThemeManager = new AddOnManager();
  8272. return AddOnManager;
  8273. });
  8274. /**
  8275. * TinyMCE theme class.
  8276. *
  8277. * @class tinymce.Theme
  8278. */
  8279. /**
  8280. * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc.
  8281. *
  8282. * @method renderUI
  8283. * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance.
  8284. * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight.
  8285. */
  8286. /**
  8287. * Plugin base class, this is a pseudo class that describes how a plugin is to be created for TinyMCE. The methods below are all optional.
  8288. *
  8289. * @class tinymce.Plugin
  8290. * @example
  8291. * tinymce.PluginManager.add('example', function(editor, url) {
  8292. * // Add a button that opens a window
  8293. * editor.addButton('example', {
  8294. * text: 'My button',
  8295. * icon: false,
  8296. * onclick: function() {
  8297. * // Open window
  8298. * editor.windowManager.open({
  8299. * title: 'Example plugin',
  8300. * body: [
  8301. * {type: 'textbox', name: 'title', label: 'Title'}
  8302. * ],
  8303. * onsubmit: function(e) {
  8304. * // Insert content when the window form is submitted
  8305. * editor.insertContent('Title: ' + e.data.title);
  8306. * }
  8307. * });
  8308. * }
  8309. * });
  8310. *
  8311. * // Adds a menu item to the tools menu
  8312. * editor.addMenuItem('example', {
  8313. * text: 'Example plugin',
  8314. * context: 'tools',
  8315. * onclick: function() {
  8316. * // Open window with a specific url
  8317. * editor.windowManager.open({
  8318. * title: 'TinyMCE site',
  8319. * url: 'http://www.tinymce.com',
  8320. * width: 800,
  8321. * height: 600,
  8322. * buttons: [{
  8323. * text: 'Close',
  8324. * onclick: 'close'
  8325. * }]
  8326. * });
  8327. * }
  8328. * });
  8329. * });
  8330. */
  8331. // Included from: js/tinymce/classes/dom/NodeType.js
  8332. /**
  8333. * NodeType.js
  8334. *
  8335. * Released under LGPL License.
  8336. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  8337. *
  8338. * License: http://www.tinymce.com/license
  8339. * Contributing: http://www.tinymce.com/contributing
  8340. */
  8341. /**
  8342. * Contains various node validation functions.
  8343. *
  8344. * @private
  8345. * @class tinymce.dom.NodeType
  8346. */
  8347. define("tinymce/dom/NodeType", [], function() {
  8348. function isNodeType(type) {
  8349. return function(node) {
  8350. return !!node && node.nodeType == type;
  8351. };
  8352. }
  8353. var isElement = isNodeType(1);
  8354. function matchNodeNames(names) {
  8355. names = names.toLowerCase().split(' ');
  8356. return function(node) {
  8357. var i, name;
  8358. if (node && node.nodeType) {
  8359. name = node.nodeName.toLowerCase();
  8360. for (i = 0; i < names.length; i++) {
  8361. if (name === names[i]) {
  8362. return true;
  8363. }
  8364. }
  8365. }
  8366. return false;
  8367. };
  8368. }
  8369. function matchStyleValues(name, values) {
  8370. values = values.toLowerCase().split(' ');
  8371. return function(node) {
  8372. var i, cssValue;
  8373. if (isElement(node)) {
  8374. for (i = 0; i < values.length; i++) {
  8375. cssValue = getComputedStyle(node, null).getPropertyValue(name);
  8376. if (cssValue === values[i]) {
  8377. return true;
  8378. }
  8379. }
  8380. }
  8381. return false;
  8382. };
  8383. }
  8384. function hasPropValue(propName, propValue) {
  8385. return function(node) {
  8386. return isElement(node) && node[propName] === propValue;
  8387. };
  8388. }
  8389. function hasAttributeValue(attrName, attrValue) {
  8390. return function(node) {
  8391. return isElement(node) && node.getAttribute(attrName) === attrValue;
  8392. };
  8393. }
  8394. function isBogus(node) {
  8395. return isElement(node) && node.hasAttribute('data-mce-bogus');
  8396. }
  8397. function hasContentEditableState(value) {
  8398. return function(node) {
  8399. if (isElement(node)) {
  8400. if (node.contentEditable === value) {
  8401. return true;
  8402. }
  8403. if (node.getAttribute('data-mce-contenteditable') === value) {
  8404. return true;
  8405. }
  8406. }
  8407. return false;
  8408. };
  8409. }
  8410. return {
  8411. isText: isNodeType(3),
  8412. isElement: isElement,
  8413. isComment: isNodeType(8),
  8414. isBr: matchNodeNames('br'),
  8415. isContentEditableTrue: hasContentEditableState('true'),
  8416. isContentEditableFalse: hasContentEditableState('false'),
  8417. matchNodeNames: matchNodeNames,
  8418. hasPropValue: hasPropValue,
  8419. hasAttributeValue: hasAttributeValue,
  8420. matchStyleValues: matchStyleValues,
  8421. isBogus: isBogus
  8422. };
  8423. });
  8424. // Included from: js/tinymce/classes/text/Zwsp.js
  8425. /**
  8426. * Zwsp.js
  8427. *
  8428. * Released under LGPL License.
  8429. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  8430. *
  8431. * License: http://www.tinymce.com/license
  8432. * Contributing: http://www.tinymce.com/contributing
  8433. */
  8434. /**
  8435. * ....
  8436. *
  8437. * @private
  8438. * @class tinymce.text.Zwsp
  8439. * @example
  8440. * var isZwsp = Zwsp.isZwsp('\u200b');
  8441. * var abc = Zwsp.trim('a\u200bc');
  8442. */
  8443. define("tinymce/text/Zwsp", [], function() {
  8444. var ZWSP = '\u200b';
  8445. function isZwsp(chr) {
  8446. return chr == ZWSP;
  8447. }
  8448. function trim(str) {
  8449. return str.replace(new RegExp(ZWSP, 'g'), '');
  8450. }
  8451. return {
  8452. isZwsp: isZwsp,
  8453. ZWSP: ZWSP,
  8454. trim: trim
  8455. };
  8456. });
  8457. // Included from: js/tinymce/classes/caret/CaretContainer.js
  8458. /**
  8459. * CaretContainer.js
  8460. *
  8461. * Released under LGPL License.
  8462. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  8463. *
  8464. * License: http://www.tinymce.com/license
  8465. * Contributing: http://www.tinymce.com/contributing
  8466. */
  8467. /**
  8468. * This module handles caret containers. A caret container is a node that
  8469. * holds the caret for positional purposes.
  8470. *
  8471. * @private
  8472. * @class tinymce.caret.CaretContainer
  8473. */
  8474. define("tinymce/caret/CaretContainer", [
  8475. "tinymce/dom/NodeType",
  8476. "tinymce/text/Zwsp"
  8477. ], function(NodeType, Zwsp) {
  8478. var isElement = NodeType.isElement,
  8479. isText = NodeType.isText;
  8480. function isCaretContainerBlock(node) {
  8481. if (isText(node)) {
  8482. node = node.parentNode;
  8483. }
  8484. return isElement(node) && node.hasAttribute('data-mce-caret');
  8485. }
  8486. function isCaretContainerInline(node) {
  8487. return isText(node) && Zwsp.isZwsp(node.data);
  8488. }
  8489. function isCaretContainer(node) {
  8490. return isCaretContainerBlock(node) || isCaretContainerInline(node);
  8491. }
  8492. function insertInline(node, before) {
  8493. var doc, sibling, textNode, parentNode;
  8494. doc = node.ownerDocument;
  8495. textNode = doc.createTextNode(Zwsp.ZWSP);
  8496. parentNode = node.parentNode;
  8497. if (!before) {
  8498. sibling = node.nextSibling;
  8499. if (isText(sibling)) {
  8500. if (isCaretContainer(sibling)) {
  8501. return sibling;
  8502. }
  8503. if (startsWithCaretContainer(sibling)) {
  8504. sibling.splitText(1);
  8505. return sibling;
  8506. }
  8507. }
  8508. if (node.nextSibling) {
  8509. parentNode.insertBefore(textNode, node.nextSibling);
  8510. } else {
  8511. parentNode.appendChild(textNode);
  8512. }
  8513. } else {
  8514. sibling = node.previousSibling;
  8515. if (isText(sibling)) {
  8516. if (isCaretContainer(sibling)) {
  8517. return sibling;
  8518. }
  8519. if (endsWithCaretContainer(sibling)) {
  8520. return sibling.splitText(sibling.data.length - 1);
  8521. }
  8522. }
  8523. parentNode.insertBefore(textNode, node);
  8524. }
  8525. return textNode;
  8526. }
  8527. function insertBlock(blockName, node, before) {
  8528. var doc, blockNode, parentNode;
  8529. doc = node.ownerDocument;
  8530. blockNode = doc.createElement(blockName);
  8531. blockNode.setAttribute('data-mce-caret', before ? 'before' : 'after');
  8532. blockNode.setAttribute('data-mce-bogus', 'all');
  8533. blockNode.appendChild(doc.createTextNode('\u00a0'));
  8534. parentNode = node.parentNode;
  8535. if (!before) {
  8536. if (node.nextSibling) {
  8537. parentNode.insertBefore(blockNode, node.nextSibling);
  8538. } else {
  8539. parentNode.appendChild(blockNode);
  8540. }
  8541. } else {
  8542. parentNode.insertBefore(blockNode, node);
  8543. }
  8544. return blockNode;
  8545. }
  8546. function remove(caretContainerNode) {
  8547. var text;
  8548. if (isElement(caretContainerNode) && isCaretContainer(caretContainerNode)) {
  8549. if (caretContainerNode.innerHTML != '&nbsp;') {
  8550. caretContainerNode.removeAttribute('data-mce-caret');
  8551. } else {
  8552. if (caretContainerNode.parentNode) {
  8553. caretContainerNode.parentNode.removeChild(caretContainerNode);
  8554. }
  8555. }
  8556. }
  8557. if (isText(caretContainerNode)) {
  8558. text = Zwsp.trim(caretContainerNode.data);
  8559. if (text.length === 0) {
  8560. if (caretContainerNode.parentNode) {
  8561. caretContainerNode.parentNode.removeChild(caretContainerNode);
  8562. }
  8563. }
  8564. caretContainerNode.nodeValue = text;
  8565. }
  8566. }
  8567. function startsWithCaretContainer(node) {
  8568. return isText(node) && node.data[0] == Zwsp.ZWSP;
  8569. }
  8570. function endsWithCaretContainer(node) {
  8571. return isText(node) && node.data[node.data.length - 1] == Zwsp.ZWSP;
  8572. }
  8573. return {
  8574. isCaretContainer: isCaretContainer,
  8575. isCaretContainerBlock: isCaretContainerBlock,
  8576. isCaretContainerInline: isCaretContainerInline,
  8577. insertInline: insertInline,
  8578. insertBlock: insertBlock,
  8579. remove: remove,
  8580. startsWithCaretContainer: startsWithCaretContainer,
  8581. endsWithCaretContainer: endsWithCaretContainer
  8582. };
  8583. });
  8584. // Included from: js/tinymce/classes/dom/RangeUtils.js
  8585. /**
  8586. * RangeUtils.js
  8587. *
  8588. * Released under LGPL License.
  8589. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  8590. *
  8591. * License: http://www.tinymce.com/license
  8592. * Contributing: http://www.tinymce.com/contributing
  8593. */
  8594. /**
  8595. * This class contains a few utility methods for ranges.
  8596. *
  8597. * @class tinymce.dom.RangeUtils
  8598. */
  8599. define("tinymce/dom/RangeUtils", [
  8600. "tinymce/util/Tools",
  8601. "tinymce/dom/TreeWalker",
  8602. "tinymce/dom/NodeType",
  8603. "tinymce/dom/Range",
  8604. "tinymce/caret/CaretContainer"
  8605. ], function(Tools, TreeWalker, NodeType, Range, CaretContainer) {
  8606. var each = Tools.each,
  8607. isContentEditableFalse = NodeType.isContentEditableFalse,
  8608. isCaretContainer = CaretContainer.isCaretContainer;
  8609. function getEndChild(container, index) {
  8610. var childNodes = container.childNodes;
  8611. index--;
  8612. if (index > childNodes.length - 1) {
  8613. index = childNodes.length - 1;
  8614. } else if (index < 0) {
  8615. index = 0;
  8616. }
  8617. return childNodes[index] || container;
  8618. }
  8619. function RangeUtils(dom) {
  8620. /**
  8621. * Walks the specified range like object and executes the callback for each sibling collection it finds.
  8622. *
  8623. * @private
  8624. * @method walk
  8625. * @param {Object} rng Range like object.
  8626. * @param {function} callback Callback function to execute for each sibling collection.
  8627. */
  8628. this.walk = function(rng, callback) {
  8629. var startContainer = rng.startContainer,
  8630. startOffset = rng.startOffset,
  8631. endContainer = rng.endContainer,
  8632. endOffset = rng.endOffset,
  8633. ancestor, startPoint,
  8634. endPoint, node, parent, siblings, nodes;
  8635. // Handle table cell selection the table plugin enables
  8636. // you to fake select table cells and perform formatting actions on them
  8637. nodes = dom.select('td[data-mce-selected],th[data-mce-selected]');
  8638. if (nodes.length > 0) {
  8639. each(nodes, function(node) {
  8640. callback([node]);
  8641. });
  8642. return;
  8643. }
  8644. /**
  8645. * Excludes start/end text node if they are out side the range
  8646. *
  8647. * @private
  8648. * @param {Array} nodes Nodes to exclude items from.
  8649. * @return {Array} Array with nodes excluding the start/end container if needed.
  8650. */
  8651. function exclude(nodes) {
  8652. var node;
  8653. // First node is excluded
  8654. node = nodes[0];
  8655. if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
  8656. nodes.splice(0, 1);
  8657. }
  8658. // Last node is excluded
  8659. node = nodes[nodes.length - 1];
  8660. if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
  8661. nodes.splice(nodes.length - 1, 1);
  8662. }
  8663. return nodes;
  8664. }
  8665. /**
  8666. * Collects siblings
  8667. *
  8668. * @private
  8669. * @param {Node} node Node to collect siblings from.
  8670. * @param {String} name Name of the sibling to check for.
  8671. * @param {Node} end_node
  8672. * @return {Array} Array of collected siblings.
  8673. */
  8674. function collectSiblings(node, name, end_node) {
  8675. var siblings = [];
  8676. for (; node && node != end_node; node = node[name]) {
  8677. siblings.push(node);
  8678. }
  8679. return siblings;
  8680. }
  8681. /**
  8682. * Find an end point this is the node just before the common ancestor root.
  8683. *
  8684. * @private
  8685. * @param {Node} node Node to start at.
  8686. * @param {Node} root Root/ancestor element to stop just before.
  8687. * @return {Node} Node just before the root element.
  8688. */
  8689. function findEndPoint(node, root) {
  8690. do {
  8691. if (node.parentNode == root) {
  8692. return node;
  8693. }
  8694. node = node.parentNode;
  8695. } while (node);
  8696. }
  8697. function walkBoundary(start_node, end_node, next) {
  8698. var siblingName = next ? 'nextSibling' : 'previousSibling';
  8699. for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
  8700. parent = node.parentNode;
  8701. siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
  8702. if (siblings.length) {
  8703. if (!next) {
  8704. siblings.reverse();
  8705. }
  8706. callback(exclude(siblings));
  8707. }
  8708. }
  8709. }
  8710. // If index based start position then resolve it
  8711. if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
  8712. startContainer = startContainer.childNodes[startOffset];
  8713. }
  8714. // If index based end position then resolve it
  8715. if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
  8716. endContainer = getEndChild(endContainer, endOffset);
  8717. }
  8718. // Same container
  8719. if (startContainer == endContainer) {
  8720. return callback(exclude([startContainer]));
  8721. }
  8722. // Find common ancestor and end points
  8723. ancestor = dom.findCommonAncestor(startContainer, endContainer);
  8724. // Process left side
  8725. for (node = startContainer; node; node = node.parentNode) {
  8726. if (node === endContainer) {
  8727. return walkBoundary(startContainer, ancestor, true);
  8728. }
  8729. if (node === ancestor) {
  8730. break;
  8731. }
  8732. }
  8733. // Process right side
  8734. for (node = endContainer; node; node = node.parentNode) {
  8735. if (node === startContainer) {
  8736. return walkBoundary(endContainer, ancestor);
  8737. }
  8738. if (node === ancestor) {
  8739. break;
  8740. }
  8741. }
  8742. // Find start/end point
  8743. startPoint = findEndPoint(startContainer, ancestor) || startContainer;
  8744. endPoint = findEndPoint(endContainer, ancestor) || endContainer;
  8745. // Walk left leaf
  8746. walkBoundary(startContainer, startPoint, true);
  8747. // Walk the middle from start to end point
  8748. siblings = collectSiblings(
  8749. startPoint == startContainer ? startPoint : startPoint.nextSibling,
  8750. 'nextSibling',
  8751. endPoint == endContainer ? endPoint.nextSibling : endPoint
  8752. );
  8753. if (siblings.length) {
  8754. callback(exclude(siblings));
  8755. }
  8756. // Walk right leaf
  8757. walkBoundary(endContainer, endPoint);
  8758. };
  8759. /**
  8760. * Splits the specified range at it's start/end points.
  8761. *
  8762. * @private
  8763. * @param {Range/RangeObject} rng Range to split.
  8764. * @return {Object} Range position object.
  8765. */
  8766. this.split = function(rng) {
  8767. var startContainer = rng.startContainer,
  8768. startOffset = rng.startOffset,
  8769. endContainer = rng.endContainer,
  8770. endOffset = rng.endOffset;
  8771. function splitText(node, offset) {
  8772. return node.splitText(offset);
  8773. }
  8774. // Handle single text node
  8775. if (startContainer == endContainer && startContainer.nodeType == 3) {
  8776. if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
  8777. endContainer = splitText(startContainer, startOffset);
  8778. startContainer = endContainer.previousSibling;
  8779. if (endOffset > startOffset) {
  8780. endOffset = endOffset - startOffset;
  8781. startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
  8782. endOffset = endContainer.nodeValue.length;
  8783. startOffset = 0;
  8784. } else {
  8785. endOffset = 0;
  8786. }
  8787. }
  8788. } else {
  8789. // Split startContainer text node if needed
  8790. if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
  8791. startContainer = splitText(startContainer, startOffset);
  8792. startOffset = 0;
  8793. }
  8794. // Split endContainer text node if needed
  8795. if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
  8796. endContainer = splitText(endContainer, endOffset).previousSibling;
  8797. endOffset = endContainer.nodeValue.length;
  8798. }
  8799. }
  8800. return {
  8801. startContainer: startContainer,
  8802. startOffset: startOffset,
  8803. endContainer: endContainer,
  8804. endOffset: endOffset
  8805. };
  8806. };
  8807. /**
  8808. * Normalizes the specified range by finding the closest best suitable caret location.
  8809. *
  8810. * @private
  8811. * @param {Range} rng Range to normalize.
  8812. * @return {Boolean} True/false if the specified range was normalized or not.
  8813. */
  8814. this.normalize = function(rng) {
  8815. var normalized, collapsed;
  8816. function normalizeEndPoint(start) {
  8817. var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
  8818. var directionLeft, isAfterNode;
  8819. function isTableCell(node) {
  8820. return node && /^(TD|TH|CAPTION)$/.test(node.nodeName);
  8821. }
  8822. function hasBrBeforeAfter(node, left) {
  8823. var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
  8824. while ((node = walker[left ? 'prev' : 'next']())) {
  8825. if (node.nodeName === "BR") {
  8826. return true;
  8827. }
  8828. }
  8829. }
  8830. function hasContentEditableFalseParent(node) {
  8831. while (node && node != body) {
  8832. if (isContentEditableFalse(node)) {
  8833. return true;
  8834. }
  8835. node = node.parentNode;
  8836. }
  8837. return false;
  8838. }
  8839. function isPrevNode(node, name) {
  8840. return node.previousSibling && node.previousSibling.nodeName == name;
  8841. }
  8842. // Walks the dom left/right to find a suitable text node to move the endpoint into
  8843. // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
  8844. function findTextNodeRelative(left, startNode) {
  8845. var walker, lastInlineElement, parentBlockContainer;
  8846. startNode = startNode || container;
  8847. parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
  8848. // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
  8849. // This: <p><br>|</p> becomes <p>|<br></p>
  8850. if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
  8851. container = startNode.parentNode;
  8852. offset = dom.nodeIndex(startNode);
  8853. normalized = true;
  8854. return;
  8855. }
  8856. // Walk left until we hit a text node we can move to or a block/br/img
  8857. walker = new TreeWalker(startNode, parentBlockContainer);
  8858. while ((node = walker[left ? 'prev' : 'next']())) {
  8859. // Break if we hit a non content editable node
  8860. if (dom.getContentEditableParent(node) === "false" || isCaretContainer(node)) {
  8861. return;
  8862. }
  8863. // Found text node that has a length
  8864. if (node.nodeType === 3 && node.nodeValue.length > 0) {
  8865. container = node;
  8866. offset = left ? node.nodeValue.length : 0;
  8867. normalized = true;
  8868. return;
  8869. }
  8870. // Break if we find a block or a BR/IMG/INPUT etc
  8871. if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  8872. return;
  8873. }
  8874. lastInlineElement = node;
  8875. }
  8876. // Only fetch the last inline element when in caret mode for now
  8877. if (collapsed && lastInlineElement) {
  8878. container = lastInlineElement;
  8879. normalized = true;
  8880. offset = 0;
  8881. }
  8882. }
  8883. container = rng[(start ? 'start' : 'end') + 'Container'];
  8884. offset = rng[(start ? 'start' : 'end') + 'Offset'];
  8885. isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
  8886. nonEmptyElementsMap = dom.schema.getNonEmptyElements();
  8887. directionLeft = start;
  8888. if (isCaretContainer(container)) {
  8889. return;
  8890. }
  8891. if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
  8892. directionLeft = false;
  8893. }
  8894. // If the container is a document move it to the body element
  8895. if (container.nodeType === 9) {
  8896. container = dom.getRoot();
  8897. offset = 0;
  8898. }
  8899. // If the container is body try move it into the closest text node or position
  8900. if (container === body) {
  8901. // If start is before/after a image, table etc
  8902. if (directionLeft) {
  8903. node = container.childNodes[offset > 0 ? offset - 1 : 0];
  8904. if (node) {
  8905. if (isCaretContainer(node)) {
  8906. return;
  8907. }
  8908. if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
  8909. return;
  8910. }
  8911. }
  8912. }
  8913. // Resolve the index
  8914. if (container.hasChildNodes()) {
  8915. offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
  8916. container = container.childNodes[offset];
  8917. offset = 0;
  8918. // Don't normalize non collapsed selections like <p>[a</p><table></table>]
  8919. if (!collapsed && container === body.lastChild && container.nodeName === 'TABLE') {
  8920. return;
  8921. }
  8922. if (hasContentEditableFalseParent(container) || isCaretContainer(container)) {
  8923. return;
  8924. }
  8925. // Don't walk into elements that doesn't have any child nodes like a IMG
  8926. if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
  8927. // Walk the DOM to find a text node to place the caret at or a BR
  8928. node = container;
  8929. walker = new TreeWalker(container, body);
  8930. do {
  8931. if (isContentEditableFalse(node) || isCaretContainer(node)) {
  8932. normalized = false;
  8933. break;
  8934. }
  8935. // Found a text node use that position
  8936. if (node.nodeType === 3 && node.nodeValue.length > 0) {
  8937. offset = directionLeft ? 0 : node.nodeValue.length;
  8938. container = node;
  8939. normalized = true;
  8940. break;
  8941. }
  8942. // Found a BR/IMG element that we can place the caret before
  8943. if (nonEmptyElementsMap[node.nodeName.toLowerCase()] && !isTableCell(node)) {
  8944. offset = dom.nodeIndex(node);
  8945. container = node.parentNode;
  8946. // Put caret after image when moving the end point
  8947. if (node.nodeName == "IMG" && !directionLeft) {
  8948. offset++;
  8949. }
  8950. normalized = true;
  8951. break;
  8952. }
  8953. } while ((node = (directionLeft ? walker.next() : walker.prev())));
  8954. }
  8955. }
  8956. }
  8957. // Lean the caret to the left if possible
  8958. if (collapsed) {
  8959. // So this: <b>x</b><i>|x</i>
  8960. // Becomes: <b>x|</b><i>x</i>
  8961. // Seems that only gecko has issues with this
  8962. if (container.nodeType === 3 && offset === 0) {
  8963. findTextNodeRelative(true);
  8964. }
  8965. // Lean left into empty inline elements when the caret is before a BR
  8966. // So this: <i><b></b><i>|<br></i>
  8967. // Becomes: <i><b>|</b><i><br></i>
  8968. // Seems that only gecko has issues with this.
  8969. // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
  8970. if (container.nodeType === 1) {
  8971. node = container.childNodes[offset];
  8972. // Offset is after the containers last child
  8973. // then use the previous child for normalization
  8974. if (!node) {
  8975. node = container.childNodes[offset - 1];
  8976. }
  8977. if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
  8978. !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
  8979. findTextNodeRelative(true, node);
  8980. }
  8981. }
  8982. }
  8983. // Lean the start of the selection right if possible
  8984. // So this: x[<b>x]</b>
  8985. // Becomes: x<b>[x]</b>
  8986. if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
  8987. findTextNodeRelative(false);
  8988. }
  8989. // Set endpoint if it was normalized
  8990. if (normalized) {
  8991. rng['set' + (start ? 'Start' : 'End')](container, offset);
  8992. }
  8993. }
  8994. collapsed = rng.collapsed;
  8995. normalizeEndPoint(true);
  8996. if (!collapsed) {
  8997. normalizeEndPoint();
  8998. }
  8999. // If it was collapsed then make sure it still is
  9000. if (normalized && collapsed) {
  9001. rng.collapse(true);
  9002. }
  9003. return normalized;
  9004. };
  9005. }
  9006. /**
  9007. * Compares two ranges and checks if they are equal.
  9008. *
  9009. * @static
  9010. * @method compareRanges
  9011. * @param {DOMRange} rng1 First range to compare.
  9012. * @param {DOMRange} rng2 First range to compare.
  9013. * @return {Boolean} true/false if the ranges are equal.
  9014. */
  9015. RangeUtils.compareRanges = function(rng1, rng2) {
  9016. if (rng1 && rng2) {
  9017. // Compare native IE ranges
  9018. if (rng1.item || rng1.duplicate) {
  9019. // Both are control ranges and the selected element matches
  9020. if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
  9021. return true;
  9022. }
  9023. // Both are text ranges and the range matches
  9024. if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
  9025. return true;
  9026. }
  9027. } else {
  9028. // Compare w3c ranges
  9029. return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
  9030. }
  9031. }
  9032. return false;
  9033. };
  9034. /**
  9035. * Finds the closest selection rect tries to get the range from that.
  9036. */
  9037. function findClosestIeRange(clientX, clientY, doc) {
  9038. var element, rng, rects;
  9039. element = doc.elementFromPoint(clientX, clientY);
  9040. rng = doc.body.createTextRange();
  9041. if (!element || element.tagName == 'HTML') {
  9042. element = doc.body;
  9043. }
  9044. rng.moveToElementText(element);
  9045. rects = Tools.toArray(rng.getClientRects());
  9046. rects = rects.sort(function(a, b) {
  9047. a = Math.abs(Math.max(a.top - clientY, a.bottom - clientY));
  9048. b = Math.abs(Math.max(b.top - clientY, b.bottom - clientY));
  9049. return a - b;
  9050. });
  9051. if (rects.length > 0) {
  9052. clientY = (rects[0].bottom + rects[0].top) / 2;
  9053. try {
  9054. rng.moveToPoint(clientX, clientY);
  9055. rng.collapse(true);
  9056. return rng;
  9057. } catch (ex) {
  9058. // At least we tried
  9059. }
  9060. }
  9061. return null;
  9062. }
  9063. /**
  9064. * Gets the caret range for the given x/y location.
  9065. *
  9066. * @static
  9067. * @method getCaretRangeFromPoint
  9068. * @param {Number} clientX X coordinate for range
  9069. * @param {Number} clientY Y coordinate for range
  9070. * @param {Document} doc Document that x/y are relative to
  9071. * @returns {Range} caret range
  9072. */
  9073. RangeUtils.getCaretRangeFromPoint = function(clientX, clientY, doc) {
  9074. var rng, point;
  9075. if (doc.caretPositionFromPoint) {
  9076. point = doc.caretPositionFromPoint(clientX, clientY);
  9077. rng = doc.createRange();
  9078. rng.setStart(point.offsetNode, point.offset);
  9079. rng.collapse(true);
  9080. } else if (doc.caretRangeFromPoint) {
  9081. rng = doc.caretRangeFromPoint(clientX, clientY);
  9082. } else if (doc.body.createTextRange) {
  9083. rng = doc.body.createTextRange();
  9084. try {
  9085. rng.moveToPoint(clientX, clientY);
  9086. rng.collapse(true);
  9087. } catch (ex) {
  9088. rng = findClosestIeRange(clientX, clientY, doc);
  9089. }
  9090. }
  9091. return rng;
  9092. };
  9093. RangeUtils.getSelectedNode = function(range) {
  9094. var startContainer = range.startContainer,
  9095. startOffset = range.startOffset;
  9096. if (startContainer.hasChildNodes() && range.endOffset == startOffset + 1) {
  9097. return startContainer.childNodes[startOffset];
  9098. }
  9099. return null;
  9100. };
  9101. RangeUtils.getNode = function(container, offset) {
  9102. if (container.nodeType == 1 && container.hasChildNodes()) {
  9103. if (offset >= container.childNodes.length) {
  9104. offset = container.childNodes.length - 1;
  9105. }
  9106. container = container.childNodes[offset];
  9107. }
  9108. return container;
  9109. };
  9110. return RangeUtils;
  9111. });
  9112. // Included from: js/tinymce/classes/NodeChange.js
  9113. /**
  9114. * NodeChange.js
  9115. *
  9116. * Released under LGPL License.
  9117. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  9118. *
  9119. * License: http://www.tinymce.com/license
  9120. * Contributing: http://www.tinymce.com/contributing
  9121. */
  9122. /**
  9123. * This class handles the nodechange event dispatching both manual and through selection change events.
  9124. *
  9125. * @class tinymce.NodeChange
  9126. * @private
  9127. */
  9128. define("tinymce/NodeChange", [
  9129. "tinymce/dom/RangeUtils",
  9130. "tinymce/Env",
  9131. "tinymce/util/Delay"
  9132. ], function(RangeUtils, Env, Delay) {
  9133. return function(editor) {
  9134. var lastRng, lastPath = [];
  9135. /**
  9136. * Returns true/false if the current element path has been changed or not.
  9137. *
  9138. * @private
  9139. * @return {Boolean} True if the element path is the same false if it's not.
  9140. */
  9141. function isSameElementPath(startElm) {
  9142. var i, currentPath;
  9143. currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm);
  9144. if (currentPath.length === lastPath.length) {
  9145. for (i = currentPath.length; i >= 0; i--) {
  9146. if (currentPath[i] !== lastPath[i]) {
  9147. break;
  9148. }
  9149. }
  9150. if (i === -1) {
  9151. lastPath = currentPath;
  9152. return true;
  9153. }
  9154. }
  9155. lastPath = currentPath;
  9156. return false;
  9157. }
  9158. // Gecko doesn't support the "selectionchange" event
  9159. if (!('onselectionchange' in editor.getDoc())) {
  9160. editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) {
  9161. var nativeRng, fakeRng;
  9162. // Since DOM Ranges mutate on modification
  9163. // of the DOM we need to clone it's contents
  9164. nativeRng = editor.selection.getRng();
  9165. fakeRng = {
  9166. startContainer: nativeRng.startContainer,
  9167. startOffset: nativeRng.startOffset,
  9168. endContainer: nativeRng.endContainer,
  9169. endOffset: nativeRng.endOffset
  9170. };
  9171. // Always treat nodechange as a selectionchange since applying
  9172. // formatting to the current range wouldn't update the range but it's parent
  9173. if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) {
  9174. editor.fire('SelectionChange');
  9175. }
  9176. lastRng = fakeRng;
  9177. });
  9178. }
  9179. // IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
  9180. // When the contextmenu event fires the selection is located at the right location
  9181. editor.on('contextmenu', function() {
  9182. editor.fire('SelectionChange');
  9183. });
  9184. // Selection change is delayed ~200ms on IE when you click inside the current range
  9185. editor.on('SelectionChange', function() {
  9186. var startElm = editor.selection.getStart(true);
  9187. // IE 8 will fire a selectionchange event with an incorrect selection
  9188. // when focusing out of table cells. Click inside cell -> toolbar = Invalid SelectionChange event
  9189. if (!Env.range && editor.selection.isCollapsed()) {
  9190. return;
  9191. }
  9192. if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
  9193. editor.nodeChanged({selectionChange: true});
  9194. }
  9195. });
  9196. // Fire an extra nodeChange on mouseup for compatibility reasons
  9197. editor.on('MouseUp', function(e) {
  9198. if (!e.isDefaultPrevented()) {
  9199. // Delay nodeChanged call for WebKit edge case issue where the range
  9200. // isn't updated until after you click outside a selected image
  9201. if (editor.selection.getNode().nodeName == 'IMG') {
  9202. Delay.setEditorTimeout(editor, function() {
  9203. editor.nodeChanged();
  9204. });
  9205. } else {
  9206. editor.nodeChanged();
  9207. }
  9208. }
  9209. });
  9210. /**
  9211. * Dispatches out a onNodeChange event to all observers. This method should be called when you
  9212. * need to update the UI states or element path etc.
  9213. *
  9214. * @method nodeChanged
  9215. * @param {Object} args Optional args to pass to NodeChange event handlers.
  9216. */
  9217. this.nodeChanged = function(args) {
  9218. var selection = editor.selection, node, parents, root;
  9219. // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
  9220. if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.readonly) {
  9221. // Get start node
  9222. root = editor.getBody();
  9223. node = selection.getStart() || root;
  9224. // Make sure the node is within the editor root or is the editor root
  9225. if (node.ownerDocument != editor.getDoc() || !editor.dom.isChildOf(node, root)) {
  9226. node = root;
  9227. }
  9228. // Edge case for <p>|<img></p>
  9229. if (node.nodeName == 'IMG' && selection.isCollapsed()) {
  9230. node = node.parentNode;
  9231. }
  9232. // Get parents and add them to object
  9233. parents = [];
  9234. editor.dom.getParent(node, function(node) {
  9235. if (node === root) {
  9236. return true;
  9237. }
  9238. parents.push(node);
  9239. });
  9240. args = args || {};
  9241. args.element = node;
  9242. args.parents = parents;
  9243. editor.fire('NodeChange', args);
  9244. }
  9245. };
  9246. };
  9247. });
  9248. // Included from: js/tinymce/classes/html/Node.js
  9249. /**
  9250. * Node.js
  9251. *
  9252. * Released under LGPL License.
  9253. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  9254. *
  9255. * License: http://www.tinymce.com/license
  9256. * Contributing: http://www.tinymce.com/contributing
  9257. */
  9258. /**
  9259. * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
  9260. *
  9261. * @example
  9262. * var node = new tinymce.html.Node('strong', 1);
  9263. * someRoot.append(node);
  9264. *
  9265. * @class tinymce.html.Node
  9266. * @version 3.4
  9267. */
  9268. define("tinymce/html/Node", [], function() {
  9269. var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
  9270. '#text': 3,
  9271. '#comment': 8,
  9272. '#cdata': 4,
  9273. '#pi': 7,
  9274. '#doctype': 10,
  9275. '#document-fragment': 11
  9276. };
  9277. // Walks the tree left/right
  9278. function walk(node, root_node, prev) {
  9279. var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
  9280. // Walk into nodes if it has a start
  9281. if (node[startName]) {
  9282. return node[startName];
  9283. }
  9284. // Return the sibling if it has one
  9285. if (node !== root_node) {
  9286. sibling = node[siblingName];
  9287. if (sibling) {
  9288. return sibling;
  9289. }
  9290. // Walk up the parents to look for siblings
  9291. for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
  9292. sibling = parent[siblingName];
  9293. if (sibling) {
  9294. return sibling;
  9295. }
  9296. }
  9297. }
  9298. }
  9299. /**
  9300. * Constructs a new Node instance.
  9301. *
  9302. * @constructor
  9303. * @method Node
  9304. * @param {String} name Name of the node type.
  9305. * @param {Number} type Numeric type representing the node.
  9306. */
  9307. function Node(name, type) {
  9308. this.name = name;
  9309. this.type = type;
  9310. if (type === 1) {
  9311. this.attributes = [];
  9312. this.attributes.map = {};
  9313. }
  9314. }
  9315. Node.prototype = {
  9316. /**
  9317. * Replaces the current node with the specified one.
  9318. *
  9319. * @example
  9320. * someNode.replace(someNewNode);
  9321. *
  9322. * @method replace
  9323. * @param {tinymce.html.Node} node Node to replace the current node with.
  9324. * @return {tinymce.html.Node} The old node that got replaced.
  9325. */
  9326. replace: function(node) {
  9327. var self = this;
  9328. if (node.parent) {
  9329. node.remove();
  9330. }
  9331. self.insert(node, self);
  9332. self.remove();
  9333. return self;
  9334. },
  9335. /**
  9336. * Gets/sets or removes an attribute by name.
  9337. *
  9338. * @example
  9339. * someNode.attr("name", "value"); // Sets an attribute
  9340. * console.log(someNode.attr("name")); // Gets an attribute
  9341. * someNode.attr("name", null); // Removes an attribute
  9342. *
  9343. * @method attr
  9344. * @param {String} name Attribute name to set or get.
  9345. * @param {String} value Optional value to set.
  9346. * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation.
  9347. */
  9348. attr: function(name, value) {
  9349. var self = this, attrs, i, undef;
  9350. if (typeof name !== "string") {
  9351. for (i in name) {
  9352. self.attr(i, name[i]);
  9353. }
  9354. return self;
  9355. }
  9356. if ((attrs = self.attributes)) {
  9357. if (value !== undef) {
  9358. // Remove attribute
  9359. if (value === null) {
  9360. if (name in attrs.map) {
  9361. delete attrs.map[name];
  9362. i = attrs.length;
  9363. while (i--) {
  9364. if (attrs[i].name === name) {
  9365. attrs = attrs.splice(i, 1);
  9366. return self;
  9367. }
  9368. }
  9369. }
  9370. return self;
  9371. }
  9372. // Set attribute
  9373. if (name in attrs.map) {
  9374. // Set attribute
  9375. i = attrs.length;
  9376. while (i--) {
  9377. if (attrs[i].name === name) {
  9378. attrs[i].value = value;
  9379. break;
  9380. }
  9381. }
  9382. } else {
  9383. attrs.push({name: name, value: value});
  9384. }
  9385. attrs.map[name] = value;
  9386. return self;
  9387. }
  9388. return attrs.map[name];
  9389. }
  9390. },
  9391. /**
  9392. * Does a shallow clones the node into a new node. It will also exclude id attributes since
  9393. * there should only be one id per document.
  9394. *
  9395. * @example
  9396. * var clonedNode = node.clone();
  9397. *
  9398. * @method clone
  9399. * @return {tinymce.html.Node} New copy of the original node.
  9400. */
  9401. clone: function() {
  9402. var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
  9403. // Clone element attributes
  9404. if ((selfAttrs = self.attributes)) {
  9405. cloneAttrs = [];
  9406. cloneAttrs.map = {};
  9407. for (i = 0, l = selfAttrs.length; i < l; i++) {
  9408. selfAttr = selfAttrs[i];
  9409. // Clone everything except id
  9410. if (selfAttr.name !== 'id') {
  9411. cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
  9412. cloneAttrs.map[selfAttr.name] = selfAttr.value;
  9413. }
  9414. }
  9415. clone.attributes = cloneAttrs;
  9416. }
  9417. clone.value = self.value;
  9418. clone.shortEnded = self.shortEnded;
  9419. return clone;
  9420. },
  9421. /**
  9422. * Wraps the node in in another node.
  9423. *
  9424. * @example
  9425. * node.wrap(wrapperNode);
  9426. *
  9427. * @method wrap
  9428. */
  9429. wrap: function(wrapper) {
  9430. var self = this;
  9431. self.parent.insert(wrapper, self);
  9432. wrapper.append(self);
  9433. return self;
  9434. },
  9435. /**
  9436. * Unwraps the node in other words it removes the node but keeps the children.
  9437. *
  9438. * @example
  9439. * node.unwrap();
  9440. *
  9441. * @method unwrap
  9442. */
  9443. unwrap: function() {
  9444. var self = this, node, next;
  9445. for (node = self.firstChild; node;) {
  9446. next = node.next;
  9447. self.insert(node, self, true);
  9448. node = next;
  9449. }
  9450. self.remove();
  9451. },
  9452. /**
  9453. * Removes the node from it's parent.
  9454. *
  9455. * @example
  9456. * node.remove();
  9457. *
  9458. * @method remove
  9459. * @return {tinymce.html.Node} Current node that got removed.
  9460. */
  9461. remove: function() {
  9462. var self = this, parent = self.parent, next = self.next, prev = self.prev;
  9463. if (parent) {
  9464. if (parent.firstChild === self) {
  9465. parent.firstChild = next;
  9466. if (next) {
  9467. next.prev = null;
  9468. }
  9469. } else {
  9470. prev.next = next;
  9471. }
  9472. if (parent.lastChild === self) {
  9473. parent.lastChild = prev;
  9474. if (prev) {
  9475. prev.next = null;
  9476. }
  9477. } else {
  9478. next.prev = prev;
  9479. }
  9480. self.parent = self.next = self.prev = null;
  9481. }
  9482. return self;
  9483. },
  9484. /**
  9485. * Appends a new node as a child of the current node.
  9486. *
  9487. * @example
  9488. * node.append(someNode);
  9489. *
  9490. * @method append
  9491. * @param {tinymce.html.Node} node Node to append as a child of the current one.
  9492. * @return {tinymce.html.Node} The node that got appended.
  9493. */
  9494. append: function(node) {
  9495. var self = this, last;
  9496. if (node.parent) {
  9497. node.remove();
  9498. }
  9499. last = self.lastChild;
  9500. if (last) {
  9501. last.next = node;
  9502. node.prev = last;
  9503. self.lastChild = node;
  9504. } else {
  9505. self.lastChild = self.firstChild = node;
  9506. }
  9507. node.parent = self;
  9508. return node;
  9509. },
  9510. /**
  9511. * Inserts a node at a specific position as a child of the current node.
  9512. *
  9513. * @example
  9514. * parentNode.insert(newChildNode, oldChildNode);
  9515. *
  9516. * @method insert
  9517. * @param {tinymce.html.Node} node Node to insert as a child of the current node.
  9518. * @param {tinymce.html.Node} ref_node Reference node to set node before/after.
  9519. * @param {Boolean} before Optional state to insert the node before the reference node.
  9520. * @return {tinymce.html.Node} The node that got inserted.
  9521. */
  9522. insert: function(node, ref_node, before) {
  9523. var parent;
  9524. if (node.parent) {
  9525. node.remove();
  9526. }
  9527. parent = ref_node.parent || this;
  9528. if (before) {
  9529. if (ref_node === parent.firstChild) {
  9530. parent.firstChild = node;
  9531. } else {
  9532. ref_node.prev.next = node;
  9533. }
  9534. node.prev = ref_node.prev;
  9535. node.next = ref_node;
  9536. ref_node.prev = node;
  9537. } else {
  9538. if (ref_node === parent.lastChild) {
  9539. parent.lastChild = node;
  9540. } else {
  9541. ref_node.next.prev = node;
  9542. }
  9543. node.next = ref_node.next;
  9544. node.prev = ref_node;
  9545. ref_node.next = node;
  9546. }
  9547. node.parent = parent;
  9548. return node;
  9549. },
  9550. /**
  9551. * Get all children by name.
  9552. *
  9553. * @method getAll
  9554. * @param {String} name Name of the child nodes to collect.
  9555. * @return {Array} Array with child nodes matchin the specified name.
  9556. */
  9557. getAll: function(name) {
  9558. var self = this, node, collection = [];
  9559. for (node = self.firstChild; node; node = walk(node, self)) {
  9560. if (node.name === name) {
  9561. collection.push(node);
  9562. }
  9563. }
  9564. return collection;
  9565. },
  9566. /**
  9567. * Removes all children of the current node.
  9568. *
  9569. * @method empty
  9570. * @return {tinymce.html.Node} The current node that got cleared.
  9571. */
  9572. empty: function() {
  9573. var self = this, nodes, i, node;
  9574. // Remove all children
  9575. if (self.firstChild) {
  9576. nodes = [];
  9577. // Collect the children
  9578. for (node = self.firstChild; node; node = walk(node, self)) {
  9579. nodes.push(node);
  9580. }
  9581. // Remove the children
  9582. i = nodes.length;
  9583. while (i--) {
  9584. node = nodes[i];
  9585. node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
  9586. }
  9587. }
  9588. self.firstChild = self.lastChild = null;
  9589. return self;
  9590. },
  9591. /**
  9592. * Returns true/false if the node is to be considered empty or not.
  9593. *
  9594. * @example
  9595. * node.isEmpty({img: true});
  9596. * @method isEmpty
  9597. * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
  9598. * @return {Boolean} true/false if the node is empty or not.
  9599. */
  9600. isEmpty: function(elements) {
  9601. var self = this, node = self.firstChild, i, name;
  9602. if (node) {
  9603. do {
  9604. if (node.type === 1) {
  9605. // Ignore bogus elements
  9606. if (node.attributes.map['data-mce-bogus']) {
  9607. continue;
  9608. }
  9609. // Keep empty elements like <img />
  9610. if (elements[node.name]) {
  9611. return false;
  9612. }
  9613. // Keep bookmark nodes and name attribute like <a name="1"></a>
  9614. i = node.attributes.length;
  9615. while (i--) {
  9616. name = node.attributes[i].name;
  9617. if (name === "name" || name.indexOf('data-mce-bookmark') === 0) {
  9618. return false;
  9619. }
  9620. }
  9621. }
  9622. // Keep comments
  9623. if (node.type === 8) {
  9624. return false;
  9625. }
  9626. // Keep non whitespace text nodes
  9627. if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) {
  9628. return false;
  9629. }
  9630. } while ((node = walk(node, self)));
  9631. }
  9632. return true;
  9633. },
  9634. /**
  9635. * Walks to the next or previous node and returns that node or null if it wasn't found.
  9636. *
  9637. * @method walk
  9638. * @param {Boolean} prev Optional previous node state defaults to false.
  9639. * @return {tinymce.html.Node} Node that is next to or previous of the current node.
  9640. */
  9641. walk: function(prev) {
  9642. return walk(this, null, prev);
  9643. }
  9644. };
  9645. /**
  9646. * Creates a node of a specific type.
  9647. *
  9648. * @static
  9649. * @method create
  9650. * @param {String} name Name of the node type to create for example "b" or "#text".
  9651. * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
  9652. */
  9653. Node.create = function(name, attrs) {
  9654. var node, attrName;
  9655. // Create node
  9656. node = new Node(name, typeLookup[name] || 1);
  9657. // Add attributes if needed
  9658. if (attrs) {
  9659. for (attrName in attrs) {
  9660. node.attr(attrName, attrs[attrName]);
  9661. }
  9662. }
  9663. return node;
  9664. };
  9665. return Node;
  9666. });
  9667. // Included from: js/tinymce/classes/html/Schema.js
  9668. /**
  9669. * Schema.js
  9670. *
  9671. * Released under LGPL License.
  9672. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  9673. *
  9674. * License: http://www.tinymce.com/license
  9675. * Contributing: http://www.tinymce.com/contributing
  9676. */
  9677. /**
  9678. * Schema validator class.
  9679. *
  9680. * @class tinymce.html.Schema
  9681. * @example
  9682. * if (tinymce.activeEditor.schema.isValidChild('p', 'span'))
  9683. * alert('span is valid child of p.');
  9684. *
  9685. * if (tinymce.activeEditor.schema.getElementRule('p'))
  9686. * alert('P is a valid element.');
  9687. *
  9688. * @class tinymce.html.Schema
  9689. * @version 3.4
  9690. */
  9691. define("tinymce/html/Schema", [
  9692. "tinymce/util/Tools"
  9693. ], function(Tools) {
  9694. var mapCache = {}, dummyObj = {};
  9695. var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
  9696. function split(items, delim) {
  9697. return items ? items.split(delim || ' ') : [];
  9698. }
  9699. /**
  9700. * Builds a schema lookup table
  9701. *
  9702. * @private
  9703. * @param {String} type html4, html5 or html5-strict schema type.
  9704. * @return {Object} Schema lookup table.
  9705. */
  9706. function compileSchema(type) {
  9707. var schema = {}, globalAttributes, blockContent;
  9708. var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
  9709. function add(name, attributes, children) {
  9710. var ni, i, attributesOrder, args = arguments;
  9711. function arrayToMap(array, obj) {
  9712. var map = {}, i, l;
  9713. for (i = 0, l = array.length; i < l; i++) {
  9714. map[array[i]] = obj || {};
  9715. }
  9716. return map;
  9717. }
  9718. children = children || [];
  9719. attributes = attributes || "";
  9720. if (typeof children === "string") {
  9721. children = split(children);
  9722. }
  9723. // Split string children
  9724. for (i = 3; i < args.length; i++) {
  9725. if (typeof args[i] === "string") {
  9726. args[i] = split(args[i]);
  9727. }
  9728. children.push.apply(children, args[i]);
  9729. }
  9730. name = split(name);
  9731. ni = name.length;
  9732. while (ni--) {
  9733. attributesOrder = [].concat(globalAttributes, split(attributes));
  9734. schema[name[ni]] = {
  9735. attributes: arrayToMap(attributesOrder),
  9736. attributesOrder: attributesOrder,
  9737. children: arrayToMap(children, dummyObj)
  9738. };
  9739. }
  9740. }
  9741. function addAttrs(name, attributes) {
  9742. var ni, schemaItem, i, l;
  9743. name = split(name);
  9744. ni = name.length;
  9745. attributes = split(attributes);
  9746. while (ni--) {
  9747. schemaItem = schema[name[ni]];
  9748. for (i = 0, l = attributes.length; i < l; i++) {
  9749. schemaItem.attributes[attributes[i]] = {};
  9750. schemaItem.attributesOrder.push(attributes[i]);
  9751. }
  9752. }
  9753. }
  9754. // Use cached schema
  9755. if (mapCache[type]) {
  9756. return mapCache[type];
  9757. }
  9758. // Attributes present on all elements
  9759. globalAttributes = split("id accesskey class dir lang style tabindex title");
  9760. // Event attributes can be opt-in/opt-out
  9761. /*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
  9762. "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
  9763. "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
  9764. "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
  9765. "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
  9766. "onwaiting"
  9767. );*/
  9768. // Block content elements
  9769. blockContent = split(
  9770. "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"
  9771. );
  9772. // Phrasing content elements from the HTML5 spec (inline)
  9773. phrasingContent = split(
  9774. "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " +
  9775. "label map noscript object q s samp script select small span strong sub sup " +
  9776. "textarea u var #text #comment"
  9777. );
  9778. // Add HTML5 items to globalAttributes, blockContent, phrasingContent
  9779. if (type != "html4") {
  9780. globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " +
  9781. "hidden spellcheck translate"));
  9782. blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
  9783. phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output picture " +
  9784. "progress time wbr video ruby bdi keygen"));
  9785. }
  9786. // Add HTML4 elements unless it's html5-strict
  9787. if (type != "html5-strict") {
  9788. globalAttributes.push("xml:lang");
  9789. html4PhrasingContent = split("acronym applet basefont big font strike tt");
  9790. phrasingContent.push.apply(phrasingContent, html4PhrasingContent);
  9791. each(html4PhrasingContent, function(name) {
  9792. add(name, "", phrasingContent);
  9793. });
  9794. html4BlockContent = split("center dir isindex noframes");
  9795. blockContent.push.apply(blockContent, html4BlockContent);
  9796. // Flow content elements from the HTML5 spec (block+inline)
  9797. flowContent = [].concat(blockContent, phrasingContent);
  9798. each(html4BlockContent, function(name) {
  9799. add(name, "", flowContent);
  9800. });
  9801. }
  9802. // Flow content elements from the HTML5 spec (block+inline)
  9803. flowContent = flowContent || [].concat(blockContent, phrasingContent);
  9804. // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
  9805. // Schema items <element name>, <specific attributes>, <children ..>
  9806. add("html", "manifest", "head body");
  9807. add("head", "", "base command link meta noscript script style title");
  9808. add("title hr noscript br");
  9809. add("base", "href target");
  9810. add("link", "href rel media hreflang type sizes hreflang");
  9811. add("meta", "name http-equiv content charset");
  9812. add("style", "media type scoped");
  9813. add("script", "src async defer type charset");
  9814. add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " +
  9815. "onhashchange onload onmessage onoffline ononline onpagehide onpageshow " +
  9816. "onpopstate onresize onscroll onstorage onunload", flowContent);
  9817. add("address dt dd div caption", "", flowContent);
  9818. add("h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn", "", phrasingContent);
  9819. add("blockquote", "cite", flowContent);
  9820. add("ol", "reversed start type", "li");
  9821. add("ul", "", "li");
  9822. add("li", "value", flowContent);
  9823. add("dl", "", "dt dd");
  9824. add("a", "href target rel media hreflang type", phrasingContent);
  9825. add("q", "cite", phrasingContent);
  9826. add("ins del", "cite datetime", flowContent);
  9827. add("img", "src sizes srcset alt usemap ismap width height");
  9828. add("iframe", "src name width height", flowContent);
  9829. add("embed", "src type width height");
  9830. add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
  9831. add("param", "name value");
  9832. add("map", "name", flowContent, "area");
  9833. add("area", "alt coords shape href target rel media hreflang type");
  9834. add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : ""));
  9835. add("colgroup", "span", "col");
  9836. add("col", "span");
  9837. add("tbody thead tfoot", "", "tr");
  9838. add("tr", "", "td th");
  9839. add("td", "colspan rowspan headers", flowContent);
  9840. add("th", "colspan rowspan headers scope abbr", flowContent);
  9841. add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent);
  9842. add("fieldset", "disabled form name", flowContent, "legend");
  9843. add("label", "form for", phrasingContent);
  9844. add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " +
  9845. "formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"
  9846. );
  9847. add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value",
  9848. type == "html4" ? flowContent : phrasingContent);
  9849. add("select", "disabled form multiple name required size", "option optgroup");
  9850. add("optgroup", "disabled label", "option");
  9851. add("option", "disabled label selected value");
  9852. add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap");
  9853. add("menu", "type label", flowContent, "li");
  9854. add("noscript", "", flowContent);
  9855. // Extend with HTML5 elements
  9856. if (type != "html4") {
  9857. add("wbr");
  9858. add("ruby", "", phrasingContent, "rt rp");
  9859. add("figcaption", "", flowContent);
  9860. add("mark rt rp summary bdi", "", phrasingContent);
  9861. add("canvas", "width height", flowContent);
  9862. add("video", "src crossorigin poster preload autoplay mediagroup loop " +
  9863. "muted controls width height buffered", flowContent, "track source");
  9864. add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source");
  9865. add("picture", "", "img source");
  9866. add("source", "src srcset type media sizes");
  9867. add("track", "kind src srclang label default");
  9868. add("datalist", "", phrasingContent, "option");
  9869. add("article section nav aside header footer", "", flowContent);
  9870. add("hgroup", "", "h1 h2 h3 h4 h5 h6");
  9871. add("figure", "", flowContent, "figcaption");
  9872. add("time", "datetime", phrasingContent);
  9873. add("dialog", "open", flowContent);
  9874. add("command", "type label icon disabled checked radiogroup command");
  9875. add("output", "for form name", phrasingContent);
  9876. add("progress", "value max", phrasingContent);
  9877. add("meter", "value min max low high optimum", phrasingContent);
  9878. add("details", "open", flowContent, "summary");
  9879. add("keygen", "autofocus challenge disabled form keytype name");
  9880. }
  9881. // Extend with HTML4 attributes unless it's html5-strict
  9882. if (type != "html5-strict") {
  9883. addAttrs("script", "language xml:space");
  9884. addAttrs("style", "xml:space");
  9885. addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace");
  9886. addAttrs("embed", "align name hspace vspace");
  9887. addAttrs("param", "valuetype type");
  9888. addAttrs("a", "charset name rev shape coords");
  9889. addAttrs("br", "clear");
  9890. addAttrs("applet", "codebase archive code object alt name width height align hspace vspace");
  9891. addAttrs("img", "name longdesc align border hspace vspace");
  9892. addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align");
  9893. addAttrs("font basefont", "size color face");
  9894. addAttrs("input", "usemap align");
  9895. addAttrs("select", "onchange");
  9896. addAttrs("textarea");
  9897. addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align");
  9898. addAttrs("ul", "type compact");
  9899. addAttrs("li", "type");
  9900. addAttrs("ol dl menu dir", "compact");
  9901. addAttrs("pre", "width xml:space");
  9902. addAttrs("hr", "align noshade size width");
  9903. addAttrs("isindex", "prompt");
  9904. addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor");
  9905. addAttrs("col", "width align char charoff valign");
  9906. addAttrs("colgroup", "width align char charoff valign");
  9907. addAttrs("thead", "align char charoff valign");
  9908. addAttrs("tr", "align char charoff valign bgcolor");
  9909. addAttrs("th", "axis align char charoff valign nowrap bgcolor width height");
  9910. addAttrs("form", "accept");
  9911. addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height");
  9912. addAttrs("tfoot", "align char charoff valign");
  9913. addAttrs("tbody", "align char charoff valign");
  9914. addAttrs("area", "nohref");
  9915. addAttrs("body", "background bgcolor text link vlink alink");
  9916. }
  9917. // Extend with HTML5 attributes unless it's html4
  9918. if (type != "html4") {
  9919. addAttrs("input button select textarea", "autofocus");
  9920. addAttrs("input textarea", "placeholder");
  9921. addAttrs("a", "download");
  9922. addAttrs("link script img", "crossorigin");
  9923. addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc
  9924. }
  9925. // Special: iframe, ruby, video, audio, label
  9926. // Delete children of the same name from it's parent
  9927. // For example: form can't have a child of the name form
  9928. each(split('a form meter progress dfn'), function(name) {
  9929. if (schema[name]) {
  9930. delete schema[name].children[name];
  9931. }
  9932. });
  9933. // Delete header, footer, sectioning and heading content descendants
  9934. /*each('dt th address', function(name) {
  9935. delete schema[name].children[name];
  9936. });*/
  9937. // Caption can't have tables
  9938. delete schema.caption.children.table;
  9939. // Delete scripts by default due to possible XSS
  9940. delete schema.script;
  9941. // TODO: LI:s can only have value if parent is OL
  9942. // TODO: Handle transparent elements
  9943. // a ins del canvas map
  9944. mapCache[type] = schema;
  9945. return schema;
  9946. }
  9947. function compileElementMap(value, mode) {
  9948. var styles;
  9949. if (value) {
  9950. styles = {};
  9951. if (typeof value == 'string') {
  9952. value = {
  9953. '*': value
  9954. };
  9955. }
  9956. // Convert styles into a rule list
  9957. each(value, function(value, key) {
  9958. styles[key] = styles[key.toUpperCase()] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/);
  9959. });
  9960. }
  9961. return styles;
  9962. }
  9963. /**
  9964. * Constructs a new Schema instance.
  9965. *
  9966. * @constructor
  9967. * @method Schema
  9968. * @param {Object} settings Name/value settings object.
  9969. */
  9970. return function(settings) {
  9971. var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems;
  9972. var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses;
  9973. var blockElementsMap, nonEmptyElementsMap, moveCaretBeforeOnEnterElementsMap, textBlockElementsMap, textInlineElementsMap;
  9974. var customElementsMap = {}, specialElements = {};
  9975. // Creates an lookup table map object for the specified option or the default value
  9976. function createLookupTable(option, default_value, extendWith) {
  9977. var value = settings[option];
  9978. if (!value) {
  9979. // Get cached default map or make it if needed
  9980. value = mapCache[option];
  9981. if (!value) {
  9982. value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
  9983. value = extend(value, extendWith);
  9984. mapCache[option] = value;
  9985. }
  9986. } else {
  9987. // Create custom map
  9988. value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/));
  9989. }
  9990. return value;
  9991. }
  9992. settings = settings || {};
  9993. schemaItems = compileSchema(settings.schema);
  9994. // Allow all elements and attributes if verify_html is set to false
  9995. if (settings.verify_html === false) {
  9996. settings.valid_elements = '*[*]';
  9997. }
  9998. validStyles = compileElementMap(settings.valid_styles);
  9999. invalidStyles = compileElementMap(settings.invalid_styles, 'map');
  10000. validClasses = compileElementMap(settings.valid_classes, 'map');
  10001. // Setup map objects
  10002. whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
  10003. selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
  10004. shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' +
  10005. 'meta param embed source wbr track');
  10006. boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
  10007. 'noshade nowrap readonly selected autoplay loop controls');
  10008. nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
  10009. moveCaretBeforeOnEnterElementsMap = createLookupTable('move_caret_before_on_enter_elements', 'table', nonEmptyElementsMap);
  10010. textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
  10011. 'blockquote center dir fieldset header footer article section hgroup aside nav figure');
  10012. blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
  10013. 'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
  10014. 'datalist select optgroup figcaption', textBlockElementsMap);
  10015. textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
  10016. 'dfn code mark q sup sub samp');
  10017. each((settings.special || 'script noscript style textarea').split(' '), function(name) {
  10018. specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
  10019. });
  10020. // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
  10021. function patternToRegExp(str) {
  10022. return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
  10023. }
  10024. // Parses the specified valid_elements string and adds to the current rules
  10025. // This function is a bit hard to read since it's heavily optimized for speed
  10026. function addValidElements(validElements) {
  10027. var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
  10028. prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
  10029. elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
  10030. attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
  10031. hasPatternsRegExp = /[*?+]/;
  10032. if (validElements) {
  10033. // Split valid elements into an array with rules
  10034. validElements = split(validElements, ',');
  10035. if (elements['@']) {
  10036. globalAttributes = elements['@'].attributes;
  10037. globalAttributesOrder = elements['@'].attributesOrder;
  10038. }
  10039. // Loop all rules
  10040. for (ei = 0, el = validElements.length; ei < el; ei++) {
  10041. // Parse element rule
  10042. matches = elementRuleRegExp.exec(validElements[ei]);
  10043. if (matches) {
  10044. // Setup local names for matches
  10045. prefix = matches[1];
  10046. elementName = matches[2];
  10047. outputName = matches[3];
  10048. attrData = matches[5];
  10049. // Create new attributes and attributesOrder
  10050. attributes = {};
  10051. attributesOrder = [];
  10052. // Create the new element
  10053. element = {
  10054. attributes: attributes,
  10055. attributesOrder: attributesOrder
  10056. };
  10057. // Padd empty elements prefix
  10058. if (prefix === '#') {
  10059. element.paddEmpty = true;
  10060. }
  10061. // Remove empty elements prefix
  10062. if (prefix === '-') {
  10063. element.removeEmpty = true;
  10064. }
  10065. if (matches[4] === '!') {
  10066. element.removeEmptyAttrs = true;
  10067. }
  10068. // Copy attributes from global rule into current rule
  10069. if (globalAttributes) {
  10070. for (key in globalAttributes) {
  10071. attributes[key] = globalAttributes[key];
  10072. }
  10073. attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
  10074. }
  10075. // Attributes defined
  10076. if (attrData) {
  10077. attrData = split(attrData, '|');
  10078. for (ai = 0, al = attrData.length; ai < al; ai++) {
  10079. matches = attrRuleRegExp.exec(attrData[ai]);
  10080. if (matches) {
  10081. attr = {};
  10082. attrType = matches[1];
  10083. attrName = matches[2].replace(/::/g, ':');
  10084. prefix = matches[3];
  10085. value = matches[4];
  10086. // Required
  10087. if (attrType === '!') {
  10088. element.attributesRequired = element.attributesRequired || [];
  10089. element.attributesRequired.push(attrName);
  10090. attr.required = true;
  10091. }
  10092. // Denied from global
  10093. if (attrType === '-') {
  10094. delete attributes[attrName];
  10095. attributesOrder.splice(inArray(attributesOrder, attrName), 1);
  10096. continue;
  10097. }
  10098. // Default value
  10099. if (prefix) {
  10100. // Default value
  10101. if (prefix === '=') {
  10102. element.attributesDefault = element.attributesDefault || [];
  10103. element.attributesDefault.push({name: attrName, value: value});
  10104. attr.defaultValue = value;
  10105. }
  10106. // Forced value
  10107. if (prefix === ':') {
  10108. element.attributesForced = element.attributesForced || [];
  10109. element.attributesForced.push({name: attrName, value: value});
  10110. attr.forcedValue = value;
  10111. }
  10112. // Required values
  10113. if (prefix === '<') {
  10114. attr.validValues = makeMap(value, '?');
  10115. }
  10116. }
  10117. // Check for attribute patterns
  10118. if (hasPatternsRegExp.test(attrName)) {
  10119. element.attributePatterns = element.attributePatterns || [];
  10120. attr.pattern = patternToRegExp(attrName);
  10121. element.attributePatterns.push(attr);
  10122. } else {
  10123. // Add attribute to order list if it doesn't already exist
  10124. if (!attributes[attrName]) {
  10125. attributesOrder.push(attrName);
  10126. }
  10127. attributes[attrName] = attr;
  10128. }
  10129. }
  10130. }
  10131. }
  10132. // Global rule, store away these for later usage
  10133. if (!globalAttributes && elementName == '@') {
  10134. globalAttributes = attributes;
  10135. globalAttributesOrder = attributesOrder;
  10136. }
  10137. // Handle substitute elements such as b/strong
  10138. if (outputName) {
  10139. element.outputName = elementName;
  10140. elements[outputName] = element;
  10141. }
  10142. // Add pattern or exact element
  10143. if (hasPatternsRegExp.test(elementName)) {
  10144. element.pattern = patternToRegExp(elementName);
  10145. patternElements.push(element);
  10146. } else {
  10147. elements[elementName] = element;
  10148. }
  10149. }
  10150. }
  10151. }
  10152. }
  10153. function setValidElements(validElements) {
  10154. elements = {};
  10155. patternElements = [];
  10156. addValidElements(validElements);
  10157. each(schemaItems, function(element, name) {
  10158. children[name] = element.children;
  10159. });
  10160. }
  10161. // Adds custom non HTML elements to the schema
  10162. function addCustomElements(customElements) {
  10163. var customElementRegExp = /^(~)?(.+)$/;
  10164. if (customElements) {
  10165. // Flush cached items since we are altering the default maps
  10166. mapCache.text_block_elements = mapCache.block_elements = null;
  10167. each(split(customElements, ','), function(rule) {
  10168. var matches = customElementRegExp.exec(rule),
  10169. inline = matches[1] === '~',
  10170. cloneName = inline ? 'span' : 'div',
  10171. name = matches[2];
  10172. children[name] = children[cloneName];
  10173. customElementsMap[name] = cloneName;
  10174. // If it's not marked as inline then add it to valid block elements
  10175. if (!inline) {
  10176. blockElementsMap[name.toUpperCase()] = {};
  10177. blockElementsMap[name] = {};
  10178. }
  10179. // Add elements clone if needed
  10180. if (!elements[name]) {
  10181. var customRule = elements[cloneName];
  10182. customRule = extend({}, customRule);
  10183. delete customRule.removeEmptyAttrs;
  10184. delete customRule.removeEmpty;
  10185. elements[name] = customRule;
  10186. }
  10187. // Add custom elements at span/div positions
  10188. each(children, function(element, elmName) {
  10189. if (element[cloneName]) {
  10190. children[elmName] = element = extend({}, children[elmName]);
  10191. element[name] = element[cloneName];
  10192. }
  10193. });
  10194. });
  10195. }
  10196. }
  10197. // Adds valid children to the schema object
  10198. function addValidChildren(validChildren) {
  10199. var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
  10200. // Invalidate the schema cache if the schema is mutated
  10201. mapCache[settings.schema] = null;
  10202. if (validChildren) {
  10203. each(split(validChildren, ','), function(rule) {
  10204. var matches = childRuleRegExp.exec(rule), parent, prefix;
  10205. if (matches) {
  10206. prefix = matches[1];
  10207. // Add/remove items from default
  10208. if (prefix) {
  10209. parent = children[matches[2]];
  10210. } else {
  10211. parent = children[matches[2]] = {'#comment': {}};
  10212. }
  10213. parent = children[matches[2]];
  10214. each(split(matches[3], '|'), function(child) {
  10215. if (prefix === '-') {
  10216. delete parent[child];
  10217. } else {
  10218. parent[child] = {};
  10219. }
  10220. });
  10221. }
  10222. });
  10223. }
  10224. }
  10225. function getElementRule(name) {
  10226. var element = elements[name], i;
  10227. // Exact match found
  10228. if (element) {
  10229. return element;
  10230. }
  10231. // No exact match then try the patterns
  10232. i = patternElements.length;
  10233. while (i--) {
  10234. element = patternElements[i];
  10235. if (element.pattern.test(name)) {
  10236. return element;
  10237. }
  10238. }
  10239. }
  10240. if (!settings.valid_elements) {
  10241. // No valid elements defined then clone the elements from the schema spec
  10242. each(schemaItems, function(element, name) {
  10243. elements[name] = {
  10244. attributes: element.attributes,
  10245. attributesOrder: element.attributesOrder
  10246. };
  10247. children[name] = element.children;
  10248. });
  10249. // Switch these on HTML4
  10250. if (settings.schema != "html5") {
  10251. each(split('strong/b em/i'), function(item) {
  10252. item = split(item, '/');
  10253. elements[item[1]].outputName = item[0];
  10254. });
  10255. }
  10256. // Add default alt attribute for images, removed since alt="" is treated as presentational.
  10257. // elements.img.attributesDefault = [{name: 'alt', value: ''}];
  10258. // Remove these if they are empty by default
  10259. each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
  10260. if (elements[name]) {
  10261. elements[name].removeEmpty = true;
  10262. }
  10263. });
  10264. // Padd these by default
  10265. each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) {
  10266. elements[name].paddEmpty = true;
  10267. });
  10268. // Remove these if they have no attributes
  10269. each(split('span'), function(name) {
  10270. elements[name].removeEmptyAttrs = true;
  10271. });
  10272. // Remove these by default
  10273. // TODO: Reenable in 4.1
  10274. /*each(split('script style'), function(name) {
  10275. delete elements[name];
  10276. });*/
  10277. } else {
  10278. setValidElements(settings.valid_elements);
  10279. }
  10280. addCustomElements(settings.custom_elements);
  10281. addValidChildren(settings.valid_children);
  10282. addValidElements(settings.extended_valid_elements);
  10283. // Todo: Remove this when we fix list handling to be valid
  10284. addValidChildren('+ol[ul|ol],+ul[ul|ol]');
  10285. // Delete invalid elements
  10286. if (settings.invalid_elements) {
  10287. each(explode(settings.invalid_elements), function(item) {
  10288. if (elements[item]) {
  10289. delete elements[item];
  10290. }
  10291. });
  10292. }
  10293. // If the user didn't allow span only allow internal spans
  10294. if (!getElementRule('span')) {
  10295. addValidElements('span[!data-mce-type|*]');
  10296. }
  10297. /**
  10298. * Name/value map object with valid parents and children to those parents.
  10299. *
  10300. * @example
  10301. * children = {
  10302. * div:{p:{}, h1:{}}
  10303. * };
  10304. * @field children
  10305. * @type Object
  10306. */
  10307. self.children = children;
  10308. /**
  10309. * Name/value map object with valid styles for each element.
  10310. *
  10311. * @method getValidStyles
  10312. * @type Object
  10313. */
  10314. self.getValidStyles = function() {
  10315. return validStyles;
  10316. };
  10317. /**
  10318. * Name/value map object with valid styles for each element.
  10319. *
  10320. * @method getInvalidStyles
  10321. * @type Object
  10322. */
  10323. self.getInvalidStyles = function() {
  10324. return invalidStyles;
  10325. };
  10326. /**
  10327. * Name/value map object with valid classes for each element.
  10328. *
  10329. * @method getValidClasses
  10330. * @type Object
  10331. */
  10332. self.getValidClasses = function() {
  10333. return validClasses;
  10334. };
  10335. /**
  10336. * Returns a map with boolean attributes.
  10337. *
  10338. * @method getBoolAttrs
  10339. * @return {Object} Name/value lookup map for boolean attributes.
  10340. */
  10341. self.getBoolAttrs = function() {
  10342. return boolAttrMap;
  10343. };
  10344. /**
  10345. * Returns a map with block elements.
  10346. *
  10347. * @method getBlockElements
  10348. * @return {Object} Name/value lookup map for block elements.
  10349. */
  10350. self.getBlockElements = function() {
  10351. return blockElementsMap;
  10352. };
  10353. /**
  10354. * Returns a map with text block elements. Such as: p,h1-h6,div,address
  10355. *
  10356. * @method getTextBlockElements
  10357. * @return {Object} Name/value lookup map for block elements.
  10358. */
  10359. self.getTextBlockElements = function() {
  10360. return textBlockElementsMap;
  10361. };
  10362. /**
  10363. * Returns a map of inline text format nodes for example strong/span or ins.
  10364. *
  10365. * @method getTextInlineElements
  10366. * @return {Object} Name/value lookup map for text format elements.
  10367. */
  10368. self.getTextInlineElements = function() {
  10369. return textInlineElementsMap;
  10370. };
  10371. /**
  10372. * Returns a map with short ended elements such as BR or IMG.
  10373. *
  10374. * @method getShortEndedElements
  10375. * @return {Object} Name/value lookup map for short ended elements.
  10376. */
  10377. self.getShortEndedElements = function() {
  10378. return shortEndedElementsMap;
  10379. };
  10380. /**
  10381. * Returns a map with self closing tags such as <li>.
  10382. *
  10383. * @method getSelfClosingElements
  10384. * @return {Object} Name/value lookup map for self closing tags elements.
  10385. */
  10386. self.getSelfClosingElements = function() {
  10387. return selfClosingElementsMap;
  10388. };
  10389. /**
  10390. * Returns a map with elements that should be treated as contents regardless if it has text
  10391. * content in them or not such as TD, VIDEO or IMG.
  10392. *
  10393. * @method getNonEmptyElements
  10394. * @return {Object} Name/value lookup map for non empty elements.
  10395. */
  10396. self.getNonEmptyElements = function() {
  10397. return nonEmptyElementsMap;
  10398. };
  10399. /**
  10400. * Returns a map with elements that the caret should be moved in front of after enter is
  10401. * pressed
  10402. *
  10403. * @method getMoveCaretBeforeOnEnterElements
  10404. * @return {Object} Name/value lookup map for elements to place the caret in front of.
  10405. */
  10406. self.getMoveCaretBeforeOnEnterElements = function() {
  10407. return moveCaretBeforeOnEnterElementsMap;
  10408. };
  10409. /**
  10410. * Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
  10411. *
  10412. * @method getWhiteSpaceElements
  10413. * @return {Object} Name/value lookup map for white space elements.
  10414. */
  10415. self.getWhiteSpaceElements = function() {
  10416. return whiteSpaceElementsMap;
  10417. };
  10418. /**
  10419. * Returns a map with special elements. These are elements that needs to be parsed
  10420. * in a special way such as script, style, textarea etc. The map object values
  10421. * are regexps used to find the end of the element.
  10422. *
  10423. * @method getSpecialElements
  10424. * @return {Object} Name/value lookup map for special elements.
  10425. */
  10426. self.getSpecialElements = function() {
  10427. return specialElements;
  10428. };
  10429. /**
  10430. * Returns true/false if the specified element and it's child is valid or not
  10431. * according to the schema.
  10432. *
  10433. * @method isValidChild
  10434. * @param {String} name Element name to check for.
  10435. * @param {String} child Element child to verify.
  10436. * @return {Boolean} True/false if the element is a valid child of the specified parent.
  10437. */
  10438. self.isValidChild = function(name, child) {
  10439. var parent = children[name];
  10440. return !!(parent && parent[child]);
  10441. };
  10442. /**
  10443. * Returns true/false if the specified element name and optional attribute is
  10444. * valid according to the schema.
  10445. *
  10446. * @method isValid
  10447. * @param {String} name Name of element to check.
  10448. * @param {String} attr Optional attribute name to check for.
  10449. * @return {Boolean} True/false if the element and attribute is valid.
  10450. */
  10451. self.isValid = function(name, attr) {
  10452. var attrPatterns, i, rule = getElementRule(name);
  10453. // Check if it's a valid element
  10454. if (rule) {
  10455. if (attr) {
  10456. // Check if attribute name exists
  10457. if (rule.attributes[attr]) {
  10458. return true;
  10459. }
  10460. // Check if attribute matches a regexp pattern
  10461. attrPatterns = rule.attributePatterns;
  10462. if (attrPatterns) {
  10463. i = attrPatterns.length;
  10464. while (i--) {
  10465. if (attrPatterns[i].pattern.test(name)) {
  10466. return true;
  10467. }
  10468. }
  10469. }
  10470. } else {
  10471. return true;
  10472. }
  10473. }
  10474. // No match
  10475. return false;
  10476. };
  10477. /**
  10478. * Returns true/false if the specified element is valid or not
  10479. * according to the schema.
  10480. *
  10481. * @method getElementRule
  10482. * @param {String} name Element name to check for.
  10483. * @return {Object} Element object or undefined if the element isn't valid.
  10484. */
  10485. self.getElementRule = getElementRule;
  10486. /**
  10487. * Returns an map object of all custom elements.
  10488. *
  10489. * @method getCustomElements
  10490. * @return {Object} Name/value map object of all custom elements.
  10491. */
  10492. self.getCustomElements = function() {
  10493. return customElementsMap;
  10494. };
  10495. /**
  10496. * Parses a valid elements string and adds it to the schema. The valid elements
  10497. * format is for example "element[attr=default|otherattr]".
  10498. * Existing rules will be replaced with the ones specified, so this extends the schema.
  10499. *
  10500. * @method addValidElements
  10501. * @param {String} valid_elements String in the valid elements format to be parsed.
  10502. */
  10503. self.addValidElements = addValidElements;
  10504. /**
  10505. * Parses a valid elements string and sets it to the schema. The valid elements
  10506. * format is for example "element[attr=default|otherattr]".
  10507. * Existing rules will be replaced with the ones specified, so this extends the schema.
  10508. *
  10509. * @method setValidElements
  10510. * @param {String} valid_elements String in the valid elements format to be parsed.
  10511. */
  10512. self.setValidElements = setValidElements;
  10513. /**
  10514. * Adds custom non HTML elements to the schema.
  10515. *
  10516. * @method addCustomElements
  10517. * @param {String} custom_elements Comma separated list of custom elements to add.
  10518. */
  10519. self.addCustomElements = addCustomElements;
  10520. /**
  10521. * Parses a valid children string and adds them to the schema structure. The valid children
  10522. * format is for example: "element[child1|child2]".
  10523. *
  10524. * @method addValidChildren
  10525. * @param {String} valid_children Valid children elements string to parse
  10526. */
  10527. self.addValidChildren = addValidChildren;
  10528. self.elements = elements;
  10529. };
  10530. });
  10531. // Included from: js/tinymce/classes/html/SaxParser.js
  10532. /**
  10533. * SaxParser.js
  10534. *
  10535. * Released under LGPL License.
  10536. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  10537. *
  10538. * License: http://www.tinymce.com/license
  10539. * Contributing: http://www.tinymce.com/contributing
  10540. */
  10541. /*eslint max-depth:[2, 9] */
  10542. /**
  10543. * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will
  10544. * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements
  10545. * and attributes that doesn't fit the schema if the validate setting is enabled.
  10546. *
  10547. * @example
  10548. * var parser = new tinymce.html.SaxParser({
  10549. * validate: true,
  10550. *
  10551. * comment: function(text) {
  10552. * console.log('Comment:', text);
  10553. * },
  10554. *
  10555. * cdata: function(text) {
  10556. * console.log('CDATA:', text);
  10557. * },
  10558. *
  10559. * text: function(text, raw) {
  10560. * console.log('Text:', text, 'Raw:', raw);
  10561. * },
  10562. *
  10563. * start: function(name, attrs, empty) {
  10564. * console.log('Start:', name, attrs, empty);
  10565. * },
  10566. *
  10567. * end: function(name) {
  10568. * console.log('End:', name);
  10569. * },
  10570. *
  10571. * pi: function(name, text) {
  10572. * console.log('PI:', name, text);
  10573. * },
  10574. *
  10575. * doctype: function(text) {
  10576. * console.log('DocType:', text);
  10577. * }
  10578. * }, schema);
  10579. * @class tinymce.html.SaxParser
  10580. * @version 3.4
  10581. */
  10582. define("tinymce/html/SaxParser", [
  10583. "tinymce/html/Schema",
  10584. "tinymce/html/Entities",
  10585. "tinymce/util/Tools"
  10586. ], function(Schema, Entities, Tools) {
  10587. var each = Tools.each;
  10588. /**
  10589. * Returns the index of the end tag for a specific start tag. This can be
  10590. * used to skip all children of a parent element from being processed.
  10591. *
  10592. * @private
  10593. * @method findEndTag
  10594. * @param {tinymce.html.Schema} schema Schema instance to use to match short ended elements.
  10595. * @param {String} html HTML string to find the end tag in.
  10596. * @param {Number} startIndex Indext to start searching at should be after the start tag.
  10597. * @return {Number} Index of the end tag.
  10598. */
  10599. function findEndTag(schema, html, startIndex) {
  10600. var count = 1, index, matches, tokenRegExp, shortEndedElements;
  10601. shortEndedElements = schema.getShortEndedElements();
  10602. tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
  10603. tokenRegExp.lastIndex = index = startIndex;
  10604. while ((matches = tokenRegExp.exec(html))) {
  10605. index = tokenRegExp.lastIndex;
  10606. if (matches[1] === '/') { // End element
  10607. count--;
  10608. } else if (!matches[1]) { // Start element
  10609. if (matches[2] in shortEndedElements) {
  10610. continue;
  10611. }
  10612. count++;
  10613. }
  10614. if (count === 0) {
  10615. break;
  10616. }
  10617. }
  10618. return index;
  10619. }
  10620. /**
  10621. * Constructs a new SaxParser instance.
  10622. *
  10623. * @constructor
  10624. * @method SaxParser
  10625. * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
  10626. * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
  10627. */
  10628. function SaxParser(settings, schema) {
  10629. var self = this;
  10630. function noop() {}
  10631. settings = settings || {};
  10632. self.schema = schema = schema || new Schema();
  10633. if (settings.fix_self_closing !== false) {
  10634. settings.fix_self_closing = true;
  10635. }
  10636. // Add handler functions from settings and setup default handlers
  10637. each('comment cdata text start end pi doctype'.split(' '), function(name) {
  10638. if (name) {
  10639. self[name] = settings[name] || noop;
  10640. }
  10641. });
  10642. /**
  10643. * Parses the specified HTML string and executes the callbacks for each item it finds.
  10644. *
  10645. * @example
  10646. * new SaxParser({...}).parse('<b>text</b>');
  10647. * @method parse
  10648. * @param {String} html Html string to sax parse.
  10649. */
  10650. self.parse = function(html) {
  10651. var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name;
  10652. var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded;
  10653. var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
  10654. var attributesRequired, attributesDefault, attributesForced;
  10655. var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
  10656. var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster');
  10657. var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i;
  10658. function processEndTag(name) {
  10659. var pos, i;
  10660. // Find position of parent of the same type
  10661. pos = stack.length;
  10662. while (pos--) {
  10663. if (stack[pos].name === name) {
  10664. break;
  10665. }
  10666. }
  10667. // Found parent
  10668. if (pos >= 0) {
  10669. // Close all the open elements
  10670. for (i = stack.length - 1; i >= pos; i--) {
  10671. name = stack[i];
  10672. if (name.valid) {
  10673. self.end(name.name);
  10674. }
  10675. }
  10676. // Remove the open elements from the stack
  10677. stack.length = pos;
  10678. }
  10679. }
  10680. function parseAttribute(match, name, value, val2, val3) {
  10681. var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g;
  10682. name = name.toLowerCase();
  10683. value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
  10684. // Validate name and value pass through all data- attributes
  10685. if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
  10686. attrRule = validAttributesMap[name];
  10687. // Find rule by pattern matching
  10688. if (!attrRule && validAttributePatterns) {
  10689. i = validAttributePatterns.length;
  10690. while (i--) {
  10691. attrRule = validAttributePatterns[i];
  10692. if (attrRule.pattern.test(name)) {
  10693. break;
  10694. }
  10695. }
  10696. // No rule matched
  10697. if (i === -1) {
  10698. attrRule = null;
  10699. }
  10700. }
  10701. // No attribute rule found
  10702. if (!attrRule) {
  10703. return;
  10704. }
  10705. // Validate value
  10706. if (attrRule.validValues && !(value in attrRule.validValues)) {
  10707. return;
  10708. }
  10709. }
  10710. // Block any javascript: urls or non image data uris
  10711. if (filteredUrlAttrs[name] && !settings.allow_script_urls) {
  10712. var uri = value.replace(trimRegExp, '');
  10713. try {
  10714. // Might throw malformed URI sequence
  10715. uri = decodeURIComponent(uri);
  10716. } catch (ex) {
  10717. // Fallback to non UTF-8 decoder
  10718. uri = unescape(uri);
  10719. }
  10720. if (scriptUriRegExp.test(uri)) {
  10721. return;
  10722. }
  10723. if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) {
  10724. return;
  10725. }
  10726. }
  10727. // Add attribute to list and map
  10728. attrList.map[name] = value;
  10729. attrList.push({
  10730. name: name,
  10731. value: value
  10732. });
  10733. }
  10734. // Precompile RegExps and map objects
  10735. tokenRegExp = new RegExp('<(?:' +
  10736. '(?:!--([\\w\\W]*?)-->)|' + // Comment
  10737. '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
  10738. '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
  10739. '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
  10740. '(?:\\/([^>]+)>)|' + // End element
  10741. '(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
  10742. ')', 'g');
  10743. attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
  10744. // Setup lookup tables for empty elements and boolean attributes
  10745. shortEndedElements = schema.getShortEndedElements();
  10746. selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
  10747. fillAttrsMap = schema.getBoolAttrs();
  10748. validate = settings.validate;
  10749. removeInternalElements = settings.remove_internals;
  10750. fixSelfClosing = settings.fix_self_closing;
  10751. specialElements = schema.getSpecialElements();
  10752. while ((matches = tokenRegExp.exec(html))) {
  10753. // Text
  10754. if (index < matches.index) {
  10755. self.text(decode(html.substr(index, matches.index - index)));
  10756. }
  10757. if ((value = matches[6])) { // End element
  10758. value = value.toLowerCase();
  10759. // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
  10760. if (value.charAt(0) === ':') {
  10761. value = value.substr(1);
  10762. }
  10763. processEndTag(value);
  10764. } else if ((value = matches[7])) { // Start element
  10765. value = value.toLowerCase();
  10766. // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
  10767. if (value.charAt(0) === ':') {
  10768. value = value.substr(1);
  10769. }
  10770. isShortEnded = value in shortEndedElements;
  10771. // Is self closing tag for example an <li> after an open <li>
  10772. if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) {
  10773. processEndTag(value);
  10774. }
  10775. // Validate element
  10776. if (!validate || (elementRule = schema.getElementRule(value))) {
  10777. isValidElement = true;
  10778. // Grab attributes map and patters when validation is enabled
  10779. if (validate) {
  10780. validAttributesMap = elementRule.attributes;
  10781. validAttributePatterns = elementRule.attributePatterns;
  10782. }
  10783. // Parse attributes
  10784. if ((attribsValue = matches[8])) {
  10785. isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
  10786. // If the element has internal attributes then remove it if we are told to do so
  10787. if (isInternalElement && removeInternalElements) {
  10788. isValidElement = false;
  10789. }
  10790. attrList = [];
  10791. attrList.map = {};
  10792. attribsValue.replace(attrRegExp, parseAttribute);
  10793. } else {
  10794. attrList = [];
  10795. attrList.map = {};
  10796. }
  10797. // Process attributes if validation is enabled
  10798. if (validate && !isInternalElement) {
  10799. attributesRequired = elementRule.attributesRequired;
  10800. attributesDefault = elementRule.attributesDefault;
  10801. attributesForced = elementRule.attributesForced;
  10802. anyAttributesRequired = elementRule.removeEmptyAttrs;
  10803. // Check if any attribute exists
  10804. if (anyAttributesRequired && !attrList.length) {
  10805. isValidElement = false;
  10806. }
  10807. // Handle forced attributes
  10808. if (attributesForced) {
  10809. i = attributesForced.length;
  10810. while (i--) {
  10811. attr = attributesForced[i];
  10812. name = attr.name;
  10813. attrValue = attr.value;
  10814. if (attrValue === '{$uid}') {
  10815. attrValue = 'mce_' + idCount++;
  10816. }
  10817. attrList.map[name] = attrValue;
  10818. attrList.push({name: name, value: attrValue});
  10819. }
  10820. }
  10821. // Handle default attributes
  10822. if (attributesDefault) {
  10823. i = attributesDefault.length;
  10824. while (i--) {
  10825. attr = attributesDefault[i];
  10826. name = attr.name;
  10827. if (!(name in attrList.map)) {
  10828. attrValue = attr.value;
  10829. if (attrValue === '{$uid}') {
  10830. attrValue = 'mce_' + idCount++;
  10831. }
  10832. attrList.map[name] = attrValue;
  10833. attrList.push({name: name, value: attrValue});
  10834. }
  10835. }
  10836. }
  10837. // Handle required attributes
  10838. if (attributesRequired) {
  10839. i = attributesRequired.length;
  10840. while (i--) {
  10841. if (attributesRequired[i] in attrList.map) {
  10842. break;
  10843. }
  10844. }
  10845. // None of the required attributes where found
  10846. if (i === -1) {
  10847. isValidElement = false;
  10848. }
  10849. }
  10850. // Invalidate element if it's marked as bogus
  10851. if ((attr = attrList.map['data-mce-bogus'])) {
  10852. if (attr === 'all') {
  10853. index = findEndTag(schema, html, tokenRegExp.lastIndex);
  10854. tokenRegExp.lastIndex = index;
  10855. continue;
  10856. }
  10857. isValidElement = false;
  10858. }
  10859. }
  10860. if (isValidElement) {
  10861. self.start(value, attrList, isShortEnded);
  10862. }
  10863. } else {
  10864. isValidElement = false;
  10865. }
  10866. // Treat script, noscript and style a bit different since they may include code that looks like elements
  10867. if ((endRegExp = specialElements[value])) {
  10868. endRegExp.lastIndex = index = matches.index + matches[0].length;
  10869. if ((matches = endRegExp.exec(html))) {
  10870. if (isValidElement) {
  10871. text = html.substr(index, matches.index - index);
  10872. }
  10873. index = matches.index + matches[0].length;
  10874. } else {
  10875. text = html.substr(index);
  10876. index = html.length;
  10877. }
  10878. if (isValidElement) {
  10879. if (text.length > 0) {
  10880. self.text(text, true);
  10881. }
  10882. self.end(value);
  10883. }
  10884. tokenRegExp.lastIndex = index;
  10885. continue;
  10886. }
  10887. // Push value on to stack
  10888. if (!isShortEnded) {
  10889. if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) {
  10890. stack.push({name: value, valid: isValidElement});
  10891. } else if (isValidElement) {
  10892. self.end(value);
  10893. }
  10894. }
  10895. } else if ((value = matches[1])) { // Comment
  10896. // Padd comment value to avoid browsers from parsing invalid comments as HTML
  10897. if (value.charAt(0) === '>') {
  10898. value = ' ' + value;
  10899. }
  10900. if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') {
  10901. value = ' ' + value;
  10902. }
  10903. self.comment(value);
  10904. } else if ((value = matches[2])) { // CDATA
  10905. self.cdata(value);
  10906. } else if ((value = matches[3])) { // DOCTYPE
  10907. self.doctype(value);
  10908. } else if ((value = matches[4])) { // PI
  10909. self.pi(value, matches[5]);
  10910. }
  10911. index = matches.index + matches[0].length;
  10912. }
  10913. // Text
  10914. if (index < html.length) {
  10915. self.text(decode(html.substr(index)));
  10916. }
  10917. // Close any open elements
  10918. for (i = stack.length - 1; i >= 0; i--) {
  10919. value = stack[i];
  10920. if (value.valid) {
  10921. self.end(value.name);
  10922. }
  10923. }
  10924. };
  10925. }
  10926. SaxParser.findEndTag = findEndTag;
  10927. return SaxParser;
  10928. });
  10929. // Included from: js/tinymce/classes/html/DomParser.js
  10930. /**
  10931. * DomParser.js
  10932. *
  10933. * Released under LGPL License.
  10934. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  10935. *
  10936. * License: http://www.tinymce.com/license
  10937. * Contributing: http://www.tinymce.com/contributing
  10938. */
  10939. /**
  10940. * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make
  10941. * sure that the node tree is valid according to the specified schema.
  10942. * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p>
  10943. *
  10944. * @example
  10945. * var parser = new tinymce.html.DomParser({validate: true}, schema);
  10946. * var rootNode = parser.parse('<h1>content</h1>');
  10947. *
  10948. * @class tinymce.html.DomParser
  10949. * @version 3.4
  10950. */
  10951. define("tinymce/html/DomParser", [
  10952. "tinymce/html/Node",
  10953. "tinymce/html/Schema",
  10954. "tinymce/html/SaxParser",
  10955. "tinymce/util/Tools"
  10956. ], function(Node, Schema, SaxParser, Tools) {
  10957. var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend;
  10958. /**
  10959. * Constructs a new DomParser instance.
  10960. *
  10961. * @constructor
  10962. * @method DomParser
  10963. * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
  10964. * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
  10965. */
  10966. return function(settings, schema) {
  10967. var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
  10968. settings = settings || {};
  10969. settings.validate = "validate" in settings ? settings.validate : true;
  10970. settings.root_name = settings.root_name || 'body';
  10971. self.schema = schema = schema || new Schema();
  10972. function fixInvalidChildren(nodes) {
  10973. var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i;
  10974. var nonEmptyElements, nonSplitableElements, textBlockElements, specialElements, sibling, nextNode;
  10975. nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table');
  10976. nonEmptyElements = schema.getNonEmptyElements();
  10977. textBlockElements = schema.getTextBlockElements();
  10978. specialElements = schema.getSpecialElements();
  10979. for (ni = 0; ni < nodes.length; ni++) {
  10980. node = nodes[ni];
  10981. // Already removed or fixed
  10982. if (!node.parent || node.fixed) {
  10983. continue;
  10984. }
  10985. // If the invalid element is a text block and the text block is within a parent LI element
  10986. // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
  10987. if (textBlockElements[node.name] && node.parent.name == 'li') {
  10988. // Move sibling text blocks after LI element
  10989. sibling = node.next;
  10990. while (sibling) {
  10991. if (textBlockElements[sibling.name]) {
  10992. sibling.name = 'li';
  10993. sibling.fixed = true;
  10994. node.parent.insert(sibling, node.parent);
  10995. } else {
  10996. break;
  10997. }
  10998. sibling = sibling.next;
  10999. }
  11000. // Unwrap current text block
  11001. node.unwrap(node);
  11002. continue;
  11003. }
  11004. // Get list of all parent nodes until we find a valid parent to stick the child into
  11005. parents = [node];
  11006. for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) &&
  11007. !nonSplitableElements[parent.name]; parent = parent.parent) {
  11008. parents.push(parent);
  11009. }
  11010. // Found a suitable parent
  11011. if (parent && parents.length > 1) {
  11012. // Reverse the array since it makes looping easier
  11013. parents.reverse();
  11014. // Clone the related parent and insert that after the moved node
  11015. newParent = currentNode = self.filterNode(parents[0].clone());
  11016. // Start cloning and moving children on the left side of the target node
  11017. for (i = 0; i < parents.length - 1; i++) {
  11018. if (schema.isValidChild(currentNode.name, parents[i].name)) {
  11019. tempNode = self.filterNode(parents[i].clone());
  11020. currentNode.append(tempNode);
  11021. } else {
  11022. tempNode = currentNode;
  11023. }
  11024. for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1];) {
  11025. nextNode = childNode.next;
  11026. tempNode.append(childNode);
  11027. childNode = nextNode;
  11028. }
  11029. currentNode = tempNode;
  11030. }
  11031. if (!newParent.isEmpty(nonEmptyElements)) {
  11032. parent.insert(newParent, parents[0], true);
  11033. parent.insert(node, newParent);
  11034. } else {
  11035. parent.insert(node, parents[0], true);
  11036. }
  11037. // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
  11038. parent = parents[0];
  11039. if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
  11040. parent.empty().remove();
  11041. }
  11042. } else if (node.parent) {
  11043. // If it's an LI try to find a UL/OL for it or wrap it
  11044. if (node.name === 'li') {
  11045. sibling = node.prev;
  11046. if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
  11047. sibling.append(node);
  11048. continue;
  11049. }
  11050. sibling = node.next;
  11051. if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
  11052. sibling.insert(node, sibling.firstChild, true);
  11053. continue;
  11054. }
  11055. node.wrap(self.filterNode(new Node('ul', 1)));
  11056. continue;
  11057. }
  11058. // Try wrapping the element in a DIV
  11059. if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
  11060. node.wrap(self.filterNode(new Node('div', 1)));
  11061. } else {
  11062. // We failed wrapping it, then remove or unwrap it
  11063. if (specialElements[node.name]) {
  11064. node.empty().remove();
  11065. } else {
  11066. node.unwrap();
  11067. }
  11068. }
  11069. }
  11070. }
  11071. }
  11072. /**
  11073. * Runs the specified node though the element and attributes filters.
  11074. *
  11075. * @method filterNode
  11076. * @param {tinymce.html.Node} Node the node to run filters on.
  11077. * @return {tinymce.html.Node} The passed in node.
  11078. */
  11079. self.filterNode = function(node) {
  11080. var i, name, list;
  11081. // Run element filters
  11082. if (name in nodeFilters) {
  11083. list = matchedNodes[name];
  11084. if (list) {
  11085. list.push(node);
  11086. } else {
  11087. matchedNodes[name] = [node];
  11088. }
  11089. }
  11090. // Run attribute filters
  11091. i = attributeFilters.length;
  11092. while (i--) {
  11093. name = attributeFilters[i].name;
  11094. if (name in node.attributes.map) {
  11095. list = matchedAttributes[name];
  11096. if (list) {
  11097. list.push(node);
  11098. } else {
  11099. matchedAttributes[name] = [node];
  11100. }
  11101. }
  11102. }
  11103. return node;
  11104. };
  11105. /**
  11106. * Adds a node filter function to the parser, the parser will collect the specified nodes by name
  11107. * and then execute the callback ones it has finished parsing the document.
  11108. *
  11109. * @example
  11110. * parser.addNodeFilter('p,h1', function(nodes, name) {
  11111. * for (var i = 0; i < nodes.length; i++) {
  11112. * console.log(nodes[i].name);
  11113. * }
  11114. * });
  11115. * @method addNodeFilter
  11116. * @method {String} name Comma separated list of nodes to collect.
  11117. * @param {function} callback Callback function to execute once it has collected nodes.
  11118. */
  11119. self.addNodeFilter = function(name, callback) {
  11120. each(explode(name), function(name) {
  11121. var list = nodeFilters[name];
  11122. if (!list) {
  11123. nodeFilters[name] = list = [];
  11124. }
  11125. list.push(callback);
  11126. });
  11127. };
  11128. /**
  11129. * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes
  11130. * and then execute the callback ones it has finished parsing the document.
  11131. *
  11132. * @example
  11133. * parser.addAttributeFilter('src,href', function(nodes, name) {
  11134. * for (var i = 0; i < nodes.length; i++) {
  11135. * console.log(nodes[i].name);
  11136. * }
  11137. * });
  11138. * @method addAttributeFilter
  11139. * @method {String} name Comma separated list of nodes to collect.
  11140. * @param {function} callback Callback function to execute once it has collected nodes.
  11141. */
  11142. self.addAttributeFilter = function(name, callback) {
  11143. each(explode(name), function(name) {
  11144. var i;
  11145. for (i = 0; i < attributeFilters.length; i++) {
  11146. if (attributeFilters[i].name === name) {
  11147. attributeFilters[i].callbacks.push(callback);
  11148. return;
  11149. }
  11150. }
  11151. attributeFilters.push({name: name, callbacks: [callback]});
  11152. });
  11153. };
  11154. /**
  11155. * Parses the specified HTML string into a DOM like node tree and returns the result.
  11156. *
  11157. * @example
  11158. * var rootNode = new DomParser({...}).parse('<b>text</b>');
  11159. * @method parse
  11160. * @param {String} html Html string to sax parse.
  11161. * @param {Object} args Optional args object that gets passed to all filter functions.
  11162. * @return {tinymce.html.Node} Root node containing the tree.
  11163. */
  11164. self.parse = function(html, args) {
  11165. var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate;
  11166. var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement;
  11167. var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements;
  11168. var children, nonEmptyElements, rootBlockName;
  11169. args = args || {};
  11170. matchedNodes = {};
  11171. matchedAttributes = {};
  11172. blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
  11173. nonEmptyElements = schema.getNonEmptyElements();
  11174. children = schema.children;
  11175. validate = settings.validate;
  11176. rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
  11177. whiteSpaceElements = schema.getWhiteSpaceElements();
  11178. startWhiteSpaceRegExp = /^[ \t\r\n]+/;
  11179. endWhiteSpaceRegExp = /[ \t\r\n]+$/;
  11180. allWhiteSpaceRegExp = /[ \t\r\n]+/g;
  11181. isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
  11182. function addRootBlocks() {
  11183. var node = rootNode.firstChild, next, rootBlockNode;
  11184. // Removes whitespace at beginning and end of block so:
  11185. // <p> x </p> -> <p>x</p>
  11186. function trim(rootBlockNode) {
  11187. if (rootBlockNode) {
  11188. node = rootBlockNode.firstChild;
  11189. if (node && node.type == 3) {
  11190. node.value = node.value.replace(startWhiteSpaceRegExp, '');
  11191. }
  11192. node = rootBlockNode.lastChild;
  11193. if (node && node.type == 3) {
  11194. node.value = node.value.replace(endWhiteSpaceRegExp, '');
  11195. }
  11196. }
  11197. }
  11198. // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root
  11199. if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
  11200. return;
  11201. }
  11202. while (node) {
  11203. next = node.next;
  11204. if (node.type == 3 || (node.type == 1 && node.name !== 'p' &&
  11205. !blockElements[node.name] && !node.attr('data-mce-type'))) {
  11206. if (!rootBlockNode) {
  11207. // Create a new root block element
  11208. rootBlockNode = createNode(rootBlockName, 1);
  11209. rootBlockNode.attr(settings.forced_root_block_attrs);
  11210. rootNode.insert(rootBlockNode, node);
  11211. rootBlockNode.append(node);
  11212. } else {
  11213. rootBlockNode.append(node);
  11214. }
  11215. } else {
  11216. trim(rootBlockNode);
  11217. rootBlockNode = null;
  11218. }
  11219. node = next;
  11220. }
  11221. trim(rootBlockNode);
  11222. }
  11223. function createNode(name, type) {
  11224. var node = new Node(name, type), list;
  11225. if (name in nodeFilters) {
  11226. list = matchedNodes[name];
  11227. if (list) {
  11228. list.push(node);
  11229. } else {
  11230. matchedNodes[name] = [node];
  11231. }
  11232. }
  11233. return node;
  11234. }
  11235. function removeWhitespaceBefore(node) {
  11236. var textNode, textNodeNext, textVal, sibling, blockElements = schema.getBlockElements();
  11237. for (textNode = node.prev; textNode && textNode.type === 3;) {
  11238. textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
  11239. // Found a text node with non whitespace then trim that and break
  11240. if (textVal.length > 0) {
  11241. textNode.value = textVal;
  11242. return;
  11243. }
  11244. textNodeNext = textNode.next;
  11245. // Fix for bug #7543 where bogus nodes would produce empty
  11246. // text nodes and these would be removed if a nested list was before it
  11247. if (textNodeNext) {
  11248. if (textNodeNext.type == 3 && textNodeNext.value.length) {
  11249. textNode = textNode.prev;
  11250. continue;
  11251. }
  11252. if (!blockElements[textNodeNext.name] && textNodeNext.name != 'script' && textNodeNext.name != 'style') {
  11253. textNode = textNode.prev;
  11254. continue;
  11255. }
  11256. }
  11257. sibling = textNode.prev;
  11258. textNode.remove();
  11259. textNode = sibling;
  11260. }
  11261. }
  11262. function cloneAndExcludeBlocks(input) {
  11263. var name, output = {};
  11264. for (name in input) {
  11265. if (name !== 'li' && name != 'p') {
  11266. output[name] = input[name];
  11267. }
  11268. }
  11269. return output;
  11270. }
  11271. parser = new SaxParser({
  11272. validate: validate,
  11273. allow_script_urls: settings.allow_script_urls,
  11274. allow_conditional_comments: settings.allow_conditional_comments,
  11275. // Exclude P and LI from DOM parsing since it's treated better by the DOM parser
  11276. self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
  11277. cdata: function(text) {
  11278. node.append(createNode('#cdata', 4)).value = text;
  11279. },
  11280. text: function(text, raw) {
  11281. var textNode;
  11282. // Trim all redundant whitespace on non white space elements
  11283. if (!isInWhiteSpacePreservedElement) {
  11284. text = text.replace(allWhiteSpaceRegExp, ' ');
  11285. if (node.lastChild && blockElements[node.lastChild.name]) {
  11286. text = text.replace(startWhiteSpaceRegExp, '');
  11287. }
  11288. }
  11289. // Do we need to create the node
  11290. if (text.length !== 0) {
  11291. textNode = createNode('#text', 3);
  11292. textNode.raw = !!raw;
  11293. node.append(textNode).value = text;
  11294. }
  11295. },
  11296. comment: function(text) {
  11297. node.append(createNode('#comment', 8)).value = text;
  11298. },
  11299. pi: function(name, text) {
  11300. node.append(createNode(name, 7)).value = text;
  11301. removeWhitespaceBefore(node);
  11302. },
  11303. doctype: function(text) {
  11304. var newNode;
  11305. newNode = node.append(createNode('#doctype', 10));
  11306. newNode.value = text;
  11307. removeWhitespaceBefore(node);
  11308. },
  11309. start: function(name, attrs, empty) {
  11310. var newNode, attrFiltersLen, elementRule, attrName, parent;
  11311. elementRule = validate ? schema.getElementRule(name) : {};
  11312. if (elementRule) {
  11313. newNode = createNode(elementRule.outputName || name, 1);
  11314. newNode.attributes = attrs;
  11315. newNode.shortEnded = empty;
  11316. node.append(newNode);
  11317. // Check if node is valid child of the parent node is the child is
  11318. // unknown we don't collect it since it's probably a custom element
  11319. parent = children[node.name];
  11320. if (parent && children[newNode.name] && !parent[newNode.name]) {
  11321. invalidChildren.push(newNode);
  11322. }
  11323. attrFiltersLen = attributeFilters.length;
  11324. while (attrFiltersLen--) {
  11325. attrName = attributeFilters[attrFiltersLen].name;
  11326. if (attrName in attrs.map) {
  11327. list = matchedAttributes[attrName];
  11328. if (list) {
  11329. list.push(newNode);
  11330. } else {
  11331. matchedAttributes[attrName] = [newNode];
  11332. }
  11333. }
  11334. }
  11335. // Trim whitespace before block
  11336. if (blockElements[name]) {
  11337. removeWhitespaceBefore(newNode);
  11338. }
  11339. // Change current node if the element wasn't empty i.e not <br /> or <img />
  11340. if (!empty) {
  11341. node = newNode;
  11342. }
  11343. // Check if we are inside a whitespace preserved element
  11344. if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
  11345. isInWhiteSpacePreservedElement = true;
  11346. }
  11347. }
  11348. },
  11349. end: function(name) {
  11350. var textNode, elementRule, text, sibling, tempNode;
  11351. elementRule = validate ? schema.getElementRule(name) : {};
  11352. if (elementRule) {
  11353. if (blockElements[name]) {
  11354. if (!isInWhiteSpacePreservedElement) {
  11355. // Trim whitespace of the first node in a block
  11356. textNode = node.firstChild;
  11357. if (textNode && textNode.type === 3) {
  11358. text = textNode.value.replace(startWhiteSpaceRegExp, '');
  11359. // Any characters left after trim or should we remove it
  11360. if (text.length > 0) {
  11361. textNode.value = text;
  11362. textNode = textNode.next;
  11363. } else {
  11364. sibling = textNode.next;
  11365. textNode.remove();
  11366. textNode = sibling;
  11367. // Remove any pure whitespace siblings
  11368. while (textNode && textNode.type === 3) {
  11369. text = textNode.value;
  11370. sibling = textNode.next;
  11371. if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
  11372. textNode.remove();
  11373. textNode = sibling;
  11374. }
  11375. textNode = sibling;
  11376. }
  11377. }
  11378. }
  11379. // Trim whitespace of the last node in a block
  11380. textNode = node.lastChild;
  11381. if (textNode && textNode.type === 3) {
  11382. text = textNode.value.replace(endWhiteSpaceRegExp, '');
  11383. // Any characters left after trim or should we remove it
  11384. if (text.length > 0) {
  11385. textNode.value = text;
  11386. textNode = textNode.prev;
  11387. } else {
  11388. sibling = textNode.prev;
  11389. textNode.remove();
  11390. textNode = sibling;
  11391. // Remove any pure whitespace siblings
  11392. while (textNode && textNode.type === 3) {
  11393. text = textNode.value;
  11394. sibling = textNode.prev;
  11395. if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
  11396. textNode.remove();
  11397. textNode = sibling;
  11398. }
  11399. textNode = sibling;
  11400. }
  11401. }
  11402. }
  11403. }
  11404. // Trim start white space
  11405. // Removed due to: #5424
  11406. /*textNode = node.prev;
  11407. if (textNode && textNode.type === 3) {
  11408. text = textNode.value.replace(startWhiteSpaceRegExp, '');
  11409. if (text.length > 0)
  11410. textNode.value = text;
  11411. else
  11412. textNode.remove();
  11413. }*/
  11414. }
  11415. // Check if we exited a whitespace preserved element
  11416. if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
  11417. isInWhiteSpacePreservedElement = false;
  11418. }
  11419. // Handle empty nodes
  11420. if (elementRule.removeEmpty || elementRule.paddEmpty) {
  11421. if (node.isEmpty(nonEmptyElements)) {
  11422. if (elementRule.paddEmpty) {
  11423. node.empty().append(new Node('#text', '3')).value = '\u00a0';
  11424. } else {
  11425. // Leave nodes that have a name like <a name="name">
  11426. if (!node.attributes.map.name && !node.attributes.map.id) {
  11427. tempNode = node.parent;
  11428. if (blockElements[node.name]) {
  11429. node.empty().remove();
  11430. } else {
  11431. node.unwrap();
  11432. }
  11433. node = tempNode;
  11434. return;
  11435. }
  11436. }
  11437. }
  11438. }
  11439. node = node.parent;
  11440. }
  11441. }
  11442. }, schema);
  11443. rootNode = node = new Node(args.context || settings.root_name, 11);
  11444. parser.parse(html);
  11445. // Fix invalid children or report invalid children in a contextual parsing
  11446. if (validate && invalidChildren.length) {
  11447. if (!args.context) {
  11448. fixInvalidChildren(invalidChildren);
  11449. } else {
  11450. args.invalid = true;
  11451. }
  11452. }
  11453. // Wrap nodes in the root into block elements if the root is body
  11454. if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
  11455. addRootBlocks();
  11456. }
  11457. // Run filters only when the contents is valid
  11458. if (!args.invalid) {
  11459. // Run node filters
  11460. for (name in matchedNodes) {
  11461. list = nodeFilters[name];
  11462. nodes = matchedNodes[name];
  11463. // Remove already removed children
  11464. fi = nodes.length;
  11465. while (fi--) {
  11466. if (!nodes[fi].parent) {
  11467. nodes.splice(fi, 1);
  11468. }
  11469. }
  11470. for (i = 0, l = list.length; i < l; i++) {
  11471. list[i](nodes, name, args);
  11472. }
  11473. }
  11474. // Run attribute filters
  11475. for (i = 0, l = attributeFilters.length; i < l; i++) {
  11476. list = attributeFilters[i];
  11477. if (list.name in matchedAttributes) {
  11478. nodes = matchedAttributes[list.name];
  11479. // Remove already removed children
  11480. fi = nodes.length;
  11481. while (fi--) {
  11482. if (!nodes[fi].parent) {
  11483. nodes.splice(fi, 1);
  11484. }
  11485. }
  11486. for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
  11487. list.callbacks[fi](nodes, list.name, args);
  11488. }
  11489. }
  11490. }
  11491. }
  11492. return rootNode;
  11493. };
  11494. // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
  11495. // make it possible to place the caret inside empty blocks. This logic tries to remove
  11496. // these elements and keep br elements that where intended to be there intact
  11497. if (settings.remove_trailing_brs) {
  11498. self.addNodeFilter('br', function(nodes) {
  11499. var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
  11500. var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
  11501. var elementRule, textNode;
  11502. // Remove brs from body element as well
  11503. blockElements.body = 1;
  11504. // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
  11505. for (i = 0; i < l; i++) {
  11506. node = nodes[i];
  11507. parent = node.parent;
  11508. if (blockElements[node.parent.name] && node === parent.lastChild) {
  11509. // Loop all nodes to the left of the current node and check for other BR elements
  11510. // excluding bookmarks since they are invisible
  11511. prev = node.prev;
  11512. while (prev) {
  11513. prevName = prev.name;
  11514. // Ignore bookmarks
  11515. if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
  11516. // Found a non BR element
  11517. if (prevName !== "br") {
  11518. break;
  11519. }
  11520. // Found another br it's a <br><br> structure then don't remove anything
  11521. if (prevName === 'br') {
  11522. node = null;
  11523. break;
  11524. }
  11525. }
  11526. prev = prev.prev;
  11527. }
  11528. if (node) {
  11529. node.remove();
  11530. // Is the parent to be considered empty after we removed the BR
  11531. if (parent.isEmpty(nonEmptyElements)) {
  11532. elementRule = schema.getElementRule(parent.name);
  11533. // Remove or padd the element depending on schema rule
  11534. if (elementRule) {
  11535. if (elementRule.removeEmpty) {
  11536. parent.remove();
  11537. } else if (elementRule.paddEmpty) {
  11538. parent.empty().append(new Node('#text', 3)).value = '\u00a0';
  11539. }
  11540. }
  11541. }
  11542. }
  11543. } else {
  11544. // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p>
  11545. // so they become <p><b><i>&nbsp;</i></b></p>
  11546. lastParent = node;
  11547. while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
  11548. lastParent = parent;
  11549. if (blockElements[parent.name]) {
  11550. break;
  11551. }
  11552. parent = parent.parent;
  11553. }
  11554. if (lastParent === parent) {
  11555. textNode = new Node('#text', 3);
  11556. textNode.value = '\u00a0';
  11557. node.replace(textNode);
  11558. }
  11559. }
  11560. }
  11561. });
  11562. }
  11563. // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
  11564. if (!settings.allow_html_in_named_anchor) {
  11565. self.addAttributeFilter('id,name', function(nodes) {
  11566. var i = nodes.length, sibling, prevSibling, parent, node;
  11567. while (i--) {
  11568. node = nodes[i];
  11569. if (node.name === 'a' && node.firstChild && !node.attr('href')) {
  11570. parent = node.parent;
  11571. // Move children after current node
  11572. sibling = node.lastChild;
  11573. do {
  11574. prevSibling = sibling.prev;
  11575. parent.insert(sibling, node);
  11576. sibling = prevSibling;
  11577. } while (sibling);
  11578. }
  11579. }
  11580. });
  11581. }
  11582. if (settings.validate && schema.getValidClasses()) {
  11583. self.addAttributeFilter('class', function(nodes) {
  11584. var i = nodes.length, node, classList, ci, className, classValue;
  11585. var validClasses = schema.getValidClasses(), validClassesMap, valid;
  11586. while (i--) {
  11587. node = nodes[i];
  11588. classList = node.attr('class').split(' ');
  11589. classValue = '';
  11590. for (ci = 0; ci < classList.length; ci++) {
  11591. className = classList[ci];
  11592. valid = false;
  11593. validClassesMap = validClasses['*'];
  11594. if (validClassesMap && validClassesMap[className]) {
  11595. valid = true;
  11596. }
  11597. validClassesMap = validClasses[node.name];
  11598. if (!valid && validClassesMap && validClassesMap[className]) {
  11599. valid = true;
  11600. }
  11601. if (valid) {
  11602. if (classValue) {
  11603. classValue += ' ';
  11604. }
  11605. classValue += className;
  11606. }
  11607. }
  11608. if (!classValue.length) {
  11609. classValue = null;
  11610. }
  11611. node.attr('class', classValue);
  11612. }
  11613. });
  11614. }
  11615. };
  11616. });
  11617. // Included from: js/tinymce/classes/html/Writer.js
  11618. /**
  11619. * Writer.js
  11620. *
  11621. * Released under LGPL License.
  11622. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  11623. *
  11624. * License: http://www.tinymce.com/license
  11625. * Contributing: http://www.tinymce.com/contributing
  11626. */
  11627. /**
  11628. * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser.
  11629. *
  11630. * @class tinymce.html.Writer
  11631. * @example
  11632. * var writer = new tinymce.html.Writer({indent: true});
  11633. * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>');
  11634. * console.log(writer.getContent());
  11635. *
  11636. * @class tinymce.html.Writer
  11637. * @version 3.4
  11638. */
  11639. define("tinymce/html/Writer", [
  11640. "tinymce/html/Entities",
  11641. "tinymce/util/Tools"
  11642. ], function(Entities, Tools) {
  11643. var makeMap = Tools.makeMap;
  11644. /**
  11645. * Constructs a new Writer instance.
  11646. *
  11647. * @constructor
  11648. * @method Writer
  11649. * @param {Object} settings Name/value settings object.
  11650. */
  11651. return function(settings) {
  11652. var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
  11653. settings = settings || {};
  11654. indent = settings.indent;
  11655. indentBefore = makeMap(settings.indent_before || '');
  11656. indentAfter = makeMap(settings.indent_after || '');
  11657. encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
  11658. htmlOutput = settings.element_format == "html";
  11659. return {
  11660. /**
  11661. * Writes the a start element such as <p id="a">.
  11662. *
  11663. * @method start
  11664. * @param {String} name Name of the element.
  11665. * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
  11666. * @param {Boolean} empty Optional empty state if the tag should end like <br />.
  11667. */
  11668. start: function(name, attrs, empty) {
  11669. var i, l, attr, value;
  11670. if (indent && indentBefore[name] && html.length > 0) {
  11671. value = html[html.length - 1];
  11672. if (value.length > 0 && value !== '\n') {
  11673. html.push('\n');
  11674. }
  11675. }
  11676. html.push('<', name);
  11677. if (attrs) {
  11678. for (i = 0, l = attrs.length; i < l; i++) {
  11679. attr = attrs[i];
  11680. html.push(' ', attr.name, '="', encode(attr.value, true), '"');
  11681. }
  11682. }
  11683. if (!empty || htmlOutput) {
  11684. html[html.length] = '>';
  11685. } else {
  11686. html[html.length] = ' />';
  11687. }
  11688. if (empty && indent && indentAfter[name] && html.length > 0) {
  11689. value = html[html.length - 1];
  11690. if (value.length > 0 && value !== '\n') {
  11691. html.push('\n');
  11692. }
  11693. }
  11694. },
  11695. /**
  11696. * Writes the a end element such as </p>.
  11697. *
  11698. * @method end
  11699. * @param {String} name Name of the element.
  11700. */
  11701. end: function(name) {
  11702. var value;
  11703. /*if (indent && indentBefore[name] && html.length > 0) {
  11704. value = html[html.length - 1];
  11705. if (value.length > 0 && value !== '\n')
  11706. html.push('\n');
  11707. }*/
  11708. html.push('</', name, '>');
  11709. if (indent && indentAfter[name] && html.length > 0) {
  11710. value = html[html.length - 1];
  11711. if (value.length > 0 && value !== '\n') {
  11712. html.push('\n');
  11713. }
  11714. }
  11715. },
  11716. /**
  11717. * Writes a text node.
  11718. *
  11719. * @method text
  11720. * @param {String} text String to write out.
  11721. * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
  11722. */
  11723. text: function(text, raw) {
  11724. if (text.length > 0) {
  11725. html[html.length] = raw ? text : encode(text);
  11726. }
  11727. },
  11728. /**
  11729. * Writes a cdata node such as <![CDATA[data]]>.
  11730. *
  11731. * @method cdata
  11732. * @param {String} text String to write out inside the cdata.
  11733. */
  11734. cdata: function(text) {
  11735. html.push('<![CDATA[', text, ']]>');
  11736. },
  11737. /**
  11738. * Writes a comment node such as <!-- Comment -->.
  11739. *
  11740. * @method cdata
  11741. * @param {String} text String to write out inside the comment.
  11742. */
  11743. comment: function(text) {
  11744. html.push('<!--', text, '-->');
  11745. },
  11746. /**
  11747. * Writes a PI node such as <?xml attr="value" ?>.
  11748. *
  11749. * @method pi
  11750. * @param {String} name Name of the pi.
  11751. * @param {String} text String to write out inside the pi.
  11752. */
  11753. pi: function(name, text) {
  11754. if (text) {
  11755. html.push('<?', name, ' ', encode(text), '?>');
  11756. } else {
  11757. html.push('<?', name, '?>');
  11758. }
  11759. if (indent) {
  11760. html.push('\n');
  11761. }
  11762. },
  11763. /**
  11764. * Writes a doctype node such as <!DOCTYPE data>.
  11765. *
  11766. * @method doctype
  11767. * @param {String} text String to write out inside the doctype.
  11768. */
  11769. doctype: function(text) {
  11770. html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
  11771. },
  11772. /**
  11773. * Resets the internal buffer if one wants to reuse the writer.
  11774. *
  11775. * @method reset
  11776. */
  11777. reset: function() {
  11778. html.length = 0;
  11779. },
  11780. /**
  11781. * Returns the contents that got serialized.
  11782. *
  11783. * @method getContent
  11784. * @return {String} HTML contents that got written down.
  11785. */
  11786. getContent: function() {
  11787. return html.join('').replace(/\n$/, '');
  11788. }
  11789. };
  11790. };
  11791. });
  11792. // Included from: js/tinymce/classes/html/Serializer.js
  11793. /**
  11794. * Serializer.js
  11795. *
  11796. * Released under LGPL License.
  11797. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  11798. *
  11799. * License: http://www.tinymce.com/license
  11800. * Contributing: http://www.tinymce.com/contributing
  11801. */
  11802. /**
  11803. * This class is used to serialize down the DOM tree into a string using a Writer instance.
  11804. *
  11805. *
  11806. * @example
  11807. * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
  11808. * @class tinymce.html.Serializer
  11809. * @version 3.4
  11810. */
  11811. define("tinymce/html/Serializer", [
  11812. "tinymce/html/Writer",
  11813. "tinymce/html/Schema"
  11814. ], function(Writer, Schema) {
  11815. /**
  11816. * Constructs a new Serializer instance.
  11817. *
  11818. * @constructor
  11819. * @method Serializer
  11820. * @param {Object} settings Name/value settings object.
  11821. * @param {tinymce.html.Schema} schema Schema instance to use.
  11822. */
  11823. return function(settings, schema) {
  11824. var self = this, writer = new Writer(settings);
  11825. settings = settings || {};
  11826. settings.validate = "validate" in settings ? settings.validate : true;
  11827. self.schema = schema = schema || new Schema();
  11828. self.writer = writer;
  11829. /**
  11830. * Serializes the specified node into a string.
  11831. *
  11832. * @example
  11833. * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
  11834. * @method serialize
  11835. * @param {tinymce.html.Node} node Node instance to serialize.
  11836. * @return {String} String with HTML based on DOM tree.
  11837. */
  11838. self.serialize = function(node) {
  11839. var handlers, validate;
  11840. validate = settings.validate;
  11841. handlers = {
  11842. // #text
  11843. 3: function(node) {
  11844. writer.text(node.value, node.raw);
  11845. },
  11846. // #comment
  11847. 8: function(node) {
  11848. writer.comment(node.value);
  11849. },
  11850. // Processing instruction
  11851. 7: function(node) {
  11852. writer.pi(node.name, node.value);
  11853. },
  11854. // Doctype
  11855. 10: function(node) {
  11856. writer.doctype(node.value);
  11857. },
  11858. // CDATA
  11859. 4: function(node) {
  11860. writer.cdata(node.value);
  11861. },
  11862. // Document fragment
  11863. 11: function(node) {
  11864. if ((node = node.firstChild)) {
  11865. do {
  11866. walk(node);
  11867. } while ((node = node.next));
  11868. }
  11869. }
  11870. };
  11871. writer.reset();
  11872. function walk(node) {
  11873. var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
  11874. if (!handler) {
  11875. name = node.name;
  11876. isEmpty = node.shortEnded;
  11877. attrs = node.attributes;
  11878. // Sort attributes
  11879. if (validate && attrs && attrs.length > 1) {
  11880. sortedAttrs = [];
  11881. sortedAttrs.map = {};
  11882. elementRule = schema.getElementRule(node.name);
  11883. if (elementRule) {
  11884. for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
  11885. attrName = elementRule.attributesOrder[i];
  11886. if (attrName in attrs.map) {
  11887. attrValue = attrs.map[attrName];
  11888. sortedAttrs.map[attrName] = attrValue;
  11889. sortedAttrs.push({name: attrName, value: attrValue});
  11890. }
  11891. }
  11892. for (i = 0, l = attrs.length; i < l; i++) {
  11893. attrName = attrs[i].name;
  11894. if (!(attrName in sortedAttrs.map)) {
  11895. attrValue = attrs.map[attrName];
  11896. sortedAttrs.map[attrName] = attrValue;
  11897. sortedAttrs.push({name: attrName, value: attrValue});
  11898. }
  11899. }
  11900. attrs = sortedAttrs;
  11901. }
  11902. }
  11903. writer.start(node.name, attrs, isEmpty);
  11904. if (!isEmpty) {
  11905. if ((node = node.firstChild)) {
  11906. do {
  11907. walk(node);
  11908. } while ((node = node.next));
  11909. }
  11910. writer.end(name);
  11911. }
  11912. } else {
  11913. handler(node);
  11914. }
  11915. }
  11916. // Serialize element and treat all non elements as fragments
  11917. if (node.type == 1 && !settings.inner) {
  11918. walk(node);
  11919. } else {
  11920. handlers[11](node);
  11921. }
  11922. return writer.getContent();
  11923. };
  11924. };
  11925. });
  11926. // Included from: js/tinymce/classes/dom/Serializer.js
  11927. /**
  11928. * Serializer.js
  11929. *
  11930. * Released under LGPL License.
  11931. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  11932. *
  11933. * License: http://www.tinymce.com/license
  11934. * Contributing: http://www.tinymce.com/contributing
  11935. */
  11936. /**
  11937. * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for
  11938. * more details and examples on how to use this class.
  11939. *
  11940. * @class tinymce.dom.Serializer
  11941. */
  11942. define("tinymce/dom/Serializer", [
  11943. "tinymce/dom/DOMUtils",
  11944. "tinymce/html/DomParser",
  11945. "tinymce/html/SaxParser",
  11946. "tinymce/html/Entities",
  11947. "tinymce/html/Serializer",
  11948. "tinymce/html/Node",
  11949. "tinymce/html/Schema",
  11950. "tinymce/Env",
  11951. "tinymce/util/Tools",
  11952. "tinymce/text/Zwsp"
  11953. ], function(DOMUtils, DomParser, SaxParser, Entities, Serializer, Node, Schema, Env, Tools, Zwsp) {
  11954. var each = Tools.each, trim = Tools.trim;
  11955. var DOM = DOMUtils.DOM, tempAttrs = ["data-mce-selected"];
  11956. /**
  11957. * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
  11958. * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync
  11959. * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML
  11960. * but not as the lastChild of the body. So this fix simply removes the last two
  11961. * BR elements at the end of the document.
  11962. *
  11963. * Example of what happens: <body>text</body> becomes <body>text<br><br></body>
  11964. */
  11965. function trimTrailingBr(rootNode) {
  11966. var brNode1, brNode2;
  11967. function isBr(node) {
  11968. return node && node.name === 'br';
  11969. }
  11970. brNode1 = rootNode.lastChild;
  11971. if (isBr(brNode1)) {
  11972. brNode2 = brNode1.prev;
  11973. if (isBr(brNode2)) {
  11974. brNode1.remove();
  11975. brNode2.remove();
  11976. }
  11977. }
  11978. }
  11979. /**
  11980. * Constructs a new DOM serializer class.
  11981. *
  11982. * @constructor
  11983. * @method Serializer
  11984. * @param {Object} settings Serializer settings object.
  11985. * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from.
  11986. */
  11987. return function(settings, editor) {
  11988. var dom, schema, htmlParser;
  11989. if (editor) {
  11990. dom = editor.dom;
  11991. schema = editor.schema;
  11992. }
  11993. function trimHtml(html) {
  11994. var trimContentRegExp = new RegExp([
  11995. '<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers
  11996. '\\s?(' + tempAttrs.join('|') + ')="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
  11997. ].join('|'), 'gi');
  11998. html = Zwsp.trim(html.replace(trimContentRegExp, ''));
  11999. return html;
  12000. }
  12001. /**
  12002. * Returns a trimmed version of the editor contents to be used for the undo level. This
  12003. * will remove any data-mce-bogus="all" marked elements since these are used for UI it will also
  12004. * remove the data-mce-selected attributes used for selection of objects and caret containers.
  12005. * It will keep all data-mce-bogus="1" elements since these can be used to place the caret etc and will
  12006. * be removed by the serialization logic when you save.
  12007. *
  12008. * @private
  12009. * @return {String} HTML contents of the editor excluding some internal bogus elements.
  12010. */
  12011. function getTrimmedContent() {
  12012. var content = editor.getBody().innerHTML;
  12013. var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g;
  12014. var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema;
  12015. content = trimHtml(content);
  12016. shortEndedElements = schema.getShortEndedElements();
  12017. // Remove all bogus elements marked with "all"
  12018. while ((matches = bogusAllRegExp.exec(content))) {
  12019. index = bogusAllRegExp.lastIndex;
  12020. matchLength = matches[0].length;
  12021. if (shortEndedElements[matches[1]]) {
  12022. endTagIndex = index;
  12023. } else {
  12024. endTagIndex = SaxParser.findEndTag(schema, content, index);
  12025. }
  12026. content = content.substring(0, index - matchLength) + content.substring(endTagIndex);
  12027. bogusAllRegExp.lastIndex = index - matchLength;
  12028. }
  12029. return trim(content);
  12030. }
  12031. function addTempAttr(name) {
  12032. if (Tools.inArray(tempAttrs, name) === -1) {
  12033. htmlParser.addAttributeFilter(name, function(nodes, name) {
  12034. var i = nodes.length;
  12035. while (i--) {
  12036. nodes[i].attr(name, null);
  12037. }
  12038. });
  12039. tempAttrs.push(name);
  12040. }
  12041. }
  12042. // Default DOM and Schema if they are undefined
  12043. dom = dom || DOM;
  12044. schema = schema || new Schema(settings);
  12045. settings.entity_encoding = settings.entity_encoding || 'named';
  12046. settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
  12047. htmlParser = new DomParser(settings, schema);
  12048. // Convert tabindex back to elements when serializing contents
  12049. htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) {
  12050. var i = nodes.length, node;
  12051. while (i--) {
  12052. node = nodes[i];
  12053. node.attr('tabindex', node.attributes.map['data-mce-tabindex']);
  12054. node.attr(name, null);
  12055. }
  12056. });
  12057. // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
  12058. htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
  12059. var i = nodes.length, node, value, internalName = 'data-mce-' + name;
  12060. var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
  12061. while (i--) {
  12062. node = nodes[i];
  12063. value = node.attributes.map[internalName];
  12064. if (value !== undef) {
  12065. // Set external name to internal value and remove internal
  12066. node.attr(name, value.length > 0 ? value : null);
  12067. node.attr(internalName, null);
  12068. } else {
  12069. // No internal attribute found then convert the value we have in the DOM
  12070. value = node.attributes.map[name];
  12071. if (name === "style") {
  12072. value = dom.serializeStyle(dom.parseStyle(value), node.name);
  12073. } else if (urlConverter) {
  12074. value = urlConverter.call(urlConverterScope, value, name, node.name);
  12075. }
  12076. node.attr(name, value.length > 0 ? value : null);
  12077. }
  12078. }
  12079. });
  12080. // Remove internal classes mceItem<..> or mceSelected
  12081. htmlParser.addAttributeFilter('class', function(nodes) {
  12082. var i = nodes.length, node, value;
  12083. while (i--) {
  12084. node = nodes[i];
  12085. value = node.attr('class');
  12086. if (value) {
  12087. value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
  12088. node.attr('class', value.length > 0 ? value : null);
  12089. }
  12090. }
  12091. });
  12092. // Remove bookmark elements
  12093. htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
  12094. var i = nodes.length, node;
  12095. while (i--) {
  12096. node = nodes[i];
  12097. if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) {
  12098. node.remove();
  12099. }
  12100. }
  12101. });
  12102. htmlParser.addNodeFilter('noscript', function(nodes) {
  12103. var i = nodes.length, node;
  12104. while (i--) {
  12105. node = nodes[i].firstChild;
  12106. if (node) {
  12107. node.value = Entities.decode(node.value);
  12108. }
  12109. }
  12110. });
  12111. // Force script into CDATA sections and remove the mce- prefix also add comments around styles
  12112. htmlParser.addNodeFilter('script,style', function(nodes, name) {
  12113. var i = nodes.length, node, value, type;
  12114. function trim(value) {
  12115. /*jshint maxlen:255 */
  12116. /*eslint max-len:0 */
  12117. return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
  12118. .replace(/^[\r\n]*|[\r\n]*$/g, '')
  12119. .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
  12120. .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
  12121. }
  12122. while (i--) {
  12123. node = nodes[i];
  12124. value = node.firstChild ? node.firstChild.value : '';
  12125. if (name === "script") {
  12126. // Remove mce- prefix from script elements and remove default type since the user specified
  12127. // a script element without type attribute
  12128. type = node.attr('type');
  12129. if (type) {
  12130. node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
  12131. }
  12132. if (value.length > 0) {
  12133. node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
  12134. }
  12135. } else {
  12136. if (value.length > 0) {
  12137. node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
  12138. }
  12139. }
  12140. }
  12141. });
  12142. // Convert comments to cdata and handle protected comments
  12143. htmlParser.addNodeFilter('#comment', function(nodes) {
  12144. var i = nodes.length, node;
  12145. while (i--) {
  12146. node = nodes[i];
  12147. if (node.value.indexOf('[CDATA[') === 0) {
  12148. node.name = '#cdata';
  12149. node.type = 4;
  12150. node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
  12151. } else if (node.value.indexOf('mce:protected ') === 0) {
  12152. node.name = "#text";
  12153. node.type = 3;
  12154. node.raw = true;
  12155. node.value = unescape(node.value).substr(14);
  12156. }
  12157. }
  12158. });
  12159. htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
  12160. var i = nodes.length, node;
  12161. while (i--) {
  12162. node = nodes[i];
  12163. if (node.type === 7) {
  12164. node.remove();
  12165. } else if (node.type === 1) {
  12166. if (name === "input" && !("type" in node.attributes.map)) {
  12167. node.attr('type', 'text');
  12168. }
  12169. }
  12170. }
  12171. });
  12172. // Fix list elements, TODO: Replace this later
  12173. if (settings.fix_list_elements) {
  12174. htmlParser.addNodeFilter('ul,ol', function(nodes) {
  12175. var i = nodes.length, node, parentNode;
  12176. while (i--) {
  12177. node = nodes[i];
  12178. parentNode = node.parent;
  12179. if (parentNode.name === 'ul' || parentNode.name === 'ol') {
  12180. if (node.prev && node.prev.name === 'li') {
  12181. node.prev.append(node);
  12182. }
  12183. }
  12184. }
  12185. });
  12186. }
  12187. // Remove internal data attributes
  12188. htmlParser.addAttributeFilter(
  12189. 'data-mce-src,data-mce-href,data-mce-style,' +
  12190. 'data-mce-selected,data-mce-expando,' +
  12191. 'data-mce-type,data-mce-resize',
  12192. function(nodes, name) {
  12193. var i = nodes.length;
  12194. while (i--) {
  12195. nodes[i].attr(name, null);
  12196. }
  12197. }
  12198. );
  12199. // Return public methods
  12200. return {
  12201. /**
  12202. * Schema instance that was used to when the Serializer was constructed.
  12203. *
  12204. * @field {tinymce.html.Schema} schema
  12205. */
  12206. schema: schema,
  12207. /**
  12208. * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
  12209. * and then execute the callback ones it has finished parsing the document.
  12210. *
  12211. * @example
  12212. * parser.addNodeFilter('p,h1', function(nodes, name) {
  12213. * for (var i = 0; i < nodes.length; i++) {
  12214. * console.log(nodes[i].name);
  12215. * }
  12216. * });
  12217. * @method addNodeFilter
  12218. * @method {String} name Comma separated list of nodes to collect.
  12219. * @param {function} callback Callback function to execute once it has collected nodes.
  12220. */
  12221. addNodeFilter: htmlParser.addNodeFilter,
  12222. /**
  12223. * Adds a attribute filter function to the parser used by the serializer, the parser will
  12224. * collect nodes that has the specified attributes
  12225. * and then execute the callback ones it has finished parsing the document.
  12226. *
  12227. * @example
  12228. * parser.addAttributeFilter('src,href', function(nodes, name) {
  12229. * for (var i = 0; i < nodes.length; i++) {
  12230. * console.log(nodes[i].name);
  12231. * }
  12232. * });
  12233. * @method addAttributeFilter
  12234. * @method {String} name Comma separated list of nodes to collect.
  12235. * @param {function} callback Callback function to execute once it has collected nodes.
  12236. */
  12237. addAttributeFilter: htmlParser.addAttributeFilter,
  12238. /**
  12239. * Serializes the specified browser DOM node into a HTML string.
  12240. *
  12241. * @method serialize
  12242. * @param {DOMNode} node DOM node to serialize.
  12243. * @param {Object} args Arguments option that gets passed to event handlers.
  12244. */
  12245. serialize: function(node, args) {
  12246. var self = this, impl, doc, oldDoc, htmlSerializer, content, rootNode;
  12247. // Explorer won't clone contents of script and style and the
  12248. // selected index of select elements are cleared on a clone operation.
  12249. if (Env.ie && dom.select('script,style,select,map').length > 0) {
  12250. content = node.innerHTML;
  12251. node = node.cloneNode(false);
  12252. dom.setHTML(node, content);
  12253. } else {
  12254. node = node.cloneNode(true);
  12255. }
  12256. // Nodes needs to be attached to something in WebKit/Opera
  12257. // This fix will make DOM ranges and make Sizzle happy!
  12258. impl = node.ownerDocument.implementation;
  12259. if (impl.createHTMLDocument) {
  12260. // Create an empty HTML document
  12261. doc = impl.createHTMLDocument("");
  12262. // Add the element or it's children if it's a body element to the new document
  12263. each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
  12264. doc.body.appendChild(doc.importNode(node, true));
  12265. });
  12266. // Grab first child or body element for serialization
  12267. if (node.nodeName != 'BODY') {
  12268. node = doc.body.firstChild;
  12269. } else {
  12270. node = doc.body;
  12271. }
  12272. // set the new document in DOMUtils so createElement etc works
  12273. oldDoc = dom.doc;
  12274. dom.doc = doc;
  12275. }
  12276. args = args || {};
  12277. args.format = args.format || 'html';
  12278. // Don't wrap content if we want selected html
  12279. if (args.selection) {
  12280. args.forced_root_block = '';
  12281. }
  12282. // Pre process
  12283. if (!args.no_events) {
  12284. args.node = node;
  12285. self.onPreProcess(args);
  12286. }
  12287. // Parse HTML
  12288. rootNode = htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args);
  12289. trimTrailingBr(rootNode);
  12290. // Serialize HTML
  12291. htmlSerializer = new Serializer(settings, schema);
  12292. args.content = htmlSerializer.serialize(rootNode);
  12293. // Replace all BOM characters for now until we can find a better solution
  12294. if (!args.cleanup) {
  12295. args.content = Zwsp.trim(args.content);
  12296. args.content = args.content.replace(/\uFEFF/g, '');
  12297. }
  12298. // Post process
  12299. if (!args.no_events) {
  12300. self.onPostProcess(args);
  12301. }
  12302. // Restore the old document if it was changed
  12303. if (oldDoc) {
  12304. dom.doc = oldDoc;
  12305. }
  12306. args.node = null;
  12307. return args.content;
  12308. },
  12309. /**
  12310. * Adds valid elements rules to the serializers schema instance this enables you to specify things
  12311. * like what elements should be outputted and what attributes specific elements might have.
  12312. * Consult the Wiki for more details on this format.
  12313. *
  12314. * @method addRules
  12315. * @param {String} rules Valid elements rules string to add to schema.
  12316. */
  12317. addRules: function(rules) {
  12318. schema.addValidElements(rules);
  12319. },
  12320. /**
  12321. * Sets the valid elements rules to the serializers schema instance this enables you to specify things
  12322. * like what elements should be outputted and what attributes specific elements might have.
  12323. * Consult the Wiki for more details on this format.
  12324. *
  12325. * @method setRules
  12326. * @param {String} rules Valid elements rules string.
  12327. */
  12328. setRules: function(rules) {
  12329. schema.setValidElements(rules);
  12330. },
  12331. onPreProcess: function(args) {
  12332. if (editor) {
  12333. editor.fire('PreProcess', args);
  12334. }
  12335. },
  12336. onPostProcess: function(args) {
  12337. if (editor) {
  12338. editor.fire('PostProcess', args);
  12339. }
  12340. },
  12341. /**
  12342. * Adds a temporary internal attribute these attributes will get removed on undo and
  12343. * when getting contents out of the editor.
  12344. *
  12345. * @method addTempAttr
  12346. * @param {String} name string
  12347. */
  12348. addTempAttr: addTempAttr,
  12349. // Internal
  12350. trimHtml: trimHtml,
  12351. getTrimmedContent: getTrimmedContent
  12352. };
  12353. };
  12354. });
  12355. // Included from: js/tinymce/classes/dom/TridentSelection.js
  12356. /**
  12357. * TridentSelection.js
  12358. *
  12359. * Released under LGPL License.
  12360. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  12361. *
  12362. * License: http://www.tinymce.com/license
  12363. * Contributing: http://www.tinymce.com/contributing
  12364. */
  12365. /**
  12366. * Selection class for old explorer versions. This one fakes the
  12367. * native selection object available on modern browsers.
  12368. *
  12369. * @private
  12370. * @class tinymce.dom.TridentSelection
  12371. */
  12372. define("tinymce/dom/TridentSelection", [], function() {
  12373. function Selection(selection) {
  12374. var self = this, dom = selection.dom, FALSE = false;
  12375. function getPosition(rng, start) {
  12376. var checkRng, startIndex = 0, endIndex, inside,
  12377. children, child, offset, index, position = -1, parent;
  12378. // Setup test range, collapse it and get the parent
  12379. checkRng = rng.duplicate();
  12380. checkRng.collapse(start);
  12381. parent = checkRng.parentElement();
  12382. // Check if the selection is within the right document
  12383. if (parent.ownerDocument !== selection.dom.doc) {
  12384. return;
  12385. }
  12386. // IE will report non editable elements as it's parent so look for an editable one
  12387. while (parent.contentEditable === "false") {
  12388. parent = parent.parentNode;
  12389. }
  12390. // If parent doesn't have any children then return that we are inside the element
  12391. if (!parent.hasChildNodes()) {
  12392. return {node: parent, inside: 1};
  12393. }
  12394. // Setup node list and endIndex
  12395. children = parent.children;
  12396. endIndex = children.length - 1;
  12397. // Perform a binary search for the position
  12398. while (startIndex <= endIndex) {
  12399. index = Math.floor((startIndex + endIndex) / 2);
  12400. // Move selection to node and compare the ranges
  12401. child = children[index];
  12402. checkRng.moveToElementText(child);
  12403. position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
  12404. // Before/after or an exact match
  12405. if (position > 0) {
  12406. endIndex = index - 1;
  12407. } else if (position < 0) {
  12408. startIndex = index + 1;
  12409. } else {
  12410. return {node: child};
  12411. }
  12412. }
  12413. // Check if child position is before or we didn't find a position
  12414. if (position < 0) {
  12415. // No element child was found use the parent element and the offset inside that
  12416. if (!child) {
  12417. checkRng.moveToElementText(parent);
  12418. checkRng.collapse(true);
  12419. child = parent;
  12420. inside = true;
  12421. } else {
  12422. checkRng.collapse(false);
  12423. }
  12424. // Walk character by character in text node until we hit the selected range endpoint,
  12425. // hit the end of document or parent isn't the right one
  12426. // We need to walk char by char since rng.text or rng.htmlText will trim line endings
  12427. offset = 0;
  12428. while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
  12429. if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
  12430. break;
  12431. }
  12432. offset++;
  12433. }
  12434. } else {
  12435. // Child position is after the selection endpoint
  12436. checkRng.collapse(true);
  12437. // Walk character by character in text node until we hit the selected range endpoint, hit
  12438. // the end of document or parent isn't the right one
  12439. offset = 0;
  12440. while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
  12441. if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
  12442. break;
  12443. }
  12444. offset++;
  12445. }
  12446. }
  12447. return {node: child, position: position, offset: offset, inside: inside};
  12448. }
  12449. // Returns a W3C DOM compatible range object by using the IE Range API
  12450. function getRange() {
  12451. var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
  12452. // If selection is outside the current document just return an empty range
  12453. element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
  12454. if (element.ownerDocument != dom.doc) {
  12455. return domRange;
  12456. }
  12457. collapsed = selection.isCollapsed();
  12458. // Handle control selection
  12459. if (ieRange.item) {
  12460. domRange.setStart(element.parentNode, dom.nodeIndex(element));
  12461. domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
  12462. return domRange;
  12463. }
  12464. function findEndPoint(start) {
  12465. var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
  12466. container = endPoint.node;
  12467. offset = endPoint.offset;
  12468. if (endPoint.inside && !container.hasChildNodes()) {
  12469. domRange[start ? 'setStart' : 'setEnd'](container, 0);
  12470. return;
  12471. }
  12472. if (offset === undef) {
  12473. domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
  12474. return;
  12475. }
  12476. if (endPoint.position < 0) {
  12477. sibling = endPoint.inside ? container.firstChild : container.nextSibling;
  12478. if (!sibling) {
  12479. domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
  12480. return;
  12481. }
  12482. if (!offset) {
  12483. if (sibling.nodeType == 3) {
  12484. domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
  12485. } else {
  12486. domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
  12487. }
  12488. return;
  12489. }
  12490. // Find the text node and offset
  12491. while (sibling) {
  12492. if (sibling.nodeType == 3) {
  12493. nodeValue = sibling.nodeValue;
  12494. textNodeOffset += nodeValue.length;
  12495. // We are at or passed the position we where looking for
  12496. if (textNodeOffset >= offset) {
  12497. container = sibling;
  12498. textNodeOffset -= offset;
  12499. textNodeOffset = nodeValue.length - textNodeOffset;
  12500. break;
  12501. }
  12502. }
  12503. sibling = sibling.nextSibling;
  12504. }
  12505. } else {
  12506. // Find the text node and offset
  12507. sibling = container.previousSibling;
  12508. if (!sibling) {
  12509. return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
  12510. }
  12511. // If there isn't any text to loop then use the first position
  12512. if (!offset) {
  12513. if (container.nodeType == 3) {
  12514. domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
  12515. } else {
  12516. domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
  12517. }
  12518. return;
  12519. }
  12520. while (sibling) {
  12521. if (sibling.nodeType == 3) {
  12522. textNodeOffset += sibling.nodeValue.length;
  12523. // We are at or passed the position we where looking for
  12524. if (textNodeOffset >= offset) {
  12525. container = sibling;
  12526. textNodeOffset -= offset;
  12527. break;
  12528. }
  12529. }
  12530. sibling = sibling.previousSibling;
  12531. }
  12532. }
  12533. domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
  12534. }
  12535. try {
  12536. // Find start point
  12537. findEndPoint(true);
  12538. // Find end point if needed
  12539. if (!collapsed) {
  12540. findEndPoint();
  12541. }
  12542. } catch (ex) {
  12543. // IE has a nasty bug where text nodes might throw "invalid argument" when you
  12544. // access the nodeValue or other properties of text nodes. This seems to happen when
  12545. // text nodes are split into two nodes by a delete/backspace call.
  12546. // So let us detect and try to fix it.
  12547. if (ex.number == -2147024809) {
  12548. // Get the current selection
  12549. bookmark = self.getBookmark(2);
  12550. // Get start element
  12551. tmpRange = ieRange.duplicate();
  12552. tmpRange.collapse(true);
  12553. element = tmpRange.parentElement();
  12554. // Get end element
  12555. if (!collapsed) {
  12556. tmpRange = ieRange.duplicate();
  12557. tmpRange.collapse(false);
  12558. element2 = tmpRange.parentElement();
  12559. element2.innerHTML = element2.innerHTML;
  12560. }
  12561. // Remove the broken elements
  12562. element.innerHTML = element.innerHTML;
  12563. // Restore the selection
  12564. self.moveToBookmark(bookmark);
  12565. // Since the range has moved we need to re-get it
  12566. ieRange = selection.getRng();
  12567. // Find start point
  12568. findEndPoint(true);
  12569. // Find end point if needed
  12570. if (!collapsed) {
  12571. findEndPoint();
  12572. }
  12573. } else {
  12574. throw ex; // Throw other errors
  12575. }
  12576. }
  12577. return domRange;
  12578. }
  12579. this.getBookmark = function(type) {
  12580. var rng = selection.getRng(), bookmark = {};
  12581. function getIndexes(node) {
  12582. var parent, root, children, i, indexes = [];
  12583. parent = node.parentNode;
  12584. root = dom.getRoot().parentNode;
  12585. while (parent != root && parent.nodeType !== 9) {
  12586. children = parent.children;
  12587. i = children.length;
  12588. while (i--) {
  12589. if (node === children[i]) {
  12590. indexes.push(i);
  12591. break;
  12592. }
  12593. }
  12594. node = parent;
  12595. parent = parent.parentNode;
  12596. }
  12597. return indexes;
  12598. }
  12599. function getBookmarkEndPoint(start) {
  12600. var position;
  12601. position = getPosition(rng, start);
  12602. if (position) {
  12603. return {
  12604. position: position.position,
  12605. offset: position.offset,
  12606. indexes: getIndexes(position.node),
  12607. inside: position.inside
  12608. };
  12609. }
  12610. }
  12611. // Non ubstructive bookmark
  12612. if (type === 2) {
  12613. // Handle text selection
  12614. if (!rng.item) {
  12615. bookmark.start = getBookmarkEndPoint(true);
  12616. if (!selection.isCollapsed()) {
  12617. bookmark.end = getBookmarkEndPoint();
  12618. }
  12619. } else {
  12620. bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))};
  12621. }
  12622. }
  12623. return bookmark;
  12624. };
  12625. this.moveToBookmark = function(bookmark) {
  12626. var rng, body = dom.doc.body;
  12627. function resolveIndexes(indexes) {
  12628. var node, i, idx, children;
  12629. node = dom.getRoot();
  12630. for (i = indexes.length - 1; i >= 0; i--) {
  12631. children = node.children;
  12632. idx = indexes[i];
  12633. if (idx <= children.length - 1) {
  12634. node = children[idx];
  12635. }
  12636. }
  12637. return node;
  12638. }
  12639. function setBookmarkEndPoint(start) {
  12640. var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
  12641. if (endPoint) {
  12642. moveLeft = endPoint.position > 0;
  12643. moveRng = body.createTextRange();
  12644. moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
  12645. offset = endPoint.offset;
  12646. if (offset !== undef) {
  12647. moveRng.collapse(endPoint.inside || moveLeft);
  12648. moveRng.moveStart('character', moveLeft ? -offset : offset);
  12649. } else {
  12650. moveRng.collapse(start);
  12651. }
  12652. rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
  12653. if (start) {
  12654. rng.collapse(true);
  12655. }
  12656. }
  12657. }
  12658. if (bookmark.start) {
  12659. if (bookmark.start.ctrl) {
  12660. rng = body.createControlRange();
  12661. rng.addElement(resolveIndexes(bookmark.start.indexes));
  12662. rng.select();
  12663. } else {
  12664. rng = body.createTextRange();
  12665. setBookmarkEndPoint(true);
  12666. setBookmarkEndPoint();
  12667. rng.select();
  12668. }
  12669. }
  12670. };
  12671. this.addRange = function(rng) {
  12672. var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
  12673. doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
  12674. function setEndPoint(start) {
  12675. var container, offset, marker, tmpRng, nodes;
  12676. marker = dom.create('a');
  12677. container = start ? startContainer : endContainer;
  12678. offset = start ? startOffset : endOffset;
  12679. tmpRng = ieRng.duplicate();
  12680. if (container == doc || container == doc.documentElement) {
  12681. container = body;
  12682. offset = 0;
  12683. }
  12684. if (container.nodeType == 3) {
  12685. container.parentNode.insertBefore(marker, container);
  12686. tmpRng.moveToElementText(marker);
  12687. tmpRng.moveStart('character', offset);
  12688. dom.remove(marker);
  12689. ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
  12690. } else {
  12691. nodes = container.childNodes;
  12692. if (nodes.length) {
  12693. if (offset >= nodes.length) {
  12694. dom.insertAfter(marker, nodes[nodes.length - 1]);
  12695. } else {
  12696. container.insertBefore(marker, nodes[offset]);
  12697. }
  12698. tmpRng.moveToElementText(marker);
  12699. } else if (container.canHaveHTML) {
  12700. // Empty node selection for example <div>|</div>
  12701. // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
  12702. container.innerHTML = '<span>&#xFEFF;</span>';
  12703. marker = container.firstChild;
  12704. tmpRng.moveToElementText(marker);
  12705. tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
  12706. }
  12707. ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
  12708. dom.remove(marker);
  12709. }
  12710. }
  12711. // Setup some shorter versions
  12712. startContainer = rng.startContainer;
  12713. startOffset = rng.startOffset;
  12714. endContainer = rng.endContainer;
  12715. endOffset = rng.endOffset;
  12716. ieRng = body.createTextRange();
  12717. // If single element selection then try making a control selection out of it
  12718. if (startContainer == endContainer && startContainer.nodeType == 1) {
  12719. // Trick to place the caret inside an empty block element like <p></p>
  12720. if (startOffset == endOffset && !startContainer.hasChildNodes()) {
  12721. if (startContainer.canHaveHTML) {
  12722. // Check if previous sibling is an empty block if it is then we need to render it
  12723. // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
  12724. // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
  12725. sibling = startContainer.previousSibling;
  12726. if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
  12727. sibling.innerHTML = '&#xFEFF;';
  12728. } else {
  12729. sibling = null;
  12730. }
  12731. startContainer.innerHTML = '<span>&#xFEFF;</span><span>&#xFEFF;</span>';
  12732. ieRng.moveToElementText(startContainer.lastChild);
  12733. ieRng.select();
  12734. dom.doc.selection.clear();
  12735. startContainer.innerHTML = '';
  12736. if (sibling) {
  12737. sibling.innerHTML = '';
  12738. }
  12739. return;
  12740. }
  12741. startOffset = dom.nodeIndex(startContainer);
  12742. startContainer = startContainer.parentNode;
  12743. }
  12744. if (startOffset == endOffset - 1) {
  12745. try {
  12746. ctrlElm = startContainer.childNodes[startOffset];
  12747. ctrlRng = body.createControlRange();
  12748. ctrlRng.addElement(ctrlElm);
  12749. ctrlRng.select();
  12750. // Check if the range produced is on the correct element and is a control range
  12751. // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
  12752. nativeRng = selection.getRng();
  12753. if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
  12754. return;
  12755. }
  12756. } catch (ex) {
  12757. // Ignore
  12758. }
  12759. }
  12760. }
  12761. // Set start/end point of selection
  12762. setEndPoint(true);
  12763. setEndPoint();
  12764. // Select the new range and scroll it into view
  12765. ieRng.select();
  12766. };
  12767. // Expose range method
  12768. this.getRangeAt = getRange;
  12769. }
  12770. return Selection;
  12771. });
  12772. // Included from: js/tinymce/classes/util/VK.js
  12773. /**
  12774. * VK.js
  12775. *
  12776. * Released under LGPL License.
  12777. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  12778. *
  12779. * License: http://www.tinymce.com/license
  12780. * Contributing: http://www.tinymce.com/contributing
  12781. */
  12782. /**
  12783. * This file exposes a set of the common KeyCodes for use. Please grow it as needed.
  12784. */
  12785. define("tinymce/util/VK", [
  12786. "tinymce/Env"
  12787. ], function(Env) {
  12788. return {
  12789. BACKSPACE: 8,
  12790. DELETE: 46,
  12791. DOWN: 40,
  12792. ENTER: 13,
  12793. LEFT: 37,
  12794. RIGHT: 39,
  12795. SPACEBAR: 32,
  12796. TAB: 9,
  12797. UP: 38,
  12798. modifierPressed: function(e) {
  12799. return e.shiftKey || e.ctrlKey || e.altKey || this.metaKeyPressed(e);
  12800. },
  12801. metaKeyPressed: function(e) {
  12802. // Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states
  12803. return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey);
  12804. }
  12805. };
  12806. });
  12807. // Included from: js/tinymce/classes/dom/ControlSelection.js
  12808. /**
  12809. * ControlSelection.js
  12810. *
  12811. * Released under LGPL License.
  12812. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  12813. *
  12814. * License: http://www.tinymce.com/license
  12815. * Contributing: http://www.tinymce.com/contributing
  12816. */
  12817. /**
  12818. * This class handles control selection of elements. Controls are elements
  12819. * that can be resized and needs to be selected as a whole. It adds custom resize handles
  12820. * to all browser engines that support properly disabling the built in resize logic.
  12821. *
  12822. * @class tinymce.dom.ControlSelection
  12823. */
  12824. define("tinymce/dom/ControlSelection", [
  12825. "tinymce/util/VK",
  12826. "tinymce/util/Tools",
  12827. "tinymce/util/Delay",
  12828. "tinymce/Env",
  12829. "tinymce/dom/NodeType"
  12830. ], function(VK, Tools, Delay, Env, NodeType) {
  12831. var isContentEditableFalse = NodeType.isContentEditableFalse;
  12832. var isContentEditableTrue = NodeType.isContentEditableTrue;
  12833. function getContentEditableRoot(root, node) {
  12834. while (node && node != root) {
  12835. if (isContentEditableTrue(node) || isContentEditableFalse(node)) {
  12836. return node;
  12837. }
  12838. node = node.parentNode;
  12839. }
  12840. return null;
  12841. }
  12842. return function(selection, editor) {
  12843. var dom = editor.dom, each = Tools.each;
  12844. var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent;
  12845. var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
  12846. var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
  12847. var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight;
  12848. // Details about each resize handle how to scale etc
  12849. resizeHandles = {
  12850. // Name: x multiplier, y multiplier, delta size x, delta size y
  12851. /*n: [0.5, 0, 0, -1],
  12852. e: [1, 0.5, 1, 0],
  12853. s: [0.5, 1, 0, 1],
  12854. w: [0, 0.5, -1, 0],*/
  12855. nw: [0, 0, -1, -1],
  12856. ne: [1, 0, 1, -1],
  12857. se: [1, 1, 1, 1],
  12858. sw: [0, 1, -1, 1]
  12859. };
  12860. // Add CSS for resize handles, cloned element and selected
  12861. var rootClass = '.mce-content-body';
  12862. editor.contentStyles.push(
  12863. rootClass + ' div.mce-resizehandle {' +
  12864. 'position: absolute;' +
  12865. 'border: 1px solid black;' +
  12866. 'box-sizing: box-sizing;' +
  12867. 'background: #FFF;' +
  12868. 'width: 7px;' +
  12869. 'height: 7px;' +
  12870. 'z-index: 10000' +
  12871. '}' +
  12872. rootClass + ' .mce-resizehandle:hover {' +
  12873. 'background: #000' +
  12874. '}' +
  12875. rootClass + ' img[data-mce-selected],' + rootClass + ' hr[data-mce-selected] {' +
  12876. 'outline: 1px solid black;' +
  12877. 'resize: none' + // Have been talks about implementing this in browsers
  12878. '}' +
  12879. rootClass + ' .mce-clonedresizable {' +
  12880. 'position: absolute;' +
  12881. (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
  12882. 'opacity: .5;' +
  12883. 'filter: alpha(opacity=50);' +
  12884. 'z-index: 10000' +
  12885. '}' +
  12886. rootClass + ' .mce-resize-helper {' +
  12887. 'background: #555;' +
  12888. 'background: rgba(0,0,0,0.75);' +
  12889. 'border-radius: 3px;' +
  12890. 'border: 1px;' +
  12891. 'color: white;' +
  12892. 'display: none;' +
  12893. 'font-family: sans-serif;' +
  12894. 'font-size: 12px;' +
  12895. 'white-space: nowrap;' +
  12896. 'line-height: 14px;' +
  12897. 'margin: 5px 10px;' +
  12898. 'padding: 5px;' +
  12899. 'position: absolute;' +
  12900. 'z-index: 10001' +
  12901. '}'
  12902. );
  12903. function isResizable(elm) {
  12904. var selector = editor.settings.object_resizing;
  12905. if (selector === false || Env.iOS) {
  12906. return false;
  12907. }
  12908. if (typeof selector != 'string') {
  12909. selector = 'table,img,div';
  12910. }
  12911. if (elm.getAttribute('data-mce-resize') === 'false') {
  12912. return false;
  12913. }
  12914. if (elm == editor.getBody()) {
  12915. return false;
  12916. }
  12917. return editor.dom.is(elm, selector);
  12918. }
  12919. function resizeGhostElement(e) {
  12920. var deltaX, deltaY, proportional;
  12921. var resizeHelperX, resizeHelperY;
  12922. // Calc new width/height
  12923. deltaX = e.screenX - startX;
  12924. deltaY = e.screenY - startY;
  12925. // Calc new size
  12926. width = deltaX * selectedHandle[2] + startW;
  12927. height = deltaY * selectedHandle[3] + startH;
  12928. // Never scale down lower than 5 pixels
  12929. width = width < 5 ? 5 : width;
  12930. height = height < 5 ? 5 : height;
  12931. if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) {
  12932. proportional = !VK.modifierPressed(e);
  12933. } else {
  12934. proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0);
  12935. }
  12936. // Constrain proportions
  12937. if (proportional) {
  12938. if (abs(deltaX) > abs(deltaY)) {
  12939. height = round(width * ratio);
  12940. width = round(height / ratio);
  12941. } else {
  12942. width = round(height / ratio);
  12943. height = round(width * ratio);
  12944. }
  12945. }
  12946. // Update ghost size
  12947. dom.setStyles(selectedElmGhost, {
  12948. width: width,
  12949. height: height
  12950. });
  12951. // Update resize helper position
  12952. resizeHelperX = selectedHandle.startPos.x + deltaX;
  12953. resizeHelperY = selectedHandle.startPos.y + deltaY;
  12954. resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0;
  12955. resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0;
  12956. dom.setStyles(resizeHelper, {
  12957. left: resizeHelperX,
  12958. top: resizeHelperY,
  12959. display: 'block'
  12960. });
  12961. resizeHelper.innerHTML = width + ' &times; ' + height;
  12962. // Update ghost X position if needed
  12963. if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
  12964. dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
  12965. }
  12966. // Update ghost Y position if needed
  12967. if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
  12968. dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
  12969. }
  12970. // Calculate how must overflow we got
  12971. deltaX = rootElement.scrollWidth - startScrollWidth;
  12972. deltaY = rootElement.scrollHeight - startScrollHeight;
  12973. // Re-position the resize helper based on the overflow
  12974. if (deltaX + deltaY !== 0) {
  12975. dom.setStyles(resizeHelper, {
  12976. left: resizeHelperX - deltaX,
  12977. top: resizeHelperY - deltaY
  12978. });
  12979. }
  12980. if (!resizeStarted) {
  12981. editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
  12982. resizeStarted = true;
  12983. }
  12984. }
  12985. function endGhostResize() {
  12986. resizeStarted = false;
  12987. function setSizeProp(name, value) {
  12988. if (value) {
  12989. // Resize by using style or attribute
  12990. if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
  12991. dom.setStyle(selectedElm, name, value);
  12992. } else {
  12993. dom.setAttrib(selectedElm, name, value);
  12994. }
  12995. }
  12996. }
  12997. // Set width/height properties
  12998. setSizeProp('width', width);
  12999. setSizeProp('height', height);
  13000. dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
  13001. dom.unbind(editableDoc, 'mouseup', endGhostResize);
  13002. if (rootDocument != editableDoc) {
  13003. dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
  13004. dom.unbind(rootDocument, 'mouseup', endGhostResize);
  13005. }
  13006. // Remove ghost/helper and update resize handle positions
  13007. dom.remove(selectedElmGhost);
  13008. dom.remove(resizeHelper);
  13009. if (!isIE || selectedElm.nodeName == "TABLE") {
  13010. showResizeRect(selectedElm);
  13011. }
  13012. editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
  13013. dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style'));
  13014. editor.nodeChanged();
  13015. }
  13016. function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
  13017. var position, targetWidth, targetHeight, e, rect;
  13018. hideResizeRect();
  13019. unbindResizeHandleEvents();
  13020. // Get position and size of target
  13021. position = dom.getPos(targetElm, rootElement);
  13022. selectedElmX = position.x;
  13023. selectedElmY = position.y;
  13024. rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
  13025. targetWidth = rect.width || (rect.right - rect.left);
  13026. targetHeight = rect.height || (rect.bottom - rect.top);
  13027. // Reset width/height if user selects a new image/table
  13028. if (selectedElm != targetElm) {
  13029. detachResizeStartListener();
  13030. selectedElm = targetElm;
  13031. width = height = 0;
  13032. }
  13033. // Makes it possible to disable resizing
  13034. e = editor.fire('ObjectSelected', {target: targetElm});
  13035. if (isResizable(targetElm) && !e.isDefaultPrevented()) {
  13036. each(resizeHandles, function(handle, name) {
  13037. var handleElm;
  13038. function startDrag(e) {
  13039. startX = e.screenX;
  13040. startY = e.screenY;
  13041. startW = selectedElm.clientWidth;
  13042. startH = selectedElm.clientHeight;
  13043. ratio = startH / startW;
  13044. selectedHandle = handle;
  13045. handle.startPos = {
  13046. x: targetWidth * handle[0] + selectedElmX,
  13047. y: targetHeight * handle[1] + selectedElmY
  13048. };
  13049. startScrollWidth = rootElement.scrollWidth;
  13050. startScrollHeight = rootElement.scrollHeight;
  13051. selectedElmGhost = selectedElm.cloneNode(true);
  13052. dom.addClass(selectedElmGhost, 'mce-clonedresizable');
  13053. dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all');
  13054. selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
  13055. selectedElmGhost.unSelectabe = true;
  13056. dom.setStyles(selectedElmGhost, {
  13057. left: selectedElmX,
  13058. top: selectedElmY,
  13059. margin: 0
  13060. });
  13061. selectedElmGhost.removeAttribute('data-mce-selected');
  13062. rootElement.appendChild(selectedElmGhost);
  13063. dom.bind(editableDoc, 'mousemove', resizeGhostElement);
  13064. dom.bind(editableDoc, 'mouseup', endGhostResize);
  13065. if (rootDocument != editableDoc) {
  13066. dom.bind(rootDocument, 'mousemove', resizeGhostElement);
  13067. dom.bind(rootDocument, 'mouseup', endGhostResize);
  13068. }
  13069. resizeHelper = dom.add(rootElement, 'div', {
  13070. 'class': 'mce-resize-helper',
  13071. 'data-mce-bogus': 'all'
  13072. }, startW + ' &times; ' + startH);
  13073. }
  13074. if (mouseDownHandleName) {
  13075. // Drag started by IE native resizestart
  13076. if (name == mouseDownHandleName) {
  13077. startDrag(mouseDownEvent);
  13078. }
  13079. return;
  13080. }
  13081. // Get existing or render resize handle
  13082. handleElm = dom.get('mceResizeHandle' + name);
  13083. if (handleElm) {
  13084. dom.remove(handleElm);
  13085. }
  13086. handleElm = dom.add(rootElement, 'div', {
  13087. id: 'mceResizeHandle' + name,
  13088. 'data-mce-bogus': 'all',
  13089. 'class': 'mce-resizehandle',
  13090. unselectable: true,
  13091. style: 'cursor:' + name + '-resize; margin:0; padding:0'
  13092. });
  13093. // Hides IE move layer cursor
  13094. // If we set it on Chrome we get this wounderful bug: #6725
  13095. if (Env.ie) {
  13096. handleElm.contentEditable = false;
  13097. }
  13098. dom.bind(handleElm, 'mousedown', function(e) {
  13099. e.stopImmediatePropagation();
  13100. e.preventDefault();
  13101. startDrag(e);
  13102. });
  13103. handle.elm = handleElm;
  13104. // Position element
  13105. dom.setStyles(handleElm, {
  13106. left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
  13107. top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
  13108. });
  13109. });
  13110. } else {
  13111. hideResizeRect();
  13112. }
  13113. selectedElm.setAttribute('data-mce-selected', '1');
  13114. }
  13115. function hideResizeRect() {
  13116. var name, handleElm;
  13117. unbindResizeHandleEvents();
  13118. if (selectedElm) {
  13119. selectedElm.removeAttribute('data-mce-selected');
  13120. }
  13121. for (name in resizeHandles) {
  13122. handleElm = dom.get('mceResizeHandle' + name);
  13123. if (handleElm) {
  13124. dom.unbind(handleElm);
  13125. dom.remove(handleElm);
  13126. }
  13127. }
  13128. }
  13129. function updateResizeRect(e) {
  13130. var startElm, controlElm;
  13131. function isChildOrEqual(node, parent) {
  13132. if (node) {
  13133. do {
  13134. if (node === parent) {
  13135. return true;
  13136. }
  13137. } while ((node = node.parentNode));
  13138. }
  13139. }
  13140. // Ignore all events while resizing or if the editor instance was removed
  13141. if (resizeStarted || editor.removed) {
  13142. return;
  13143. }
  13144. // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
  13145. each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
  13146. img.removeAttribute('data-mce-selected');
  13147. });
  13148. controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
  13149. controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0];
  13150. if (isChildOrEqual(controlElm, rootElement)) {
  13151. disableGeckoResize();
  13152. startElm = selection.getStart(true);
  13153. if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) {
  13154. if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) {
  13155. showResizeRect(controlElm);
  13156. return;
  13157. }
  13158. }
  13159. }
  13160. hideResizeRect();
  13161. }
  13162. function attachEvent(elm, name, func) {
  13163. if (elm && elm.attachEvent) {
  13164. elm.attachEvent('on' + name, func);
  13165. }
  13166. }
  13167. function detachEvent(elm, name, func) {
  13168. if (elm && elm.detachEvent) {
  13169. elm.detachEvent('on' + name, func);
  13170. }
  13171. }
  13172. function resizeNativeStart(e) {
  13173. var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;
  13174. pos = target.getBoundingClientRect();
  13175. relativeX = lastMouseDownEvent.clientX - pos.left;
  13176. relativeY = lastMouseDownEvent.clientY - pos.top;
  13177. // Figure out what corner we are draging on
  13178. for (name in resizeHandles) {
  13179. corner = resizeHandles[name];
  13180. cornerX = target.offsetWidth * corner[0];
  13181. cornerY = target.offsetHeight * corner[1];
  13182. if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) {
  13183. selectedHandle = corner;
  13184. break;
  13185. }
  13186. }
  13187. // Remove native selection and let the magic begin
  13188. resizeStarted = true;
  13189. editor.fire('ObjectResizeStart', {
  13190. target: selectedElm,
  13191. width: selectedElm.clientWidth,
  13192. height: selectedElm.clientHeight
  13193. });
  13194. editor.getDoc().selection.empty();
  13195. showResizeRect(target, name, lastMouseDownEvent);
  13196. }
  13197. function preventDefault(e) {
  13198. if (e.preventDefault) {
  13199. e.preventDefault();
  13200. } else {
  13201. e.returnValue = false; // IE
  13202. }
  13203. }
  13204. function isWithinContentEditableFalse(elm) {
  13205. return isContentEditableFalse(getContentEditableRoot(editor.getBody(), elm));
  13206. }
  13207. function nativeControlSelect(e) {
  13208. var target = e.srcElement;
  13209. if (isWithinContentEditableFalse(target)) {
  13210. preventDefault(e);
  13211. return;
  13212. }
  13213. if (target != selectedElm) {
  13214. editor.fire('ObjectSelected', {target: target});
  13215. detachResizeStartListener();
  13216. if (target.id.indexOf('mceResizeHandle') === 0) {
  13217. e.returnValue = false;
  13218. return;
  13219. }
  13220. if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
  13221. hideResizeRect();
  13222. selectedElm = target;
  13223. attachEvent(target, 'resizestart', resizeNativeStart);
  13224. }
  13225. }
  13226. }
  13227. function detachResizeStartListener() {
  13228. detachEvent(selectedElm, 'resizestart', resizeNativeStart);
  13229. }
  13230. function unbindResizeHandleEvents() {
  13231. for (var name in resizeHandles) {
  13232. var handle = resizeHandles[name];
  13233. if (handle.elm) {
  13234. dom.unbind(handle.elm);
  13235. delete handle.elm;
  13236. }
  13237. }
  13238. }
  13239. function disableGeckoResize() {
  13240. try {
  13241. // Disable object resizing on Gecko
  13242. editor.getDoc().execCommand('enableObjectResizing', false, false);
  13243. } catch (ex) {
  13244. // Ignore
  13245. }
  13246. }
  13247. function controlSelect(elm) {
  13248. var ctrlRng;
  13249. if (!isIE) {
  13250. return;
  13251. }
  13252. ctrlRng = editableDoc.body.createControlRange();
  13253. try {
  13254. ctrlRng.addElement(elm);
  13255. ctrlRng.select();
  13256. return true;
  13257. } catch (ex) {
  13258. // Ignore since the element can't be control selected for example a P tag
  13259. }
  13260. }
  13261. editor.on('init', function() {
  13262. if (isIE) {
  13263. // Hide the resize rect on resize and reselect the image
  13264. editor.on('ObjectResized', function(e) {
  13265. if (e.target.nodeName != 'TABLE') {
  13266. hideResizeRect();
  13267. controlSelect(e.target);
  13268. }
  13269. });
  13270. attachEvent(rootElement, 'controlselect', nativeControlSelect);
  13271. editor.on('mousedown', function(e) {
  13272. lastMouseDownEvent = e;
  13273. });
  13274. } else {
  13275. disableGeckoResize();
  13276. // Sniff sniff, hard to feature detect this stuff
  13277. if (Env.ie >= 11) {
  13278. // Needs to be mousedown for drag/drop to work on IE 11
  13279. // Needs to be click on Edge to properly select images
  13280. editor.on('mousedown click', function(e) {
  13281. var target = e.target, nodeName = target.nodeName;
  13282. if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName) && !isWithinContentEditableFalse(target)) {
  13283. editor.selection.select(target, nodeName == 'TABLE');
  13284. // Only fire once since nodeChange is expensive
  13285. if (e.type == 'mousedown') {
  13286. editor.nodeChanged();
  13287. }
  13288. }
  13289. });
  13290. editor.dom.bind(rootElement, 'mscontrolselect', function(e) {
  13291. function delayedSelect(node) {
  13292. Delay.setEditorTimeout(editor, function() {
  13293. editor.selection.select(node);
  13294. });
  13295. }
  13296. if (isWithinContentEditableFalse(e.target)) {
  13297. e.preventDefault();
  13298. delayedSelect(e.target);
  13299. return;
  13300. }
  13301. if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
  13302. e.preventDefault();
  13303. // This moves the selection from being a control selection to a text like selection like in WebKit #6753
  13304. // TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
  13305. if (e.target.tagName == 'IMG') {
  13306. delayedSelect(e.target);
  13307. }
  13308. }
  13309. });
  13310. }
  13311. }
  13312. var throttledUpdateResizeRect = Delay.throttle(function(e) {
  13313. if (!editor.composing) {
  13314. updateResizeRect(e);
  13315. }
  13316. });
  13317. editor.on('nodechange ResizeEditor ResizeWindow drop', throttledUpdateResizeRect);
  13318. // Update resize rect while typing in a table
  13319. editor.on('keyup compositionend', function(e) {
  13320. // Don't update the resize rect while composing since it blows away the IME see: #2710
  13321. if (selectedElm && selectedElm.nodeName == "TABLE") {
  13322. throttledUpdateResizeRect(e);
  13323. }
  13324. });
  13325. editor.on('hide blur', hideResizeRect);
  13326. // Hide rect on focusout since it would float on top of windows otherwise
  13327. //editor.on('focusout', hideResizeRect);
  13328. });
  13329. editor.on('remove', unbindResizeHandleEvents);
  13330. function destroy() {
  13331. selectedElm = selectedElmGhost = null;
  13332. if (isIE) {
  13333. detachResizeStartListener();
  13334. detachEvent(rootElement, 'controlselect', nativeControlSelect);
  13335. }
  13336. }
  13337. return {
  13338. isResizable: isResizable,
  13339. showResizeRect: showResizeRect,
  13340. hideResizeRect: hideResizeRect,
  13341. updateResizeRect: updateResizeRect,
  13342. controlSelect: controlSelect,
  13343. destroy: destroy
  13344. };
  13345. };
  13346. });
  13347. // Included from: js/tinymce/classes/util/Fun.js
  13348. /**
  13349. * Fun.js
  13350. *
  13351. * Released under LGPL License.
  13352. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  13353. *
  13354. * License: http://www.tinymce.com/license
  13355. * Contributing: http://www.tinymce.com/contributing
  13356. */
  13357. /**
  13358. * Functional utility class.
  13359. *
  13360. * @private
  13361. * @class tinymce.util.Fun
  13362. */
  13363. define("tinymce/util/Fun", [], function() {
  13364. var slice = [].slice;
  13365. function constant(value) {
  13366. return function() {
  13367. return value;
  13368. };
  13369. }
  13370. function negate(predicate) {
  13371. return function(x) {
  13372. return !predicate(x);
  13373. };
  13374. }
  13375. function compose(f, g) {
  13376. return function(x) {
  13377. return f(g(x));
  13378. };
  13379. }
  13380. function or() {
  13381. var args = slice.call(arguments);
  13382. return function(x) {
  13383. for (var i = 0; i < args.length; i++) {
  13384. if (args[i](x)) {
  13385. return true;
  13386. }
  13387. }
  13388. return false;
  13389. };
  13390. }
  13391. function and() {
  13392. var args = slice.call(arguments);
  13393. return function(x) {
  13394. for (var i = 0; i < args.length; i++) {
  13395. if (!args[i](x)) {
  13396. return false;
  13397. }
  13398. }
  13399. return true;
  13400. };
  13401. }
  13402. function curry(fn) {
  13403. var args = slice.call(arguments);
  13404. if (args.length - 1 >= fn.length) {
  13405. return fn.apply(this, args.slice(1));
  13406. }
  13407. return function() {
  13408. var tempArgs = args.concat([].slice.call(arguments));
  13409. return curry.apply(this, tempArgs);
  13410. };
  13411. }
  13412. return {
  13413. constant: constant,
  13414. negate: negate,
  13415. and: and,
  13416. or: or,
  13417. curry: curry,
  13418. compose: compose
  13419. };
  13420. });
  13421. // Included from: js/tinymce/classes/caret/CaretCandidate.js
  13422. /**
  13423. * CaretCandidate.js
  13424. *
  13425. * Released under LGPL License.
  13426. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  13427. *
  13428. * License: http://www.tinymce.com/license
  13429. * Contributing: http://www.tinymce.com/contributing
  13430. */
  13431. /**
  13432. * This module contains logic for handling caret candidates. A caret candidate is
  13433. * for example text nodes, images, input elements, cE=false elements etc.
  13434. *
  13435. * @private
  13436. * @class tinymce.caret.CaretCandidate
  13437. */
  13438. define("tinymce/caret/CaretCandidate", [
  13439. "tinymce/dom/NodeType",
  13440. "tinymce/util/Arr",
  13441. "tinymce/caret/CaretContainer"
  13442. ], function(NodeType, Arr, CaretContainer) {
  13443. var isContentEditableTrue = NodeType.isContentEditableTrue,
  13444. isContentEditableFalse = NodeType.isContentEditableFalse,
  13445. isBr = NodeType.isBr,
  13446. isText = NodeType.isText,
  13447. isInvalidTextElement = NodeType.matchNodeNames('script style textarea'),
  13448. isAtomicInline = NodeType.matchNodeNames('img input textarea hr iframe video audio object'),
  13449. isTable = NodeType.matchNodeNames('table'),
  13450. isCaretContainer = CaretContainer.isCaretContainer;
  13451. function isCaretCandidate(node) {
  13452. if (isCaretContainer(node)) {
  13453. return false;
  13454. }
  13455. if (isText(node)) {
  13456. if (isInvalidTextElement(node.parentNode)) {
  13457. return false;
  13458. }
  13459. return true;
  13460. }
  13461. return isAtomicInline(node) || isBr(node) || isTable(node) || isContentEditableFalse(node);
  13462. }
  13463. function isInEditable(node, rootNode) {
  13464. for (node = node.parentNode; node && node != rootNode; node = node.parentNode) {
  13465. if (isContentEditableFalse(node)) {
  13466. return false;
  13467. }
  13468. if (isContentEditableTrue(node)) {
  13469. return true;
  13470. }
  13471. }
  13472. return true;
  13473. }
  13474. function isAtomicContentEditableFalse(node) {
  13475. if (!isContentEditableFalse(node)) {
  13476. return false;
  13477. }
  13478. return Arr.reduce(node.getElementsByTagName('*'), function(result, elm) {
  13479. return result || isContentEditableTrue(elm);
  13480. }, false) !== true;
  13481. }
  13482. function isAtomic(node) {
  13483. return isAtomicInline(node) || isAtomicContentEditableFalse(node);
  13484. }
  13485. function isEditableCaretCandidate(node, rootNode) {
  13486. return isCaretCandidate(node) && isInEditable(node, rootNode);
  13487. }
  13488. return {
  13489. isCaretCandidate: isCaretCandidate,
  13490. isInEditable: isInEditable,
  13491. isAtomic: isAtomic,
  13492. isEditableCaretCandidate: isEditableCaretCandidate
  13493. };
  13494. });
  13495. // Included from: js/tinymce/classes/geom/ClientRect.js
  13496. /**
  13497. * ClientRect.js
  13498. *
  13499. * Released under LGPL License.
  13500. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  13501. *
  13502. * License: http://www.tinymce.com/license
  13503. * Contributing: http://www.tinymce.com/contributing
  13504. */
  13505. /**
  13506. * Utility functions for working with client rects.
  13507. *
  13508. * @private
  13509. * @class tinymce.geom.ClientRect
  13510. */
  13511. define("tinymce/geom/ClientRect", [], function() {
  13512. var round = Math.round;
  13513. function clone(rect) {
  13514. if (!rect) {
  13515. return {left: 0, top: 0, bottom: 0, right: 0, width: 0, height: 0};
  13516. }
  13517. return {
  13518. left: round(rect.left),
  13519. top: round(rect.top),
  13520. bottom: round(rect.bottom),
  13521. right: round(rect.right),
  13522. width: round(rect.width),
  13523. height: round(rect.height)
  13524. };
  13525. }
  13526. function collapse(clientRect, toStart) {
  13527. clientRect = clone(clientRect);
  13528. if (toStart) {
  13529. clientRect.right = clientRect.left;
  13530. } else {
  13531. clientRect.left = clientRect.left + clientRect.width;
  13532. clientRect.right = clientRect.left;
  13533. }
  13534. clientRect.width = 0;
  13535. return clientRect;
  13536. }
  13537. function isEqual(rect1, rect2) {
  13538. return (
  13539. rect1.left === rect2.left &&
  13540. rect1.top === rect2.top &&
  13541. rect1.bottom === rect2.bottom &&
  13542. rect1.right === rect2.right
  13543. );
  13544. }
  13545. function isValidOverflow(overflowY, clientRect1, clientRect2) {
  13546. return overflowY >= 0 && overflowY <= Math.min(clientRect1.height, clientRect2.height) / 2;
  13547. }
  13548. function isAbove(clientRect1, clientRect2) {
  13549. if (clientRect1.bottom < clientRect2.top) {
  13550. return true;
  13551. }
  13552. if (clientRect1.top > clientRect2.bottom) {
  13553. return false;
  13554. }
  13555. return isValidOverflow(clientRect2.top - clientRect1.bottom, clientRect1, clientRect2);
  13556. }
  13557. function isBelow(clientRect1, clientRect2) {
  13558. if (clientRect1.top > clientRect2.bottom) {
  13559. return true;
  13560. }
  13561. if (clientRect1.bottom < clientRect2.top) {
  13562. return false;
  13563. }
  13564. return isValidOverflow(clientRect2.bottom - clientRect1.top, clientRect1, clientRect2);
  13565. }
  13566. function isLeft(clientRect1, clientRect2) {
  13567. return clientRect1.left < clientRect2.left;
  13568. }
  13569. function isRight(clientRect1, clientRect2) {
  13570. return clientRect1.right > clientRect2.right;
  13571. }
  13572. function compare(clientRect1, clientRect2) {
  13573. if (isAbove(clientRect1, clientRect2)) {
  13574. return -1;
  13575. }
  13576. if (isBelow(clientRect1, clientRect2)) {
  13577. return 1;
  13578. }
  13579. if (isLeft(clientRect1, clientRect2)) {
  13580. return -1;
  13581. }
  13582. if (isRight(clientRect1, clientRect2)) {
  13583. return 1;
  13584. }
  13585. return 0;
  13586. }
  13587. function containsXY(clientRect, clientX, clientY) {
  13588. return (
  13589. clientX >= clientRect.left &&
  13590. clientX <= clientRect.right &&
  13591. clientY >= clientRect.top &&
  13592. clientY <= clientRect.bottom
  13593. );
  13594. }
  13595. return {
  13596. clone: clone,
  13597. collapse: collapse,
  13598. isEqual: isEqual,
  13599. isAbove: isAbove,
  13600. isBelow: isBelow,
  13601. isLeft: isLeft,
  13602. isRight: isRight,
  13603. compare: compare,
  13604. containsXY: containsXY
  13605. };
  13606. });
  13607. // Included from: js/tinymce/classes/text/ExtendingChar.js
  13608. /**
  13609. * ExtendingChar.js
  13610. *
  13611. * Released under LGPL License.
  13612. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  13613. *
  13614. * License: http://www.tinymce.com/license
  13615. * Contributing: http://www.tinymce.com/contributing
  13616. */
  13617. /**
  13618. * This class contains logic for detecting extending characters.
  13619. *
  13620. * @private
  13621. * @class tinymce.text.ExtendingChar
  13622. * @example
  13623. * var isExtending = ExtendingChar.isExtendingChar('a');
  13624. */
  13625. define("tinymce/text/ExtendingChar", [], function() {
  13626. // Generated from: http://www.unicode.org/Public/UNIDATA/DerivedCoreProperties.txt
  13627. // Only includes the characters in that fit into UCS-2 16 bit
  13628. var extendingChars = new RegExp(
  13629. "[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A" +
  13630. "\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0" +
  13631. "\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E3-\u0902\u093A\u093C" +
  13632. "\u0941-\u0948\u094D\u0951-\u0957\u0962-\u0963\u0981\u09BC\u09BE\u09C1-\u09C4\u09CD\u09D7\u09E2-\u09E3" +
  13633. "\u0A01-\u0A02\u0A3C\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A51\u0A70-\u0A71\u0A75\u0A81-\u0A82\u0ABC" +
  13634. "\u0AC1-\u0AC5\u0AC7-\u0AC8\u0ACD\u0AE2-\u0AE3\u0B01\u0B3C\u0B3E\u0B3F\u0B41-\u0B44\u0B4D\u0B56\u0B57" +
  13635. "\u0B62-\u0B63\u0B82\u0BBE\u0BC0\u0BCD\u0BD7\u0C00\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56" +
  13636. "\u0C62-\u0C63\u0C81\u0CBC\u0CBF\u0CC2\u0CC6\u0CCC-\u0CCD\u0CD5-\u0CD6\u0CE2-\u0CE3\u0D01\u0D3E\u0D41-\u0D44" +
  13637. "\u0D4D\u0D57\u0D62-\u0D63\u0DCA\u0DCF\u0DD2-\u0DD4\u0DD6\u0DDF\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9" +
  13638. "\u0EBB-\u0EBC\u0EC8-\u0ECD\u0F18-\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86-\u0F87\u0F8D-\u0F97" +
  13639. "\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039-\u103A\u103D-\u103E\u1058-\u1059\u105E-\u1060\u1071-\u1074" +
  13640. "\u1082\u1085-\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17B4-\u17B5" +
  13641. "\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u18A9\u1920-\u1922\u1927-\u1928\u1932\u1939-\u193B\u1A17-\u1A18" +
  13642. "\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABD\u1ABE\u1B00-\u1B03\u1B34" +
  13643. "\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80-\u1B81\u1BA2-\u1BA5\u1BA8-\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8-\u1BE9" +
  13644. "\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8-\u1CF9" +
  13645. "\u1DC0-\u1DF5\u1DFC-\u1DFF\u200C-\u200D\u20D0-\u20DC\u20DD-\u20E0\u20E1\u20E2-\u20E4\u20E5-\u20F0\u2CEF-\u2CF1" +
  13646. "\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u302E-\u302F\u3099-\u309A\uA66F\uA670-\uA672\uA674-\uA67D\uA69E-\uA69F\uA6F0-\uA6F1" +
  13647. "\uA802\uA806\uA80B\uA825-\uA826\uA8C4\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC" +
  13648. "\uA9E5\uAA29-\uAA2E\uAA31-\uAA32\uAA35-\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7-\uAAB8\uAABE-\uAABF\uAAC1" +
  13649. "\uAAEC-\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFF9E-\uFF9F]"
  13650. );
  13651. function isExtendingChar(ch) {
  13652. return typeof ch == "string" && ch.charCodeAt(0) >= 768 && extendingChars.test(ch);
  13653. }
  13654. return {
  13655. isExtendingChar: isExtendingChar
  13656. };
  13657. });
  13658. // Included from: js/tinymce/classes/caret/CaretPosition.js
  13659. /**
  13660. * CaretPosition.js
  13661. *
  13662. * Released under LGPL License.
  13663. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  13664. *
  13665. * License: http://www.tinymce.com/license
  13666. * Contributing: http://www.tinymce.com/contributing
  13667. */
  13668. /**
  13669. * This module contains logic for creating caret positions within a document a caretposition
  13670. * is similar to a DOMRange object but it doesn't have two endpoints and is also more lightweight
  13671. * since it's now updated live when the DOM changes.
  13672. *
  13673. * @private
  13674. * @class tinymce.caret.CaretPosition
  13675. * @example
  13676. * var caretPos1 = new CaretPosition(container, offset);
  13677. * var caretPos2 = CaretPosition.fromRangeStart(someRange);
  13678. */
  13679. define("tinymce/caret/CaretPosition", [
  13680. "tinymce/util/Fun",
  13681. "tinymce/dom/NodeType",
  13682. "tinymce/dom/DOMUtils",
  13683. "tinymce/dom/RangeUtils",
  13684. "tinymce/caret/CaretCandidate",
  13685. "tinymce/geom/ClientRect",
  13686. "tinymce/text/ExtendingChar"
  13687. ], function(Fun, NodeType, DOMUtils, RangeUtils, CaretCandidate, ClientRect, ExtendingChar) {
  13688. var isElement = NodeType.isElement,
  13689. isCaretCandidate = CaretCandidate.isCaretCandidate,
  13690. isBlock = NodeType.matchStyleValues('display', 'block table'),
  13691. isFloated = NodeType.matchStyleValues('float', 'left right'),
  13692. isValidElementCaretCandidate = Fun.and(isElement, isCaretCandidate, Fun.negate(isFloated)),
  13693. isNotPre = Fun.negate(NodeType.matchStyleValues('white-space', 'pre pre-line pre-wrap')),
  13694. isText = NodeType.isText,
  13695. isBr = NodeType.isBr,
  13696. nodeIndex = DOMUtils.nodeIndex,
  13697. resolveIndex = RangeUtils.getNode;
  13698. function createRange(doc) {
  13699. return "createRange" in doc ? doc.createRange() : DOMUtils.DOM.createRng();
  13700. }
  13701. function isWhiteSpace(chr) {
  13702. return chr && /[\r\n\t ]/.test(chr);
  13703. }
  13704. function isHiddenWhiteSpaceRange(range) {
  13705. var container = range.startContainer,
  13706. offset = range.startOffset,
  13707. text;
  13708. if (isWhiteSpace(range.toString()) && isNotPre(container.parentNode)) {
  13709. text = container.data;
  13710. if (isWhiteSpace(text[offset - 1]) || isWhiteSpace(text[offset + 1])) {
  13711. return true;
  13712. }
  13713. }
  13714. return false;
  13715. }
  13716. function getCaretPositionClientRects(caretPosition) {
  13717. var clientRects = [], beforeNode, node;
  13718. // Hack for older WebKit versions that doesn't
  13719. // support getBoundingClientRect on BR elements
  13720. function getBrClientRect(brNode) {
  13721. var doc = brNode.ownerDocument,
  13722. rng = createRange(doc),
  13723. nbsp = doc.createTextNode('\u00a0'),
  13724. parentNode = brNode.parentNode,
  13725. clientRect;
  13726. parentNode.insertBefore(nbsp, brNode);
  13727. rng.setStart(nbsp, 0);
  13728. rng.setEnd(nbsp, 1);
  13729. clientRect = ClientRect.clone(rng.getBoundingClientRect());
  13730. parentNode.removeChild(nbsp);
  13731. return clientRect;
  13732. }
  13733. function getBoundingClientRect(item) {
  13734. var clientRect, clientRects;
  13735. clientRects = item.getClientRects();
  13736. if (clientRects.length > 0) {
  13737. clientRect = ClientRect.clone(clientRects[0]);
  13738. } else {
  13739. clientRect = ClientRect.clone(item.getBoundingClientRect());
  13740. }
  13741. if (isBr(item) && clientRect.left === 0) {
  13742. return getBrClientRect(item);
  13743. }
  13744. return clientRect;
  13745. }
  13746. function collapseAndInflateWidth(clientRect, toStart) {
  13747. clientRect = ClientRect.collapse(clientRect, toStart);
  13748. clientRect.width = 1;
  13749. clientRect.right = clientRect.left + 1;
  13750. return clientRect;
  13751. }
  13752. function addUniqueAndValidRect(clientRect) {
  13753. if (clientRect.height === 0) {
  13754. return;
  13755. }
  13756. if (clientRects.length > 0) {
  13757. if (ClientRect.isEqual(clientRect, clientRects[clientRects.length - 1])) {
  13758. return;
  13759. }
  13760. }
  13761. clientRects.push(clientRect);
  13762. }
  13763. function addCharacterOffset(container, offset) {
  13764. var range = createRange(container.ownerDocument);
  13765. if (offset < container.data.length) {
  13766. if (ExtendingChar.isExtendingChar(container.data[offset])) {
  13767. return clientRects;
  13768. }
  13769. // WebKit returns two client rects for a position after an extending
  13770. // character a\uxxx|b so expand on "b" and collapse to start of "b" box
  13771. if (ExtendingChar.isExtendingChar(container.data[offset - 1])) {
  13772. range.setStart(container, offset);
  13773. range.setEnd(container, offset + 1);
  13774. if (!isHiddenWhiteSpaceRange(range)) {
  13775. addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), false));
  13776. return clientRects;
  13777. }
  13778. }
  13779. }
  13780. if (offset > 0) {
  13781. range.setStart(container, offset - 1);
  13782. range.setEnd(container, offset);
  13783. if (!isHiddenWhiteSpaceRange(range)) {
  13784. addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), false));
  13785. }
  13786. }
  13787. if (offset < container.data.length) {
  13788. range.setStart(container, offset);
  13789. range.setEnd(container, offset + 1);
  13790. if (!isHiddenWhiteSpaceRange(range)) {
  13791. addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), true));
  13792. }
  13793. }
  13794. }
  13795. if (isText(caretPosition.container())) {
  13796. addCharacterOffset(caretPosition.container(), caretPosition.offset());
  13797. return clientRects;
  13798. }
  13799. if (isElement(caretPosition.container())) {
  13800. if (caretPosition.isAtEnd()) {
  13801. node = resolveIndex(caretPosition.container(), caretPosition.offset());
  13802. if (isText(node)) {
  13803. addCharacterOffset(node, node.data.length);
  13804. }
  13805. if (isValidElementCaretCandidate(node) && !isBr(node)) {
  13806. addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), false));
  13807. }
  13808. } else {
  13809. node = resolveIndex(caretPosition.container(), caretPosition.offset());
  13810. if (isText(node)) {
  13811. addCharacterOffset(node, 0);
  13812. }
  13813. if (isValidElementCaretCandidate(node) && caretPosition.isAtEnd()) {
  13814. addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), false));
  13815. return clientRects;
  13816. }
  13817. beforeNode = resolveIndex(caretPosition.container(), caretPosition.offset() - 1);
  13818. if (isValidElementCaretCandidate(beforeNode) && !isBr(beforeNode)) {
  13819. if (isBlock(beforeNode) || isBlock(node) || !isValidElementCaretCandidate(node)) {
  13820. addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(beforeNode), false));
  13821. }
  13822. }
  13823. if (isValidElementCaretCandidate(node)) {
  13824. addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), true));
  13825. }
  13826. }
  13827. }
  13828. return clientRects;
  13829. }
  13830. /**
  13831. * Represents a location within the document by a container and an offset.
  13832. *
  13833. * @constructor
  13834. * @param {Node} container Container node.
  13835. * @param {Number} offset Offset within that container node.
  13836. * @param {Array} clientRects Optional client rects array for the position.
  13837. */
  13838. function CaretPosition(container, offset, clientRects) {
  13839. function isAtStart() {
  13840. if (isText(container)) {
  13841. return offset === 0;
  13842. }
  13843. return offset === 0;
  13844. }
  13845. function isAtEnd() {
  13846. if (isText(container)) {
  13847. return offset >= container.data.length;
  13848. }
  13849. return offset >= container.childNodes.length;
  13850. }
  13851. function toRange() {
  13852. var range;
  13853. range = createRange(container.ownerDocument);
  13854. range.setStart(container, offset);
  13855. range.setEnd(container, offset);
  13856. return range;
  13857. }
  13858. function getClientRects() {
  13859. if (!clientRects) {
  13860. clientRects = getCaretPositionClientRects(new CaretPosition(container, offset));
  13861. }
  13862. return clientRects;
  13863. }
  13864. function isVisible() {
  13865. return getClientRects().length > 0;
  13866. }
  13867. function isEqual(caretPosition) {
  13868. return caretPosition && container === caretPosition.container() && offset === caretPosition.offset();
  13869. }
  13870. function getNode(before) {
  13871. return resolveIndex(container, before ? offset - 1 : offset);
  13872. }
  13873. return {
  13874. /**
  13875. * Returns the container node.
  13876. *
  13877. * @method container
  13878. * @return {Node} Container node.
  13879. */
  13880. container: Fun.constant(container),
  13881. /**
  13882. * Returns the offset within the container node.
  13883. *
  13884. * @method offset
  13885. * @return {Number} Offset within the container node.
  13886. */
  13887. offset: Fun.constant(offset),
  13888. /**
  13889. * Returns a range out of a the caret position.
  13890. *
  13891. * @method toRange
  13892. * @return {DOMRange} range for the caret position.
  13893. */
  13894. toRange: toRange,
  13895. /**
  13896. * Returns the client rects for the caret position. Might be multiple rects between
  13897. * block elements.
  13898. *
  13899. * @method getClientRects
  13900. * @return {Array} Array of client rects.
  13901. */
  13902. getClientRects: getClientRects,
  13903. /**
  13904. * Returns true if the caret location is visible/displayed on screen.
  13905. *
  13906. * @method isVisible
  13907. * @return {Boolean} true/false if the position is visible or not.
  13908. */
  13909. isVisible: isVisible,
  13910. /**
  13911. * Returns true if the caret location is at the beginning of text node or container.
  13912. *
  13913. * @method isVisible
  13914. * @return {Boolean} true/false if the position is at the beginning.
  13915. */
  13916. isAtStart: isAtStart,
  13917. /**
  13918. * Returns true if the caret location is at the end of text node or container.
  13919. *
  13920. * @method isVisible
  13921. * @return {Boolean} true/false if the position is at the end.
  13922. */
  13923. isAtEnd: isAtEnd,
  13924. /**
  13925. * Compares the caret position to another caret position. This will only compare the
  13926. * container and offset not it's visual position.
  13927. *
  13928. * @method isEqual
  13929. * @param {tinymce.caret.CaretPosition} caretPosition Caret position to compare with.
  13930. * @return {Boolean} true if the caret positions are equal.
  13931. */
  13932. isEqual: isEqual,
  13933. /**
  13934. * Returns the closest resolved node from a node index. That means if you have an offset after the
  13935. * last node in a container it will return that last node.
  13936. *
  13937. * @method getNode
  13938. * @return {Node} Node that is closest to the index.
  13939. */
  13940. getNode: getNode
  13941. };
  13942. }
  13943. /**
  13944. * Creates a caret position from the start of a range.
  13945. *
  13946. * @method fromRangeStart
  13947. * @param {DOMRange} range DOM Range to create caret position from.
  13948. * @return {tinymce.caret.CaretPosition} Caret position from the start of DOM range.
  13949. */
  13950. CaretPosition.fromRangeStart = function(range) {
  13951. return new CaretPosition(range.startContainer, range.startOffset);
  13952. };
  13953. /**
  13954. * Creates a caret position from the end of a range.
  13955. *
  13956. * @method fromRangeEnd
  13957. * @param {DOMRange} range DOM Range to create caret position from.
  13958. * @return {tinymce.caret.CaretPosition} Caret position from the end of DOM range.
  13959. */
  13960. CaretPosition.fromRangeEnd = function(range) {
  13961. return new CaretPosition(range.endContainer, range.endOffset);
  13962. };
  13963. /**
  13964. * Creates a caret position from a node and places the offset after it.
  13965. *
  13966. * @method after
  13967. * @param {Node} node Node to get caret position from.
  13968. * @return {tinymce.caret.CaretPosition} Caret position from the node.
  13969. */
  13970. CaretPosition.after = function(node) {
  13971. return new CaretPosition(node.parentNode, nodeIndex(node) + 1);
  13972. };
  13973. /**
  13974. * Creates a caret position from a node and places the offset before it.
  13975. *
  13976. * @method before
  13977. * @param {Node} node Node to get caret position from.
  13978. * @return {tinymce.caret.CaretPosition} Caret position from the node.
  13979. */
  13980. CaretPosition.before = function(node) {
  13981. return new CaretPosition(node.parentNode, nodeIndex(node));
  13982. };
  13983. return CaretPosition;
  13984. });
  13985. // Included from: js/tinymce/classes/caret/CaretBookmark.js
  13986. /**
  13987. * CaretBookmark.js
  13988. *
  13989. * Released under LGPL License.
  13990. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  13991. *
  13992. * License: http://www.tinymce.com/license
  13993. * Contributing: http://www.tinymce.com/contributing
  13994. */
  13995. /**
  13996. * This module creates or resolves xpath like string representation of a CaretPositions.
  13997. *
  13998. * The format is a / separated list of chunks with:
  13999. * <element|text()>[index|after|before]
  14000. *
  14001. * For example:
  14002. * p[0]/b[0]/text()[0],1 = <p><b>a|c</b></p>
  14003. * p[0]/img[0],before = <p>|<img></p>
  14004. * p[0]/img[0],after = <p><img>|</p>
  14005. *
  14006. * @private
  14007. * @static
  14008. * @class tinymce.caret.CaretBookmark
  14009. * @example
  14010. * var bookmark = CaretBookmark.create(rootElm, CaretPosition.before(rootElm.firstChild));
  14011. * var caretPosition = CaretBookmark.resolve(bookmark);
  14012. */
  14013. define('tinymce/caret/CaretBookmark', [
  14014. 'tinymce/dom/NodeType',
  14015. 'tinymce/dom/DOMUtils',
  14016. 'tinymce/util/Fun',
  14017. 'tinymce/util/Arr',
  14018. 'tinymce/caret/CaretPosition'
  14019. ], function(NodeType, DomUtils, Fun, Arr, CaretPosition) {
  14020. var isText = NodeType.isText,
  14021. isBogus = NodeType.isBogus,
  14022. nodeIndex = DomUtils.nodeIndex;
  14023. function normalizedParent(node) {
  14024. var parentNode = node.parentNode;
  14025. if (isBogus(parentNode)) {
  14026. return normalizedParent(parentNode);
  14027. }
  14028. return parentNode;
  14029. }
  14030. function getChildNodes(node) {
  14031. if (!node) {
  14032. return [];
  14033. }
  14034. return Arr.reduce(node.childNodes, function(result, node) {
  14035. if (isBogus(node) && node.nodeName != 'BR') {
  14036. result = result.concat(getChildNodes(node));
  14037. } else {
  14038. result.push(node);
  14039. }
  14040. return result;
  14041. }, []);
  14042. }
  14043. function normalizedTextOffset(textNode, offset) {
  14044. while ((textNode = textNode.previousSibling)) {
  14045. if (!isText(textNode)) {
  14046. break;
  14047. }
  14048. offset += textNode.data.length;
  14049. }
  14050. return offset;
  14051. }
  14052. function equal(targetValue) {
  14053. return function(value) {
  14054. return targetValue === value;
  14055. };
  14056. }
  14057. function normalizedNodeIndex(node) {
  14058. var nodes, index, numTextFragments;
  14059. nodes = getChildNodes(normalizedParent(node));
  14060. index = Arr.findIndex(nodes, equal(node), node);
  14061. nodes = nodes.slice(0, index + 1);
  14062. numTextFragments = Arr.reduce(nodes, function(result, node, i) {
  14063. if (isText(node) && isText(nodes[i - 1])) {
  14064. result++;
  14065. }
  14066. return result;
  14067. }, 0);
  14068. nodes = Arr.filter(nodes, NodeType.matchNodeNames(node.nodeName));
  14069. index = Arr.findIndex(nodes, equal(node), node);
  14070. return index - numTextFragments;
  14071. }
  14072. function createPathItem(node) {
  14073. var name;
  14074. if (isText(node)) {
  14075. name = 'text()';
  14076. } else {
  14077. name = node.nodeName.toLowerCase();
  14078. }
  14079. return name + '[' + normalizedNodeIndex(node) + ']';
  14080. }
  14081. function parentsUntil(rootNode, node, predicate) {
  14082. var parents = [];
  14083. for (node = node.parentNode; node != rootNode; node = node.parentNode) {
  14084. if (predicate && predicate(node)) {
  14085. break;
  14086. }
  14087. parents.push(node);
  14088. }
  14089. return parents;
  14090. }
  14091. function create(rootNode, caretPosition) {
  14092. var container, offset, path = [],
  14093. outputOffset, childNodes, parents;
  14094. container = caretPosition.container();
  14095. offset = caretPosition.offset();
  14096. if (isText(container)) {
  14097. outputOffset = normalizedTextOffset(container, offset);
  14098. } else {
  14099. childNodes = container.childNodes;
  14100. if (offset >= childNodes.length) {
  14101. outputOffset = 'after';
  14102. offset = childNodes.length - 1;
  14103. } else {
  14104. outputOffset = 'before';
  14105. }
  14106. container = childNodes[offset];
  14107. }
  14108. path.push(createPathItem(container));
  14109. parents = parentsUntil(rootNode, container);
  14110. parents = Arr.filter(parents, Fun.negate(NodeType.isBogus));
  14111. path = path.concat(Arr.map(parents, function(node) {
  14112. return createPathItem(node);
  14113. }));
  14114. return path.reverse().join('/') + ',' + outputOffset;
  14115. }
  14116. function resolvePathItem(node, name, index) {
  14117. var nodes = getChildNodes(node);
  14118. nodes = Arr.filter(nodes, function(node, index) {
  14119. return !isText(node) || !isText(nodes[index - 1]);
  14120. });
  14121. nodes = Arr.filter(nodes, NodeType.matchNodeNames(name));
  14122. return nodes[index];
  14123. }
  14124. function findTextPosition(container, offset) {
  14125. var node = container, targetOffset = 0, dataLen;
  14126. while (isText(node)) {
  14127. dataLen = node.data.length;
  14128. if (offset >= targetOffset && offset <= targetOffset + dataLen) {
  14129. container = node;
  14130. offset = offset - targetOffset;
  14131. break;
  14132. }
  14133. if (!isText(node.nextSibling)) {
  14134. container = node;
  14135. offset = dataLen;
  14136. break;
  14137. }
  14138. targetOffset += dataLen;
  14139. node = node.nextSibling;
  14140. }
  14141. if (offset > container.data.length) {
  14142. offset = container.data.length;
  14143. }
  14144. return new CaretPosition(container, offset);
  14145. }
  14146. function resolve(rootNode, path) {
  14147. var parts, container, offset;
  14148. if (!path) {
  14149. return null;
  14150. }
  14151. parts = path.split(',');
  14152. path = parts[0].split('/');
  14153. offset = parts.length > 1 ? parts[1] : 'before';
  14154. container = Arr.reduce(path, function(result, value) {
  14155. value = /([\w\-\(\)]+)\[([0-9]+)\]/.exec(value);
  14156. if (!value) {
  14157. return null;
  14158. }
  14159. if (value[1] === 'text()') {
  14160. value[1] = '#text';
  14161. }
  14162. return resolvePathItem(result, value[1], parseInt(value[2], 10));
  14163. }, rootNode);
  14164. if (!container) {
  14165. return null;
  14166. }
  14167. if (!isText(container)) {
  14168. if (offset === 'after') {
  14169. offset = nodeIndex(container) + 1;
  14170. } else {
  14171. offset = nodeIndex(container);
  14172. }
  14173. return new CaretPosition(container.parentNode, offset);
  14174. }
  14175. return findTextPosition(container, parseInt(offset, 10));
  14176. }
  14177. return {
  14178. /**
  14179. * Create a xpath bookmark location for the specified caret position.
  14180. *
  14181. * @method create
  14182. * @param {Node} rootNode Root node to create bookmark within.
  14183. * @param {tinymce.caret.CaretPosition} caretPosition Caret position within the root node.
  14184. * @return {String} String xpath like location of caret position.
  14185. */
  14186. create: create,
  14187. /**
  14188. * Resolves a xpath like bookmark location to the a caret position.
  14189. *
  14190. * @method resolve
  14191. * @param {Node} rootNode Root node to resolve xpath bookmark within.
  14192. * @param {String} bookmark Bookmark string to resolve.
  14193. * @return {tinymce.caret.CaretPosition} Caret position resolved from xpath like bookmark.
  14194. */
  14195. resolve: resolve
  14196. };
  14197. });
  14198. // Included from: js/tinymce/classes/dom/BookmarkManager.js
  14199. /**
  14200. * BookmarkManager.js
  14201. *
  14202. * Released under LGPL License.
  14203. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  14204. *
  14205. * License: http://www.tinymce.com/license
  14206. * Contributing: http://www.tinymce.com/contributing
  14207. */
  14208. /**
  14209. * This class handles selection bookmarks.
  14210. *
  14211. * @class tinymce.dom.BookmarkManager
  14212. */
  14213. define("tinymce/dom/BookmarkManager", [
  14214. "tinymce/Env",
  14215. "tinymce/util/Tools",
  14216. "tinymce/caret/CaretContainer",
  14217. "tinymce/caret/CaretBookmark",
  14218. "tinymce/caret/CaretPosition",
  14219. "tinymce/dom/NodeType"
  14220. ], function(Env, Tools, CaretContainer, CaretBookmark, CaretPosition, NodeType) {
  14221. var isContentEditableFalse = NodeType.isContentEditableFalse;
  14222. /**
  14223. * Constructs a new BookmarkManager instance for a specific selection instance.
  14224. *
  14225. * @constructor
  14226. * @method BookmarkManager
  14227. * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
  14228. */
  14229. function BookmarkManager(selection) {
  14230. var dom = selection.dom;
  14231. /**
  14232. * Returns a bookmark location for the current selection. This bookmark object
  14233. * can then be used to restore the selection after some content modification to the document.
  14234. *
  14235. * @method getBookmark
  14236. * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
  14237. * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
  14238. * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
  14239. * @example
  14240. * // Stores a bookmark of the current selection
  14241. * var bm = tinymce.activeEditor.selection.getBookmark();
  14242. *
  14243. * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
  14244. *
  14245. * // Restore the selection bookmark
  14246. * tinymce.activeEditor.selection.moveToBookmark(bm);
  14247. */
  14248. this.getBookmark = function(type, normalized) {
  14249. var rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
  14250. function findIndex(name, element) {
  14251. var count = 0;
  14252. Tools.each(dom.select(name), function(node) {
  14253. if (node.getAttribute('data-mce-bogus') === 'all') {
  14254. return;
  14255. }
  14256. if (node == element) {
  14257. return false;
  14258. }
  14259. count++;
  14260. });
  14261. return count;
  14262. }
  14263. function normalizeTableCellSelection(rng) {
  14264. function moveEndPoint(start) {
  14265. var container, offset, childNodes, prefix = start ? 'start' : 'end';
  14266. container = rng[prefix + 'Container'];
  14267. offset = rng[prefix + 'Offset'];
  14268. if (container.nodeType == 1 && container.nodeName == "TR") {
  14269. childNodes = container.childNodes;
  14270. container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
  14271. if (container) {
  14272. offset = start ? 0 : container.childNodes.length;
  14273. rng['set' + (start ? 'Start' : 'End')](container, offset);
  14274. }
  14275. }
  14276. }
  14277. moveEndPoint(true);
  14278. moveEndPoint();
  14279. return rng;
  14280. }
  14281. function getLocation(rng) {
  14282. var root = dom.getRoot(), bookmark = {};
  14283. function getPoint(rng, start) {
  14284. var container = rng[start ? 'startContainer' : 'endContainer'],
  14285. offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
  14286. if (container.nodeType == 3) {
  14287. if (normalized) {
  14288. for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
  14289. offset += node.nodeValue.length;
  14290. }
  14291. }
  14292. point.push(offset);
  14293. } else {
  14294. childNodes = container.childNodes;
  14295. if (offset >= childNodes.length && childNodes.length) {
  14296. after = 1;
  14297. offset = Math.max(0, childNodes.length - 1);
  14298. }
  14299. point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
  14300. }
  14301. for (; container && container != root; container = container.parentNode) {
  14302. point.push(dom.nodeIndex(container, normalized));
  14303. }
  14304. return point;
  14305. }
  14306. bookmark.start = getPoint(rng, true);
  14307. if (!selection.isCollapsed()) {
  14308. bookmark.end = getPoint(rng);
  14309. }
  14310. return bookmark;
  14311. }
  14312. function findAdjacentContentEditableFalseElm(rng) {
  14313. function findSibling(node) {
  14314. var sibling;
  14315. if (CaretContainer.isCaretContainer(node)) {
  14316. if (NodeType.isText(node) && CaretContainer.isCaretContainerBlock(node)) {
  14317. node = node.parentNode;
  14318. }
  14319. sibling = node.previousSibling;
  14320. if (isContentEditableFalse(sibling)) {
  14321. return sibling;
  14322. }
  14323. sibling = node.nextSibling;
  14324. if (isContentEditableFalse(sibling)) {
  14325. return sibling;
  14326. }
  14327. }
  14328. }
  14329. return findSibling(rng.startContainer) || findSibling(rng.endContainer);
  14330. }
  14331. if (type == 2) {
  14332. element = selection.getNode();
  14333. name = element ? element.nodeName : null;
  14334. rng = selection.getRng();
  14335. if (isContentEditableFalse(element) || name == 'IMG') {
  14336. return {name: name, index: findIndex(name, element)};
  14337. }
  14338. if (selection.tridentSel) {
  14339. return selection.tridentSel.getBookmark(type);
  14340. }
  14341. element = findAdjacentContentEditableFalseElm(rng);
  14342. if (element) {
  14343. name = element.tagName;
  14344. return {name: name, index: findIndex(name, element)};
  14345. }
  14346. return getLocation(rng);
  14347. }
  14348. if (type == 3) {
  14349. rng = selection.getRng();
  14350. return {
  14351. start: CaretBookmark.create(dom.getRoot(), CaretPosition.fromRangeStart(rng)),
  14352. end: CaretBookmark.create(dom.getRoot(), CaretPosition.fromRangeEnd(rng))
  14353. };
  14354. }
  14355. // Handle simple range
  14356. if (type) {
  14357. return {rng: selection.getRng()};
  14358. }
  14359. rng = selection.getRng();
  14360. id = dom.uniqueId();
  14361. collapsed = selection.isCollapsed();
  14362. styles = 'overflow:hidden;line-height:0px';
  14363. // Explorer method
  14364. if (rng.duplicate || rng.item) {
  14365. // Text selection
  14366. if (!rng.item) {
  14367. rng2 = rng.duplicate();
  14368. try {
  14369. // Insert start marker
  14370. rng.collapse();
  14371. rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
  14372. // Insert end marker
  14373. if (!collapsed) {
  14374. rng2.collapse(false);
  14375. // Detect the empty space after block elements in IE and move the
  14376. // end back one character <p></p>] becomes <p>]</p>
  14377. rng.moveToElementText(rng2.parentElement());
  14378. if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
  14379. rng2.move('character', -1);
  14380. }
  14381. rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
  14382. }
  14383. } catch (ex) {
  14384. // IE might throw unspecified error so lets ignore it
  14385. return null;
  14386. }
  14387. } else {
  14388. // Control selection
  14389. element = rng.item(0);
  14390. name = element.nodeName;
  14391. return {name: name, index: findIndex(name, element)};
  14392. }
  14393. } else {
  14394. element = selection.getNode();
  14395. name = element.nodeName;
  14396. if (name == 'IMG') {
  14397. return {name: name, index: findIndex(name, element)};
  14398. }
  14399. // W3C method
  14400. rng2 = normalizeTableCellSelection(rng.cloneRange());
  14401. // Insert end marker
  14402. if (!collapsed) {
  14403. rng2.collapse(false);
  14404. rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
  14405. }
  14406. rng = normalizeTableCellSelection(rng);
  14407. rng.collapse(true);
  14408. rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
  14409. }
  14410. selection.moveToBookmark({id: id, keep: 1});
  14411. return {id: id};
  14412. };
  14413. /**
  14414. * Restores the selection to the specified bookmark.
  14415. *
  14416. * @method moveToBookmark
  14417. * @param {Object} bookmark Bookmark to restore selection from.
  14418. * @return {Boolean} true/false if it was successful or not.
  14419. * @example
  14420. * // Stores a bookmark of the current selection
  14421. * var bm = tinymce.activeEditor.selection.getBookmark();
  14422. *
  14423. * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
  14424. *
  14425. * // Restore the selection bookmark
  14426. * tinymce.activeEditor.selection.moveToBookmark(bm);
  14427. */
  14428. this.moveToBookmark = function(bookmark) {
  14429. var rng, root, startContainer, endContainer, startOffset, endOffset;
  14430. function setEndPoint(start) {
  14431. var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
  14432. if (point) {
  14433. offset = point[0];
  14434. // Find container node
  14435. for (node = root, i = point.length - 1; i >= 1; i--) {
  14436. children = node.childNodes;
  14437. if (point[i] > children.length - 1) {
  14438. return;
  14439. }
  14440. node = children[point[i]];
  14441. }
  14442. // Move text offset to best suitable location
  14443. if (node.nodeType === 3) {
  14444. offset = Math.min(point[0], node.nodeValue.length);
  14445. }
  14446. // Move element offset to best suitable location
  14447. if (node.nodeType === 1) {
  14448. offset = Math.min(point[0], node.childNodes.length);
  14449. }
  14450. // Set offset within container node
  14451. if (start) {
  14452. rng.setStart(node, offset);
  14453. } else {
  14454. rng.setEnd(node, offset);
  14455. }
  14456. }
  14457. return true;
  14458. }
  14459. function restoreEndPoint(suffix) {
  14460. var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
  14461. if (marker) {
  14462. node = marker.parentNode;
  14463. if (suffix == 'start') {
  14464. if (!keep) {
  14465. idx = dom.nodeIndex(marker);
  14466. } else {
  14467. node = marker.firstChild;
  14468. idx = 1;
  14469. }
  14470. startContainer = endContainer = node;
  14471. startOffset = endOffset = idx;
  14472. } else {
  14473. if (!keep) {
  14474. idx = dom.nodeIndex(marker);
  14475. } else {
  14476. node = marker.firstChild;
  14477. idx = 1;
  14478. }
  14479. endContainer = node;
  14480. endOffset = idx;
  14481. }
  14482. if (!keep) {
  14483. prev = marker.previousSibling;
  14484. next = marker.nextSibling;
  14485. // Remove all marker text nodes
  14486. Tools.each(Tools.grep(marker.childNodes), function(node) {
  14487. if (node.nodeType == 3) {
  14488. node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
  14489. }
  14490. });
  14491. // Remove marker but keep children if for example contents where inserted into the marker
  14492. // Also remove duplicated instances of the marker for example by a
  14493. // split operation or by WebKit auto split on paste feature
  14494. while ((marker = dom.get(bookmark.id + '_' + suffix))) {
  14495. dom.remove(marker, 1);
  14496. }
  14497. // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
  14498. // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
  14499. // isn't worth the effort. Sorry, Opera but it's just a fact
  14500. if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
  14501. idx = prev.nodeValue.length;
  14502. prev.appendData(next.nodeValue);
  14503. dom.remove(next);
  14504. if (suffix == 'start') {
  14505. startContainer = endContainer = prev;
  14506. startOffset = endOffset = idx;
  14507. } else {
  14508. endContainer = prev;
  14509. endOffset = idx;
  14510. }
  14511. }
  14512. }
  14513. }
  14514. }
  14515. function addBogus(node) {
  14516. // Adds a bogus BR element for empty block elements
  14517. if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
  14518. node.innerHTML = '<br data-mce-bogus="1" />';
  14519. }
  14520. return node;
  14521. }
  14522. function resolveCaretPositionBookmark() {
  14523. var rng, pos;
  14524. rng = dom.createRng();
  14525. pos = CaretBookmark.resolve(dom.getRoot(), bookmark.start);
  14526. rng.setStart(pos.container(), pos.offset());
  14527. pos = CaretBookmark.resolve(dom.getRoot(), bookmark.end);
  14528. rng.setEnd(pos.container(), pos.offset());
  14529. return rng;
  14530. }
  14531. if (bookmark) {
  14532. if (Tools.isArray(bookmark.start)) {
  14533. rng = dom.createRng();
  14534. root = dom.getRoot();
  14535. if (selection.tridentSel) {
  14536. return selection.tridentSel.moveToBookmark(bookmark);
  14537. }
  14538. if (setEndPoint(true) && setEndPoint()) {
  14539. selection.setRng(rng);
  14540. }
  14541. } else if (typeof bookmark.start == 'string') {
  14542. selection.setRng(resolveCaretPositionBookmark(bookmark));
  14543. } else if (bookmark.id) {
  14544. // Restore start/end points
  14545. restoreEndPoint('start');
  14546. restoreEndPoint('end');
  14547. if (startContainer) {
  14548. rng = dom.createRng();
  14549. rng.setStart(addBogus(startContainer), startOffset);
  14550. rng.setEnd(addBogus(endContainer), endOffset);
  14551. selection.setRng(rng);
  14552. }
  14553. } else if (bookmark.name) {
  14554. selection.select(dom.select(bookmark.name)[bookmark.index]);
  14555. } else if (bookmark.rng) {
  14556. selection.setRng(bookmark.rng);
  14557. }
  14558. }
  14559. };
  14560. }
  14561. /**
  14562. * Returns true/false if the specified node is a bookmark node or not.
  14563. *
  14564. * @static
  14565. * @method isBookmarkNode
  14566. * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
  14567. * @return {Boolean} true/false if the node is a bookmark node or not.
  14568. */
  14569. BookmarkManager.isBookmarkNode = function(node) {
  14570. return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
  14571. };
  14572. return BookmarkManager;
  14573. });
  14574. // Included from: js/tinymce/classes/dom/Selection.js
  14575. /**
  14576. * Selection.js
  14577. *
  14578. * Released under LGPL License.
  14579. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  14580. *
  14581. * License: http://www.tinymce.com/license
  14582. * Contributing: http://www.tinymce.com/contributing
  14583. */
  14584. /**
  14585. * This class handles text and control selection it's an crossbrowser utility class.
  14586. * Consult the TinyMCE Wiki API for more details and examples on how to use this class.
  14587. *
  14588. * @class tinymce.dom.Selection
  14589. * @example
  14590. * // Getting the currently selected node for the active editor
  14591. * alert(tinymce.activeEditor.selection.getNode().nodeName);
  14592. */
  14593. define("tinymce/dom/Selection", [
  14594. "tinymce/dom/TreeWalker",
  14595. "tinymce/dom/TridentSelection",
  14596. "tinymce/dom/ControlSelection",
  14597. "tinymce/dom/RangeUtils",
  14598. "tinymce/dom/BookmarkManager",
  14599. "tinymce/dom/NodeType",
  14600. "tinymce/Env",
  14601. "tinymce/util/Tools",
  14602. "tinymce/caret/CaretPosition"
  14603. ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, NodeType, Env, Tools, CaretPosition) {
  14604. var each = Tools.each, trim = Tools.trim;
  14605. var isIE = Env.ie;
  14606. /**
  14607. * Constructs a new selection instance.
  14608. *
  14609. * @constructor
  14610. * @method Selection
  14611. * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
  14612. * @param {Window} win Window to bind the selection object to.
  14613. * @param {tinymce.Editor} editor Editor instance of the selection.
  14614. * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
  14615. */
  14616. function Selection(dom, win, serializer, editor) {
  14617. var self = this;
  14618. self.dom = dom;
  14619. self.win = win;
  14620. self.serializer = serializer;
  14621. self.editor = editor;
  14622. self.bookmarkManager = new BookmarkManager(self);
  14623. self.controlSelection = new ControlSelection(self, editor);
  14624. // No W3C Range support
  14625. if (!self.win.getSelection) {
  14626. self.tridentSel = new TridentSelection(self);
  14627. }
  14628. }
  14629. Selection.prototype = {
  14630. /**
  14631. * Move the selection cursor range to the specified node and offset.
  14632. * If there is no node specified it will move it to the first suitable location within the body.
  14633. *
  14634. * @method setCursorLocation
  14635. * @param {Node} node Optional node to put the cursor in.
  14636. * @param {Number} offset Optional offset from the start of the node to put the cursor at.
  14637. */
  14638. setCursorLocation: function(node, offset) {
  14639. var self = this, rng = self.dom.createRng();
  14640. if (!node) {
  14641. self._moveEndPoint(rng, self.editor.getBody(), true);
  14642. self.setRng(rng);
  14643. } else {
  14644. rng.setStart(node, offset);
  14645. rng.setEnd(node, offset);
  14646. self.setRng(rng);
  14647. self.collapse(false);
  14648. }
  14649. },
  14650. /**
  14651. * Returns the selected contents using the DOM serializer passed in to this class.
  14652. *
  14653. * @method getContent
  14654. * @param {Object} args Optional settings class with for example output format text or html.
  14655. * @return {String} Selected contents in for example HTML format.
  14656. * @example
  14657. * // Alerts the currently selected contents
  14658. * alert(tinymce.activeEditor.selection.getContent());
  14659. *
  14660. * // Alerts the currently selected contents as plain text
  14661. * alert(tinymce.activeEditor.selection.getContent({format: 'text'}));
  14662. */
  14663. getContent: function(args) {
  14664. var self = this, rng = self.getRng(), tmpElm = self.dom.create("body");
  14665. var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment;
  14666. args = args || {};
  14667. whiteSpaceBefore = whiteSpaceAfter = '';
  14668. args.get = true;
  14669. args.format = args.format || 'html';
  14670. args.selection = true;
  14671. self.editor.fire('BeforeGetContent', args);
  14672. if (args.format == 'text') {
  14673. return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : ''));
  14674. }
  14675. if (rng.cloneContents) {
  14676. fragment = rng.cloneContents();
  14677. if (fragment) {
  14678. tmpElm.appendChild(fragment);
  14679. }
  14680. } else if (rng.item !== undefined || rng.htmlText !== undefined) {
  14681. // IE will produce invalid markup if elements are present that
  14682. // it doesn't understand like custom elements or HTML5 elements.
  14683. // Adding a BR in front of the contents and then remoiving it seems to fix it though.
  14684. tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText);
  14685. tmpElm.removeChild(tmpElm.firstChild);
  14686. } else {
  14687. tmpElm.innerHTML = rng.toString();
  14688. }
  14689. // Keep whitespace before and after
  14690. if (/^\s/.test(tmpElm.innerHTML)) {
  14691. whiteSpaceBefore = ' ';
  14692. }
  14693. if (/\s+$/.test(tmpElm.innerHTML)) {
  14694. whiteSpaceAfter = ' ';
  14695. }
  14696. args.getInner = true;
  14697. args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter;
  14698. self.editor.fire('GetContent', args);
  14699. return args.content;
  14700. },
  14701. /**
  14702. * Sets the current selection to the specified content. If any contents is selected it will be replaced
  14703. * with the contents passed in to this function. If there is no selection the contents will be inserted
  14704. * where the caret is placed in the editor/page.
  14705. *
  14706. * @method setContent
  14707. * @param {String} content HTML contents to set could also be other formats depending on settings.
  14708. * @param {Object} args Optional settings object with for example data format.
  14709. * @example
  14710. * // Inserts some HTML contents at the current selection
  14711. * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>');
  14712. */
  14713. setContent: function(content, args) {
  14714. var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
  14715. args = args || {format: 'html'};
  14716. args.set = true;
  14717. args.selection = true;
  14718. args.content = content;
  14719. // Dispatch before set content event
  14720. if (!args.no_events) {
  14721. self.editor.fire('BeforeSetContent', args);
  14722. }
  14723. content = args.content;
  14724. if (rng.insertNode) {
  14725. // Make caret marker since insertNode places the caret in the beginning of text after insert
  14726. content += '<span id="__caret">_</span>';
  14727. // Delete and insert new node
  14728. if (rng.startContainer == doc && rng.endContainer == doc) {
  14729. // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
  14730. doc.body.innerHTML = content;
  14731. } else {
  14732. rng.deleteContents();
  14733. if (doc.body.childNodes.length === 0) {
  14734. doc.body.innerHTML = content;
  14735. } else {
  14736. // createContextualFragment doesn't exists in IE 9 DOMRanges
  14737. if (rng.createContextualFragment) {
  14738. rng.insertNode(rng.createContextualFragment(content));
  14739. } else {
  14740. // Fake createContextualFragment call in IE 9
  14741. frag = doc.createDocumentFragment();
  14742. temp = doc.createElement('div');
  14743. frag.appendChild(temp);
  14744. temp.outerHTML = content;
  14745. rng.insertNode(frag);
  14746. }
  14747. }
  14748. }
  14749. // Move to caret marker
  14750. caretNode = self.dom.get('__caret');
  14751. // Make sure we wrap it compleatly, Opera fails with a simple select call
  14752. rng = doc.createRange();
  14753. rng.setStartBefore(caretNode);
  14754. rng.setEndBefore(caretNode);
  14755. self.setRng(rng);
  14756. // Remove the caret position
  14757. self.dom.remove('__caret');
  14758. try {
  14759. self.setRng(rng);
  14760. } catch (ex) {
  14761. // Might fail on Opera for some odd reason
  14762. }
  14763. } else {
  14764. if (rng.item) {
  14765. // Delete content and get caret text selection
  14766. doc.execCommand('Delete', false, null);
  14767. rng = self.getRng();
  14768. }
  14769. // Explorer removes spaces from the beginning of pasted contents
  14770. if (/^\s+/.test(content)) {
  14771. rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
  14772. self.dom.remove('__mce_tmp');
  14773. } else {
  14774. rng.pasteHTML(content);
  14775. }
  14776. }
  14777. // Dispatch set content event
  14778. if (!args.no_events) {
  14779. self.editor.fire('SetContent', args);
  14780. }
  14781. },
  14782. /**
  14783. * Returns the start element of a selection range. If the start is in a text
  14784. * node the parent element will be returned.
  14785. *
  14786. * @method getStart
  14787. * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
  14788. * @return {Element} Start element of selection range.
  14789. */
  14790. getStart: function(real) {
  14791. var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
  14792. if (rng.duplicate || rng.item) {
  14793. // Control selection, return first item
  14794. if (rng.item) {
  14795. return rng.item(0);
  14796. }
  14797. // Get start element
  14798. checkRng = rng.duplicate();
  14799. checkRng.collapse(1);
  14800. startElement = checkRng.parentElement();
  14801. if (startElement.ownerDocument !== self.dom.doc) {
  14802. startElement = self.dom.getRoot();
  14803. }
  14804. // Check if range parent is inside the start element, then return the inner parent element
  14805. // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
  14806. parentElement = node = rng.parentElement();
  14807. while ((node = node.parentNode)) {
  14808. if (node == startElement) {
  14809. startElement = parentElement;
  14810. break;
  14811. }
  14812. }
  14813. return startElement;
  14814. }
  14815. startElement = rng.startContainer;
  14816. if (startElement.nodeType == 1 && startElement.hasChildNodes()) {
  14817. if (!real || !rng.collapsed) {
  14818. startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
  14819. }
  14820. }
  14821. if (startElement && startElement.nodeType == 3) {
  14822. return startElement.parentNode;
  14823. }
  14824. return startElement;
  14825. },
  14826. /**
  14827. * Returns the end element of a selection range. If the end is in a text
  14828. * node the parent element will be returned.
  14829. *
  14830. * @method getEnd
  14831. * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
  14832. * @return {Element} End element of selection range.
  14833. */
  14834. getEnd: function(real) {
  14835. var self = this, rng = self.getRng(), endElement, endOffset;
  14836. if (rng.duplicate || rng.item) {
  14837. if (rng.item) {
  14838. return rng.item(0);
  14839. }
  14840. rng = rng.duplicate();
  14841. rng.collapse(0);
  14842. endElement = rng.parentElement();
  14843. if (endElement.ownerDocument !== self.dom.doc) {
  14844. endElement = self.dom.getRoot();
  14845. }
  14846. if (endElement && endElement.nodeName == 'BODY') {
  14847. return endElement.lastChild || endElement;
  14848. }
  14849. return endElement;
  14850. }
  14851. endElement = rng.endContainer;
  14852. endOffset = rng.endOffset;
  14853. if (endElement.nodeType == 1 && endElement.hasChildNodes()) {
  14854. if (!real || !rng.collapsed) {
  14855. endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
  14856. }
  14857. }
  14858. if (endElement && endElement.nodeType == 3) {
  14859. return endElement.parentNode;
  14860. }
  14861. return endElement;
  14862. },
  14863. /**
  14864. * Returns a bookmark location for the current selection. This bookmark object
  14865. * can then be used to restore the selection after some content modification to the document.
  14866. *
  14867. * @method getBookmark
  14868. * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
  14869. * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
  14870. * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
  14871. * @example
  14872. * // Stores a bookmark of the current selection
  14873. * var bm = tinymce.activeEditor.selection.getBookmark();
  14874. *
  14875. * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
  14876. *
  14877. * // Restore the selection bookmark
  14878. * tinymce.activeEditor.selection.moveToBookmark(bm);
  14879. */
  14880. getBookmark: function(type, normalized) {
  14881. return this.bookmarkManager.getBookmark(type, normalized);
  14882. },
  14883. /**
  14884. * Restores the selection to the specified bookmark.
  14885. *
  14886. * @method moveToBookmark
  14887. * @param {Object} bookmark Bookmark to restore selection from.
  14888. * @return {Boolean} true/false if it was successful or not.
  14889. * @example
  14890. * // Stores a bookmark of the current selection
  14891. * var bm = tinymce.activeEditor.selection.getBookmark();
  14892. *
  14893. * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
  14894. *
  14895. * // Restore the selection bookmark
  14896. * tinymce.activeEditor.selection.moveToBookmark(bm);
  14897. */
  14898. moveToBookmark: function(bookmark) {
  14899. return this.bookmarkManager.moveToBookmark(bookmark);
  14900. },
  14901. /**
  14902. * Selects the specified element. This will place the start and end of the selection range around the element.
  14903. *
  14904. * @method select
  14905. * @param {Element} node HTML DOM element to select.
  14906. * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
  14907. * @return {Element} Selected element the same element as the one that got passed in.
  14908. * @example
  14909. * // Select the first paragraph in the active editor
  14910. * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
  14911. */
  14912. select: function(node, content) {
  14913. var self = this, dom = self.dom, rng = dom.createRng(), idx;
  14914. // Clear stored range set by FocusManager
  14915. self.lastFocusBookmark = null;
  14916. if (node) {
  14917. if (!content && self.controlSelection.controlSelect(node)) {
  14918. return;
  14919. }
  14920. idx = dom.nodeIndex(node);
  14921. rng.setStart(node.parentNode, idx);
  14922. rng.setEnd(node.parentNode, idx + 1);
  14923. // Find first/last text node or BR element
  14924. if (content) {
  14925. self._moveEndPoint(rng, node, true);
  14926. self._moveEndPoint(rng, node);
  14927. }
  14928. self.setRng(rng);
  14929. }
  14930. return node;
  14931. },
  14932. /**
  14933. * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
  14934. *
  14935. * @method isCollapsed
  14936. * @return {Boolean} true/false state if the selection range is collapsed or not.
  14937. * Collapsed means if it's a caret or a larger selection.
  14938. */
  14939. isCollapsed: function() {
  14940. var self = this, rng = self.getRng(), sel = self.getSel();
  14941. if (!rng || rng.item) {
  14942. return false;
  14943. }
  14944. if (rng.compareEndPoints) {
  14945. return rng.compareEndPoints('StartToEnd', rng) === 0;
  14946. }
  14947. return !sel || rng.collapsed;
  14948. },
  14949. /**
  14950. * Collapse the selection to start or end of range.
  14951. *
  14952. * @method collapse
  14953. * @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to false.
  14954. */
  14955. collapse: function(toStart) {
  14956. var self = this, rng = self.getRng(), node;
  14957. // Control range on IE
  14958. if (rng.item) {
  14959. node = rng.item(0);
  14960. rng = self.win.document.body.createTextRange();
  14961. rng.moveToElementText(node);
  14962. }
  14963. rng.collapse(!!toStart);
  14964. self.setRng(rng);
  14965. },
  14966. /**
  14967. * Returns the browsers internal selection object.
  14968. *
  14969. * @method getSel
  14970. * @return {Selection} Internal browser selection object.
  14971. */
  14972. getSel: function() {
  14973. var win = this.win;
  14974. return win.getSelection ? win.getSelection() : win.document.selection;
  14975. },
  14976. /**
  14977. * Returns the browsers internal range object.
  14978. *
  14979. * @method getRng
  14980. * @param {Boolean} w3c Forces a compatible W3C range on IE.
  14981. * @return {Range} Internal browser range object.
  14982. * @see http://www.quirksmode.org/dom/range_intro.html
  14983. * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
  14984. */
  14985. getRng: function(w3c) {
  14986. var self = this, selection, rng, elm, doc, ieRng, evt;
  14987. function tryCompareBoundaryPoints(how, sourceRange, destinationRange) {
  14988. try {
  14989. return sourceRange.compareBoundaryPoints(how, destinationRange);
  14990. } catch (ex) {
  14991. // Gecko throws wrong document exception if the range points
  14992. // to nodes that where removed from the dom #6690
  14993. // Browsers should mutate existing DOMRange instances so that they always point
  14994. // to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
  14995. // For performance reasons just return -1
  14996. return -1;
  14997. }
  14998. }
  14999. if (!self.win) {
  15000. return null;
  15001. }
  15002. doc = self.win.document;
  15003. // Use last rng passed from FocusManager if it's available this enables
  15004. // calls to editor.selection.getStart() to work when caret focus is lost on IE
  15005. if (!w3c && self.lastFocusBookmark) {
  15006. var bookmark = self.lastFocusBookmark;
  15007. // Convert bookmark to range IE 11 fix
  15008. if (bookmark.startContainer) {
  15009. rng = doc.createRange();
  15010. rng.setStart(bookmark.startContainer, bookmark.startOffset);
  15011. rng.setEnd(bookmark.endContainer, bookmark.endOffset);
  15012. } else {
  15013. rng = bookmark;
  15014. }
  15015. return rng;
  15016. }
  15017. // Found tridentSel object then we need to use that one
  15018. if (w3c && self.tridentSel) {
  15019. return self.tridentSel.getRangeAt(0);
  15020. }
  15021. try {
  15022. if ((selection = self.getSel())) {
  15023. if (selection.rangeCount > 0) {
  15024. rng = selection.getRangeAt(0);
  15025. } else {
  15026. rng = selection.createRange ? selection.createRange() : doc.createRange();
  15027. }
  15028. }
  15029. } catch (ex) {
  15030. // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
  15031. }
  15032. evt = self.editor.fire('GetSelectionRange', {range: rng});
  15033. if (evt.range !== rng) {
  15034. return evt.range;
  15035. }
  15036. // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
  15037. // IE 11 doesn't support the selection object so we check for that as well
  15038. if (isIE && rng && rng.setStart && doc.selection) {
  15039. try {
  15040. // IE will sometimes throw an exception here
  15041. ieRng = doc.selection.createRange();
  15042. } catch (ex) {
  15043. // Ignore
  15044. }
  15045. if (ieRng && ieRng.item) {
  15046. elm = ieRng.item(0);
  15047. rng = doc.createRange();
  15048. rng.setStartBefore(elm);
  15049. rng.setEndAfter(elm);
  15050. }
  15051. }
  15052. // No range found then create an empty one
  15053. // This can occur when the editor is placed in a hidden container element on Gecko
  15054. // Or on IE when there was an exception
  15055. if (!rng) {
  15056. rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
  15057. }
  15058. // If range is at start of document then move it to start of body
  15059. if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
  15060. elm = self.dom.getRoot();
  15061. rng.setStart(elm, 0);
  15062. rng.setEnd(elm, 0);
  15063. }
  15064. if (self.selectedRange && self.explicitRange) {
  15065. if (tryCompareBoundaryPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
  15066. tryCompareBoundaryPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
  15067. // Safari, Opera and Chrome only ever select text which causes the range to change.
  15068. // This lets us use the originally set range if the selection hasn't been changed by the user.
  15069. rng = self.explicitRange;
  15070. } else {
  15071. self.selectedRange = null;
  15072. self.explicitRange = null;
  15073. }
  15074. }
  15075. return rng;
  15076. },
  15077. /**
  15078. * Changes the selection to the specified DOM range.
  15079. *
  15080. * @method setRng
  15081. * @param {Range} rng Range to select.
  15082. * @param {Boolean} forward Optional boolean if the selection is forwards or backwards.
  15083. */
  15084. setRng: function(rng, forward) {
  15085. var self = this, sel, node, evt;
  15086. if (!rng) {
  15087. return;
  15088. }
  15089. // Is IE specific range
  15090. if (rng.select) {
  15091. self.explicitRange = null;
  15092. try {
  15093. rng.select();
  15094. } catch (ex) {
  15095. // Needed for some odd IE bug #1843306
  15096. }
  15097. return;
  15098. }
  15099. if (!self.tridentSel) {
  15100. sel = self.getSel();
  15101. evt = self.editor.fire('SetSelectionRange', {range: rng});
  15102. rng = evt.range;
  15103. if (sel) {
  15104. self.explicitRange = rng;
  15105. try {
  15106. sel.removeAllRanges();
  15107. sel.addRange(rng);
  15108. } catch (ex) {
  15109. // IE might throw errors here if the editor is within a hidden container and selection is changed
  15110. }
  15111. // Forward is set to false and we have an extend function
  15112. if (forward === false && sel.extend) {
  15113. sel.collapse(rng.endContainer, rng.endOffset);
  15114. sel.extend(rng.startContainer, rng.startOffset);
  15115. }
  15116. // adding range isn't always successful so we need to check range count otherwise an exception can occur
  15117. self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
  15118. }
  15119. // WebKit egde case selecting images works better using setBaseAndExtent
  15120. if (!rng.collapsed && rng.startContainer == rng.endContainer && sel.setBaseAndExtent && !Env.ie) {
  15121. if (rng.endOffset - rng.startOffset < 2) {
  15122. if (rng.startContainer.hasChildNodes()) {
  15123. node = rng.startContainer.childNodes[rng.startOffset];
  15124. if (node && node.tagName == 'IMG') {
  15125. self.getSel().setBaseAndExtent(node, 0, node, 1);
  15126. }
  15127. }
  15128. }
  15129. }
  15130. } else {
  15131. // Is W3C Range fake range on IE
  15132. if (rng.cloneRange) {
  15133. try {
  15134. self.tridentSel.addRange(rng);
  15135. } catch (ex) {
  15136. //IE9 throws an error here if called before selection is placed in the editor
  15137. }
  15138. }
  15139. }
  15140. },
  15141. /**
  15142. * Sets the current selection to the specified DOM element.
  15143. *
  15144. * @method setNode
  15145. * @param {Element} elm Element to set as the contents of the selection.
  15146. * @return {Element} Returns the element that got passed in.
  15147. * @example
  15148. * // Inserts a DOM node at current selection/caret location
  15149. * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'}));
  15150. */
  15151. setNode: function(elm) {
  15152. var self = this;
  15153. self.setContent(self.dom.getOuterHTML(elm));
  15154. return elm;
  15155. },
  15156. /**
  15157. * Returns the currently selected element or the common ancestor element for both start and end of the selection.
  15158. *
  15159. * @method getNode
  15160. * @return {Element} Currently selected element or common ancestor element.
  15161. * @example
  15162. * // Alerts the currently selected elements node name
  15163. * alert(tinymce.activeEditor.selection.getNode().nodeName);
  15164. */
  15165. getNode: function() {
  15166. var self = this, rng = self.getRng(), elm;
  15167. var startContainer, endContainer, startOffset, endOffset, root = self.dom.getRoot();
  15168. function skipEmptyTextNodes(node, forwards) {
  15169. var orig = node;
  15170. while (node && node.nodeType === 3 && node.length === 0) {
  15171. node = forwards ? node.nextSibling : node.previousSibling;
  15172. }
  15173. return node || orig;
  15174. }
  15175. // Range maybe lost after the editor is made visible again
  15176. if (!rng) {
  15177. return root;
  15178. }
  15179. startContainer = rng.startContainer;
  15180. endContainer = rng.endContainer;
  15181. startOffset = rng.startOffset;
  15182. endOffset = rng.endOffset;
  15183. if (rng.setStart) {
  15184. elm = rng.commonAncestorContainer;
  15185. // Handle selection a image or other control like element such as anchors
  15186. if (!rng.collapsed) {
  15187. if (startContainer == endContainer) {
  15188. if (endOffset - startOffset < 2) {
  15189. if (startContainer.hasChildNodes()) {
  15190. elm = startContainer.childNodes[startOffset];
  15191. }
  15192. }
  15193. }
  15194. // If the anchor node is a element instead of a text node then return this element
  15195. //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
  15196. // return sel.anchorNode.childNodes[sel.anchorOffset];
  15197. // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
  15198. // This happens when you double click an underlined word in FireFox.
  15199. if (startContainer.nodeType === 3 && endContainer.nodeType === 3) {
  15200. if (startContainer.length === startOffset) {
  15201. startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
  15202. } else {
  15203. startContainer = startContainer.parentNode;
  15204. }
  15205. if (endOffset === 0) {
  15206. endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
  15207. } else {
  15208. endContainer = endContainer.parentNode;
  15209. }
  15210. if (startContainer && startContainer === endContainer) {
  15211. return startContainer;
  15212. }
  15213. }
  15214. }
  15215. if (elm && elm.nodeType == 3) {
  15216. return elm.parentNode;
  15217. }
  15218. return elm;
  15219. }
  15220. elm = rng.item ? rng.item(0) : rng.parentElement();
  15221. // IE 7 might return elements outside the iframe
  15222. if (elm.ownerDocument !== self.win.document) {
  15223. elm = root;
  15224. }
  15225. return elm;
  15226. },
  15227. getSelectedBlocks: function(startElm, endElm) {
  15228. var self = this, dom = self.dom, node, root, selectedBlocks = [];
  15229. root = dom.getRoot();
  15230. startElm = dom.getParent(startElm || self.getStart(), dom.isBlock);
  15231. endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock);
  15232. if (startElm && startElm != root) {
  15233. selectedBlocks.push(startElm);
  15234. }
  15235. if (startElm && endElm && startElm != endElm) {
  15236. node = startElm;
  15237. var walker = new TreeWalker(startElm, root);
  15238. while ((node = walker.next()) && node != endElm) {
  15239. if (dom.isBlock(node)) {
  15240. selectedBlocks.push(node);
  15241. }
  15242. }
  15243. }
  15244. if (endElm && startElm != endElm && endElm != root) {
  15245. selectedBlocks.push(endElm);
  15246. }
  15247. return selectedBlocks;
  15248. },
  15249. isForward: function() {
  15250. var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
  15251. // No support for selection direction then always return true
  15252. if (!sel || !sel.anchorNode || !sel.focusNode) {
  15253. return true;
  15254. }
  15255. anchorRange = dom.createRng();
  15256. anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
  15257. anchorRange.collapse(true);
  15258. focusRange = dom.createRng();
  15259. focusRange.setStart(sel.focusNode, sel.focusOffset);
  15260. focusRange.collapse(true);
  15261. return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
  15262. },
  15263. normalize: function() {
  15264. var self = this, rng = self.getRng();
  15265. if (Env.range && new RangeUtils(self.dom).normalize(rng)) {
  15266. self.setRng(rng, self.isForward());
  15267. }
  15268. return rng;
  15269. },
  15270. /**
  15271. * Executes callback when the current selection starts/stops matching the specified selector. The current
  15272. * state will be passed to the callback as it's first argument.
  15273. *
  15274. * @method selectorChanged
  15275. * @param {String} selector CSS selector to check for.
  15276. * @param {function} callback Callback with state and args when the selector is matches or not.
  15277. */
  15278. selectorChanged: function(selector, callback) {
  15279. var self = this, currentSelectors;
  15280. if (!self.selectorChangedData) {
  15281. self.selectorChangedData = {};
  15282. currentSelectors = {};
  15283. self.editor.on('NodeChange', function(e) {
  15284. var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
  15285. // Check for new matching selectors
  15286. each(self.selectorChangedData, function(callbacks, selector) {
  15287. each(parents, function(node) {
  15288. if (dom.is(node, selector)) {
  15289. if (!currentSelectors[selector]) {
  15290. // Execute callbacks
  15291. each(callbacks, function(callback) {
  15292. callback(true, {node: node, selector: selector, parents: parents});
  15293. });
  15294. currentSelectors[selector] = callbacks;
  15295. }
  15296. matchedSelectors[selector] = callbacks;
  15297. return false;
  15298. }
  15299. });
  15300. });
  15301. // Check if current selectors still match
  15302. each(currentSelectors, function(callbacks, selector) {
  15303. if (!matchedSelectors[selector]) {
  15304. delete currentSelectors[selector];
  15305. each(callbacks, function(callback) {
  15306. callback(false, {node: node, selector: selector, parents: parents});
  15307. });
  15308. }
  15309. });
  15310. });
  15311. }
  15312. // Add selector listeners
  15313. if (!self.selectorChangedData[selector]) {
  15314. self.selectorChangedData[selector] = [];
  15315. }
  15316. self.selectorChangedData[selector].push(callback);
  15317. return self;
  15318. },
  15319. getScrollContainer: function() {
  15320. var scrollContainer, node = this.dom.getRoot();
  15321. while (node && node.nodeName != 'BODY') {
  15322. if (node.scrollHeight > node.clientHeight) {
  15323. scrollContainer = node;
  15324. break;
  15325. }
  15326. node = node.parentNode;
  15327. }
  15328. return scrollContainer;
  15329. },
  15330. scrollIntoView: function(elm, alignToTop) {
  15331. var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH, offsetY = 0;
  15332. function getPos(elm) {
  15333. var x = 0, y = 0;
  15334. var offsetParent = elm;
  15335. while (offsetParent && offsetParent.nodeType) {
  15336. x += offsetParent.offsetLeft || 0;
  15337. y += offsetParent.offsetTop || 0;
  15338. offsetParent = offsetParent.offsetParent;
  15339. }
  15340. return {x: x, y: y};
  15341. }
  15342. if (!NodeType.isElement(elm)) {
  15343. return;
  15344. }
  15345. if (alignToTop === false) {
  15346. offsetY = elm.offsetHeight;
  15347. }
  15348. if (root.nodeName != 'BODY') {
  15349. var scrollContainer = self.getScrollContainer();
  15350. if (scrollContainer) {
  15351. y = getPos(elm).y - getPos(scrollContainer).y + offsetY;
  15352. viewPortH = scrollContainer.clientHeight;
  15353. viewPortY = scrollContainer.scrollTop;
  15354. if (y < viewPortY || y + 25 > viewPortY + viewPortH) {
  15355. scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25;
  15356. }
  15357. return;
  15358. }
  15359. }
  15360. viewPort = dom.getViewPort(self.editor.getWin());
  15361. y = dom.getPos(elm).y + offsetY;
  15362. viewPortY = viewPort.y;
  15363. viewPortH = viewPort.h;
  15364. if (y < viewPort.y || y + 25 > viewPortY + viewPortH) {
  15365. self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25);
  15366. }
  15367. },
  15368. placeCaretAt: function(clientX, clientY) {
  15369. this.setRng(RangeUtils.getCaretRangeFromPoint(clientX, clientY, this.editor.getDoc()));
  15370. },
  15371. _moveEndPoint: function(rng, node, start) {
  15372. var root = node, walker = new TreeWalker(node, root);
  15373. var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements();
  15374. do {
  15375. // Text node
  15376. if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
  15377. if (start) {
  15378. rng.setStart(node, 0);
  15379. } else {
  15380. rng.setEnd(node, node.nodeValue.length);
  15381. }
  15382. return;
  15383. }
  15384. // BR/IMG/INPUT elements but not table cells
  15385. if (nonEmptyElementsMap[node.nodeName] && !/^(TD|TH)$/.test(node.nodeName)) {
  15386. if (start) {
  15387. rng.setStartBefore(node);
  15388. } else {
  15389. if (node.nodeName == 'BR') {
  15390. rng.setEndBefore(node);
  15391. } else {
  15392. rng.setEndAfter(node);
  15393. }
  15394. }
  15395. return;
  15396. }
  15397. // Found empty text block old IE can place the selection inside those
  15398. if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) {
  15399. if (start) {
  15400. rng.setStart(node, 0);
  15401. } else {
  15402. rng.setEnd(node, 0);
  15403. }
  15404. return;
  15405. }
  15406. } while ((node = (start ? walker.next() : walker.prev())));
  15407. // Failed to find any text node or other suitable location then move to the root of body
  15408. if (root.nodeName == 'BODY') {
  15409. if (start) {
  15410. rng.setStart(root, 0);
  15411. } else {
  15412. rng.setEnd(root, root.childNodes.length);
  15413. }
  15414. }
  15415. },
  15416. getBoundingClientRect: function() {
  15417. var rng = this.getRng();
  15418. return rng.collapsed ? CaretPosition.fromRangeStart(rng).getClientRects()[0] : rng.getBoundingClientRect();
  15419. },
  15420. destroy: function() {
  15421. this.win = null;
  15422. this.controlSelection.destroy();
  15423. }
  15424. };
  15425. return Selection;
  15426. });
  15427. // Included from: js/tinymce/classes/dom/ElementUtils.js
  15428. /**
  15429. * ElementUtils.js
  15430. *
  15431. * Released under LGPL License.
  15432. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  15433. *
  15434. * License: http://www.tinymce.com/license
  15435. * Contributing: http://www.tinymce.com/contributing
  15436. */
  15437. /**
  15438. * Utility class for various element specific functions.
  15439. *
  15440. * @private
  15441. * @class tinymce.dom.ElementUtils
  15442. */
  15443. define("tinymce/dom/ElementUtils", [
  15444. "tinymce/dom/BookmarkManager",
  15445. "tinymce/util/Tools"
  15446. ], function(BookmarkManager, Tools) {
  15447. var each = Tools.each;
  15448. function ElementUtils(dom) {
  15449. /**
  15450. * Compares two nodes and checks if it's attributes and styles matches.
  15451. * This doesn't compare classes as items since their order is significant.
  15452. *
  15453. * @method compare
  15454. * @param {Node} node1 First node to compare with.
  15455. * @param {Node} node2 Second node to compare with.
  15456. * @return {boolean} True/false if the nodes are the same or not.
  15457. */
  15458. this.compare = function(node1, node2) {
  15459. // Not the same name
  15460. if (node1.nodeName != node2.nodeName) {
  15461. return false;
  15462. }
  15463. /**
  15464. * Returns all the nodes attributes excluding internal ones, styles and classes.
  15465. *
  15466. * @private
  15467. * @param {Node} node Node to get attributes from.
  15468. * @return {Object} Name/value object with attributes and attribute values.
  15469. */
  15470. function getAttribs(node) {
  15471. var attribs = {};
  15472. each(dom.getAttribs(node), function(attr) {
  15473. var name = attr.nodeName.toLowerCase();
  15474. // Don't compare internal attributes or style
  15475. if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style' && name != 'data-mce-fragment') {
  15476. attribs[name] = dom.getAttrib(node, name);
  15477. }
  15478. });
  15479. return attribs;
  15480. }
  15481. /**
  15482. * Compares two objects checks if it's key + value exists in the other one.
  15483. *
  15484. * @private
  15485. * @param {Object} obj1 First object to compare.
  15486. * @param {Object} obj2 Second object to compare.
  15487. * @return {boolean} True/false if the objects matches or not.
  15488. */
  15489. function compareObjects(obj1, obj2) {
  15490. var value, name;
  15491. for (name in obj1) {
  15492. // Obj1 has item obj2 doesn't have
  15493. if (obj1.hasOwnProperty(name)) {
  15494. value = obj2[name];
  15495. // Obj2 doesn't have obj1 item
  15496. if (typeof value == "undefined") {
  15497. return false;
  15498. }
  15499. // Obj2 item has a different value
  15500. if (obj1[name] != value) {
  15501. return false;
  15502. }
  15503. // Delete similar value
  15504. delete obj2[name];
  15505. }
  15506. }
  15507. // Check if obj 2 has something obj 1 doesn't have
  15508. for (name in obj2) {
  15509. // Obj2 has item obj1 doesn't have
  15510. if (obj2.hasOwnProperty(name)) {
  15511. return false;
  15512. }
  15513. }
  15514. return true;
  15515. }
  15516. // Attribs are not the same
  15517. if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
  15518. return false;
  15519. }
  15520. // Styles are not the same
  15521. if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
  15522. return false;
  15523. }
  15524. return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
  15525. };
  15526. }
  15527. return ElementUtils;
  15528. });
  15529. // Included from: js/tinymce/classes/fmt/Preview.js
  15530. /**
  15531. * Preview.js
  15532. *
  15533. * Released under LGPL License.
  15534. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  15535. *
  15536. * License: http://www.tinymce.com/license
  15537. * Contributing: http://www.tinymce.com/contributing
  15538. */
  15539. /**
  15540. * Internal class for generating previews styles for formats.
  15541. *
  15542. * Example:
  15543. * Preview.getCssText(editor, 'bold');
  15544. *
  15545. * @private
  15546. * @class tinymce.fmt.Preview
  15547. */
  15548. define("tinymce/fmt/Preview", [
  15549. "tinymce/util/Tools"
  15550. ], function(Tools) {
  15551. var each = Tools.each;
  15552. function getCssText(editor, format) {
  15553. var name, previewElm, dom = editor.dom;
  15554. var previewCss = '', parentFontSize, previewStyles;
  15555. previewStyles = editor.settings.preview_styles;
  15556. // No preview forced
  15557. if (previewStyles === false) {
  15558. return '';
  15559. }
  15560. // Default preview
  15561. if (!previewStyles) {
  15562. previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
  15563. 'text-transform color background-color border border-radius outline text-shadow';
  15564. }
  15565. // Removes any variables since these can't be previewed
  15566. function removeVars(val) {
  15567. return val.replace(/%(\w+)/g, '');
  15568. }
  15569. // Create block/inline element to use for preview
  15570. if (typeof format == "string") {
  15571. format = editor.formatter.get(format);
  15572. if (!format) {
  15573. return;
  15574. }
  15575. format = format[0];
  15576. }
  15577. name = format.block || format.inline || 'span';
  15578. previewElm = dom.create(name);
  15579. // Add format styles to preview element
  15580. each(format.styles, function(value, name) {
  15581. value = removeVars(value);
  15582. if (value) {
  15583. dom.setStyle(previewElm, name, value);
  15584. }
  15585. });
  15586. // Add attributes to preview element
  15587. each(format.attributes, function(value, name) {
  15588. value = removeVars(value);
  15589. if (value) {
  15590. dom.setAttrib(previewElm, name, value);
  15591. }
  15592. });
  15593. // Add classes to preview element
  15594. each(format.classes, function(value) {
  15595. value = removeVars(value);
  15596. if (!dom.hasClass(previewElm, value)) {
  15597. dom.addClass(previewElm, value);
  15598. }
  15599. });
  15600. editor.fire('PreviewFormats');
  15601. // Add the previewElm outside the visual area
  15602. dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
  15603. editor.getBody().appendChild(previewElm);
  15604. // Get parent container font size so we can compute px values out of em/% for older IE:s
  15605. parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
  15606. parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
  15607. each(previewStyles.split(' '), function(name) {
  15608. var value = dom.getStyle(previewElm, name, true);
  15609. // If background is transparent then check if the body has a background color we can use
  15610. if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
  15611. value = dom.getStyle(editor.getBody(), name, true);
  15612. // Ignore white since it's the default color, not the nicest fix
  15613. // TODO: Fix this by detecting runtime style
  15614. if (dom.toHex(value).toLowerCase() == '#ffffff') {
  15615. return;
  15616. }
  15617. }
  15618. if (name == 'color') {
  15619. // Ignore black since it's the default color, not the nicest fix
  15620. // TODO: Fix this by detecting runtime style
  15621. if (dom.toHex(value).toLowerCase() == '#000000') {
  15622. return;
  15623. }
  15624. }
  15625. // Old IE won't calculate the font size so we need to do that manually
  15626. if (name == 'font-size') {
  15627. if (/em|%$/.test(value)) {
  15628. if (parentFontSize === 0) {
  15629. return;
  15630. }
  15631. // Convert font size from em/% to px
  15632. value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
  15633. value = (value * parentFontSize) + 'px';
  15634. }
  15635. }
  15636. if (name == "border" && value) {
  15637. previewCss += 'padding:0 2px;';
  15638. }
  15639. previewCss += name + ':' + value + ';';
  15640. });
  15641. editor.fire('AfterPreviewFormats');
  15642. //previewCss += 'line-height:normal';
  15643. dom.remove(previewElm);
  15644. return previewCss;
  15645. }
  15646. return {
  15647. getCssText: getCssText
  15648. };
  15649. });
  15650. // Included from: js/tinymce/classes/fmt/Hooks.js
  15651. /**
  15652. * Hooks.js
  15653. *
  15654. * Released under LGPL License.
  15655. * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
  15656. *
  15657. * License: http://www.tinymce.com/license
  15658. * Contributing: http://www.tinymce.com/contributing
  15659. */
  15660. /**
  15661. * Internal class for overriding formatting.
  15662. *
  15663. * @private
  15664. * @class tinymce.fmt.Hooks
  15665. */
  15666. define("tinymce/fmt/Hooks", [
  15667. "tinymce/util/Arr",
  15668. "tinymce/dom/NodeType",
  15669. "tinymce/dom/DomQuery"
  15670. ], function(Arr, NodeType, $) {
  15671. var postProcessHooks = [], filter = Arr.filter, each = Arr.each;
  15672. function addPostProcessHook(name, hook) {
  15673. var hooks = postProcessHooks[name];
  15674. if (!hooks) {
  15675. postProcessHooks[name] = hooks = [];
  15676. }
  15677. postProcessHooks[name].push(hook);
  15678. }
  15679. function postProcess(name, editor) {
  15680. each(postProcessHooks[name], function(hook) {
  15681. hook(editor);
  15682. });
  15683. }
  15684. addPostProcessHook("pre", function(editor) {
  15685. var rng = editor.selection.getRng(), isPre, blocks;
  15686. function hasPreSibling(pre) {
  15687. return isPre(pre.previousSibling) && Arr.indexOf(blocks, pre.previousSibling) != -1;
  15688. }
  15689. function joinPre(pre1, pre2) {
  15690. $(pre2).remove();
  15691. $(pre1).append('<br><br>').append(pre2.childNodes);
  15692. }
  15693. isPre = NodeType.matchNodeNames('pre');
  15694. if (!rng.collapsed) {
  15695. blocks = editor.selection.getSelectedBlocks();
  15696. each(filter(filter(blocks, isPre), hasPreSibling), function(pre) {
  15697. joinPre(pre.previousSibling, pre);
  15698. });
  15699. }
  15700. });
  15701. return {
  15702. postProcess: postProcess
  15703. };
  15704. });
  15705. // Included from: js/tinymce/classes/Formatter.js
  15706. /**
  15707. * Formatter.js
  15708. *
  15709. * Released under LGPL License.
  15710. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  15711. *
  15712. * License: http://www.tinymce.com/license
  15713. * Contributing: http://www.tinymce.com/contributing
  15714. */
  15715. /**
  15716. * Text formatter engine class. This class is used to apply formats like bold, italic, font size
  15717. * etc to the current selection or specific nodes. This engine was built to replace the browser's
  15718. * default formatting logic for execCommand due to its inconsistent and buggy behavior.
  15719. *
  15720. * @class tinymce.Formatter
  15721. * @example
  15722. * tinymce.activeEditor.formatter.register('mycustomformat', {
  15723. * inline: 'span',
  15724. * styles: {color: '#ff0000'}
  15725. * });
  15726. *
  15727. * tinymce.activeEditor.formatter.apply('mycustomformat');
  15728. */
  15729. define("tinymce/Formatter", [
  15730. "tinymce/dom/TreeWalker",
  15731. "tinymce/dom/RangeUtils",
  15732. "tinymce/dom/BookmarkManager",
  15733. "tinymce/dom/ElementUtils",
  15734. "tinymce/util/Tools",
  15735. "tinymce/fmt/Preview",
  15736. "tinymce/fmt/Hooks"
  15737. ], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview, Hooks) {
  15738. /**
  15739. * Constructs a new formatter instance.
  15740. *
  15741. * @constructor Formatter
  15742. * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to.
  15743. */
  15744. return function(ed) {
  15745. var formats = {},
  15746. dom = ed.dom,
  15747. selection = ed.selection,
  15748. rangeUtils = new RangeUtils(dom),
  15749. isValid = ed.schema.isValidChild,
  15750. isBlock = dom.isBlock,
  15751. forcedRootBlock = ed.settings.forced_root_block,
  15752. nodeIndex = dom.nodeIndex,
  15753. INVISIBLE_CHAR = '\uFEFF',
  15754. MCE_ATTR_RE = /^(src|href|style)$/,
  15755. FALSE = false,
  15756. TRUE = true,
  15757. formatChangeData,
  15758. undef,
  15759. getContentEditable = dom.getContentEditable,
  15760. disableCaretContainer,
  15761. markCaretContainersBogus,
  15762. isBookmarkNode = BookmarkManager.isBookmarkNode;
  15763. var each = Tools.each,
  15764. grep = Tools.grep,
  15765. walk = Tools.walk,
  15766. extend = Tools.extend;
  15767. function isTextBlock(name) {
  15768. if (name.nodeType) {
  15769. name = name.nodeName;
  15770. }
  15771. return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
  15772. }
  15773. function isTableCell(node) {
  15774. return /^(TH|TD)$/.test(node.nodeName);
  15775. }
  15776. function isInlineBlock(node) {
  15777. return node && /^(IMG)$/.test(node.nodeName);
  15778. }
  15779. function getParents(node, selector) {
  15780. return dom.getParents(node, selector, dom.getRoot());
  15781. }
  15782. function isCaretNode(node) {
  15783. return node.nodeType === 1 && node.id === '_mce_caret';
  15784. }
  15785. function defaultFormats() {
  15786. register({
  15787. valigntop: [
  15788. {selector: 'td,th', styles: {'verticalAlign': 'top'}}
  15789. ],
  15790. valignmiddle: [
  15791. {selector: 'td,th', styles: {'verticalAlign': 'middle'}}
  15792. ],
  15793. valignbottom: [
  15794. {selector: 'td,th', styles: {'verticalAlign': 'bottom'}}
  15795. ],
  15796. alignleft: [
  15797. {selector: 'figure.image', collapsed: false, classes: 'align-left', ceFalseOverride: true},
  15798. {
  15799. selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
  15800. styles: {
  15801. textAlign: 'left'
  15802. },
  15803. inherit: false,
  15804. defaultBlock: 'div'
  15805. },
  15806. {selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
  15807. ],
  15808. aligncenter: [
  15809. {
  15810. selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
  15811. styles: {
  15812. textAlign: 'center'
  15813. },
  15814. inherit: false,
  15815. defaultBlock: 'div'
  15816. },
  15817. {selector: 'figure.image', collapsed: false, classes: 'align-center', ceFalseOverride: true},
  15818. {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
  15819. {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
  15820. ],
  15821. alignright: [
  15822. {selector: 'figure.image', collapsed: false, classes: 'align-right', ceFalseOverride: true},
  15823. {
  15824. selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
  15825. styles: {
  15826. textAlign: 'right'
  15827. },
  15828. inherit: false,
  15829. defaultBlock: 'div'
  15830. },
  15831. {selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
  15832. ],
  15833. alignjustify: [
  15834. {
  15835. selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
  15836. styles: {
  15837. textAlign: 'justify'
  15838. },
  15839. inherit: false,
  15840. defaultBlock: 'div'
  15841. }
  15842. ],
  15843. bold: [
  15844. {inline: 'strong', remove: 'all'},
  15845. {inline: 'span', styles: {fontWeight: 'bold'}},
  15846. {inline: 'b', remove: 'all'}
  15847. ],
  15848. italic: [
  15849. {inline: 'em', remove: 'all'},
  15850. {inline: 'span', styles: {fontStyle: 'italic'}},
  15851. {inline: 'i', remove: 'all'}
  15852. ],
  15853. underline: [
  15854. {inline: 'span', styles: {textDecoration: 'underline'}, exact: true},
  15855. {inline: 'u', remove: 'all'}
  15856. ],
  15857. strikethrough: [
  15858. {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true},
  15859. {inline: 'strike', remove: 'all'}
  15860. ],
  15861. forecolor: {inline: 'span', styles: {color: '%value'}, links: true, remove_similar: true},
  15862. hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, links: true, remove_similar: true},
  15863. fontname: {inline: 'span', styles: {fontFamily: '%value'}},
  15864. fontsize: {inline: 'span', styles: {fontSize: '%value'}},
  15865. fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
  15866. blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'},
  15867. subscript: {inline: 'sub'},
  15868. superscript: {inline: 'sup'},
  15869. code: {inline: 'code'},
  15870. link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
  15871. onmatch: function() {
  15872. return true;
  15873. },
  15874. onformat: function(elm, fmt, vars) {
  15875. each(vars, function(value, key) {
  15876. dom.setAttrib(elm, key, value);
  15877. });
  15878. }
  15879. },
  15880. removeformat: [
  15881. {
  15882. selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins',
  15883. remove: 'all',
  15884. split: true,
  15885. expand: false,
  15886. block_expand: true,
  15887. deep: true
  15888. },
  15889. {selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true},
  15890. {selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true}
  15891. ]
  15892. });
  15893. // Register default block formats
  15894. each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) {
  15895. register(name, {block: name, remove: 'all'});
  15896. });
  15897. // Register user defined formats
  15898. register(ed.settings.formats);
  15899. }
  15900. function addKeyboardShortcuts() {
  15901. // Add some inline shortcuts
  15902. ed.addShortcut('meta+b', 'bold_desc', 'Bold');
  15903. ed.addShortcut('meta+i', 'italic_desc', 'Italic');
  15904. ed.addShortcut('meta+u', 'underline_desc', 'Underline');
  15905. // BlockFormat shortcuts keys
  15906. for (var i = 1; i <= 6; i++) {
  15907. ed.addShortcut('access+' + i, '', ['FormatBlock', false, 'h' + i]);
  15908. }
  15909. ed.addShortcut('access+7', '', ['FormatBlock', false, 'p']);
  15910. ed.addShortcut('access+8', '', ['FormatBlock', false, 'div']);
  15911. ed.addShortcut('access+9', '', ['FormatBlock', false, 'address']);
  15912. }
  15913. // Public functions
  15914. /**
  15915. * Returns the format by name or all formats if no name is specified.
  15916. *
  15917. * @method get
  15918. * @param {String} name Optional name to retrieve by.
  15919. * @return {Array/Object} Array/Object with all registered formats or a specific format.
  15920. */
  15921. function get(name) {
  15922. return name ? formats[name] : formats;
  15923. }
  15924. /**
  15925. * Registers a specific format by name.
  15926. *
  15927. * @method register
  15928. * @param {Object/String} name Name of the format for example "bold".
  15929. * @param {Object/Array} format Optional format object or array of format variants
  15930. * can only be omitted if the first arg is an object.
  15931. */
  15932. function register(name, format) {
  15933. if (name) {
  15934. if (typeof name !== 'string') {
  15935. each(name, function(format, name) {
  15936. register(name, format);
  15937. });
  15938. } else {
  15939. // Force format into array and add it to internal collection
  15940. format = format.length ? format : [format];
  15941. each(format, function(format) {
  15942. // Set deep to false by default on selector formats this to avoid removing
  15943. // alignment on images inside paragraphs when alignment is changed on paragraphs
  15944. if (format.deep === undef) {
  15945. format.deep = !format.selector;
  15946. }
  15947. // Default to true
  15948. if (format.split === undef) {
  15949. format.split = !format.selector || format.inline;
  15950. }
  15951. // Default to true
  15952. if (format.remove === undef && format.selector && !format.inline) {
  15953. format.remove = 'none';
  15954. }
  15955. // Mark format as a mixed format inline + block level
  15956. if (format.selector && format.inline) {
  15957. format.mixed = true;
  15958. format.block_expand = true;
  15959. }
  15960. // Split classes if needed
  15961. if (typeof format.classes === 'string') {
  15962. format.classes = format.classes.split(/\s+/);
  15963. }
  15964. });
  15965. formats[name] = format;
  15966. }
  15967. }
  15968. }
  15969. /**
  15970. * Unregister a specific format by name.
  15971. *
  15972. * @method unregister
  15973. * @param {String} name Name of the format for example "bold".
  15974. */
  15975. function unregister(name) {
  15976. if (name && formats[name]) {
  15977. delete formats[name];
  15978. }
  15979. return formats;
  15980. }
  15981. function matchesUnInheritedFormatSelector(node, name) {
  15982. var formatList = get(name);
  15983. if (formatList) {
  15984. for (var i = 0; i < formatList.length; i++) {
  15985. if (formatList[i].inherit === false && dom.is(node, formatList[i].selector)) {
  15986. return true;
  15987. }
  15988. }
  15989. }
  15990. return false;
  15991. }
  15992. function getTextDecoration(node) {
  15993. var decoration;
  15994. ed.dom.getParent(node, function(n) {
  15995. decoration = ed.dom.getStyle(n, 'text-decoration');
  15996. return decoration && decoration !== 'none';
  15997. });
  15998. return decoration;
  15999. }
  16000. function processUnderlineAndColor(node) {
  16001. var textDecoration;
  16002. if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
  16003. textDecoration = getTextDecoration(node.parentNode);
  16004. if (ed.dom.getStyle(node, 'color') && textDecoration) {
  16005. ed.dom.setStyle(node, 'text-decoration', textDecoration);
  16006. } else if (ed.dom.getStyle(node, 'text-decoration') === textDecoration) {
  16007. ed.dom.setStyle(node, 'text-decoration', null);
  16008. }
  16009. }
  16010. }
  16011. /**
  16012. * Applies the specified format to the current selection or specified node.
  16013. *
  16014. * @method apply
  16015. * @param {String} name Name of format to apply.
  16016. * @param {Object} vars Optional list of variables to replace within format before applying it.
  16017. * @param {Node} node Optional node to apply the format to defaults to current selection.
  16018. */
  16019. function apply(name, vars, node) {
  16020. var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed();
  16021. function setElementFormat(elm, fmt) {
  16022. fmt = fmt || format;
  16023. if (elm) {
  16024. if (fmt.onformat) {
  16025. fmt.onformat(elm, fmt, vars, node);
  16026. }
  16027. each(fmt.styles, function(value, name) {
  16028. dom.setStyle(elm, name, replaceVars(value, vars));
  16029. });
  16030. // Needed for the WebKit span spam bug
  16031. // TODO: Remove this once WebKit/Blink fixes this
  16032. if (fmt.styles) {
  16033. var styleVal = dom.getAttrib(elm, 'style');
  16034. if (styleVal) {
  16035. elm.setAttribute('data-mce-style', styleVal);
  16036. }
  16037. }
  16038. each(fmt.attributes, function(value, name) {
  16039. dom.setAttrib(elm, name, replaceVars(value, vars));
  16040. });
  16041. each(fmt.classes, function(value) {
  16042. value = replaceVars(value, vars);
  16043. if (!dom.hasClass(elm, value)) {
  16044. dom.addClass(elm, value);
  16045. }
  16046. });
  16047. }
  16048. }
  16049. // This converts: <p>[a</p><p>]b</p> -> <p>[a]</p><p>b</p>
  16050. function adjustSelectionToVisibleSelection() {
  16051. function findSelectionEnd(start, end) {
  16052. var walker = new TreeWalker(end);
  16053. for (node = walker.prev2(); node; node = walker.prev2()) {
  16054. if (node.nodeType == 3 && node.data.length > 0) {
  16055. return node;
  16056. }
  16057. if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
  16058. return node;
  16059. }
  16060. }
  16061. }
  16062. // Adjust selection so that a end container with a end offset of zero is not included in the selection
  16063. // as this isn't visible to the user.
  16064. var rng = ed.selection.getRng();
  16065. var start = rng.startContainer;
  16066. var end = rng.endContainer;
  16067. if (start != end && rng.endOffset === 0) {
  16068. var newEnd = findSelectionEnd(start, end);
  16069. var endOffset = newEnd.nodeType == 3 ? newEnd.data.length : newEnd.childNodes.length;
  16070. rng.setEnd(newEnd, endOffset);
  16071. }
  16072. return rng;
  16073. }
  16074. function applyRngStyle(rng, bookmark, node_specific) {
  16075. var newWrappers = [], wrapName, wrapElm, contentEditable = true;
  16076. // Setup wrapper element
  16077. wrapName = format.inline || format.block;
  16078. wrapElm = dom.create(wrapName);
  16079. setElementFormat(wrapElm);
  16080. rangeUtils.walk(rng, function(nodes) {
  16081. var currentWrapElm;
  16082. /**
  16083. * Process a list of nodes wrap them.
  16084. */
  16085. function process(node) {
  16086. var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
  16087. lastContentEditable = contentEditable;
  16088. nodeName = node.nodeName.toLowerCase();
  16089. parentName = node.parentNode.nodeName.toLowerCase();
  16090. // Node has a contentEditable value
  16091. if (node.nodeType === 1 && getContentEditable(node)) {
  16092. lastContentEditable = contentEditable;
  16093. contentEditable = getContentEditable(node) === "true";
  16094. hasContentEditableState = true; // We don't want to wrap the container only it's children
  16095. }
  16096. // Stop wrapping on br elements
  16097. if (isEq(nodeName, 'br')) {
  16098. currentWrapElm = 0;
  16099. // Remove any br elements when we wrap things
  16100. if (format.block) {
  16101. dom.remove(node);
  16102. }
  16103. return;
  16104. }
  16105. // If node is wrapper type
  16106. if (format.wrapper && matchNode(node, name, vars)) {
  16107. currentWrapElm = 0;
  16108. return;
  16109. }
  16110. // Can we rename the block
  16111. // TODO: Break this if up, too complex
  16112. if (contentEditable && !hasContentEditableState && format.block &&
  16113. !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) {
  16114. node = dom.rename(node, wrapName);
  16115. setElementFormat(node);
  16116. newWrappers.push(node);
  16117. currentWrapElm = 0;
  16118. return;
  16119. }
  16120. // Handle selector patterns
  16121. if (format.selector) {
  16122. // Look for matching formats
  16123. each(formatList, function(format) {
  16124. // Check collapsed state if it exists
  16125. if ('collapsed' in format && format.collapsed !== isCollapsed) {
  16126. return;
  16127. }
  16128. if (dom.is(node, format.selector) && !isCaretNode(node)) {
  16129. setElementFormat(node, format);
  16130. found = true;
  16131. return false;
  16132. }
  16133. });
  16134. // Continue processing if a selector match wasn't found and a inline element is defined
  16135. if (!format.inline || found) {
  16136. currentWrapElm = 0;
  16137. return;
  16138. }
  16139. }
  16140. // Is it valid to wrap this item
  16141. // TODO: Break this if up, too complex
  16142. if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
  16143. !(!node_specific && node.nodeType === 3 &&
  16144. node.nodeValue.length === 1 &&
  16145. node.nodeValue.charCodeAt(0) === 65279) &&
  16146. !isCaretNode(node) &&
  16147. (!format.inline || !isBlock(node))) {
  16148. // Start wrapping
  16149. if (!currentWrapElm) {
  16150. // Wrap the node
  16151. currentWrapElm = dom.clone(wrapElm, FALSE);
  16152. node.parentNode.insertBefore(currentWrapElm, node);
  16153. newWrappers.push(currentWrapElm);
  16154. }
  16155. currentWrapElm.appendChild(node);
  16156. } else {
  16157. // Start a new wrapper for possible children
  16158. currentWrapElm = 0;
  16159. each(grep(node.childNodes), process);
  16160. if (hasContentEditableState) {
  16161. contentEditable = lastContentEditable; // Restore last contentEditable state from stack
  16162. }
  16163. // End the last wrapper
  16164. currentWrapElm = 0;
  16165. }
  16166. }
  16167. // Process siblings from range
  16168. each(nodes, process);
  16169. });
  16170. // Apply formats to links as well to get the color of the underline to change as well
  16171. if (format.links === true) {
  16172. each(newWrappers, function(node) {
  16173. function process(node) {
  16174. if (node.nodeName === 'A') {
  16175. setElementFormat(node, format);
  16176. }
  16177. each(grep(node.childNodes), process);
  16178. }
  16179. process(node);
  16180. });
  16181. }
  16182. // Cleanup
  16183. each(newWrappers, function(node) {
  16184. var childCount;
  16185. function getChildCount(node) {
  16186. var count = 0;
  16187. each(node.childNodes, function(node) {
  16188. if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) {
  16189. count++;
  16190. }
  16191. });
  16192. return count;
  16193. }
  16194. function mergeStyles(node) {
  16195. var child, clone;
  16196. each(node.childNodes, function(node) {
  16197. if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
  16198. child = node;
  16199. return FALSE; // break loop
  16200. }
  16201. });
  16202. // If child was found and of the same type as the current node
  16203. if (child && !isBookmarkNode(child) && matchName(child, format)) {
  16204. clone = dom.clone(child, FALSE);
  16205. setElementFormat(clone);
  16206. dom.replace(clone, node, TRUE);
  16207. dom.remove(child, 1);
  16208. }
  16209. return clone || node;
  16210. }
  16211. childCount = getChildCount(node);
  16212. // Remove empty nodes but only if there is multiple wrappers and they are not block
  16213. // elements so never remove single <h1></h1> since that would remove the
  16214. // current empty block element where the caret is at
  16215. if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
  16216. dom.remove(node, 1);
  16217. return;
  16218. }
  16219. if (format.inline || format.wrapper) {
  16220. // Merges the current node with it's children of similar type to reduce the number of elements
  16221. if (!format.exact && childCount === 1) {
  16222. node = mergeStyles(node);
  16223. }
  16224. // Remove/merge children
  16225. each(formatList, function(format) {
  16226. // Merge all children of similar type will move styles from child to parent
  16227. // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
  16228. // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
  16229. each(dom.select(format.inline, node), function(child) {
  16230. if (isBookmarkNode(child)) {
  16231. return;
  16232. }
  16233. removeFormat(format, vars, child, format.exact ? child : null);
  16234. });
  16235. });
  16236. // Remove child if direct parent is of same type
  16237. if (matchNode(node.parentNode, name, vars)) {
  16238. dom.remove(node, 1);
  16239. node = 0;
  16240. return TRUE;
  16241. }
  16242. // Look for parent with similar style format
  16243. if (format.merge_with_parents) {
  16244. dom.getParent(node.parentNode, function(parent) {
  16245. if (matchNode(parent, name, vars)) {
  16246. dom.remove(node, 1);
  16247. node = 0;
  16248. return TRUE;
  16249. }
  16250. });
  16251. }
  16252. // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
  16253. if (node && format.merge_siblings !== false) {
  16254. node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
  16255. node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
  16256. }
  16257. }
  16258. });
  16259. }
  16260. if (getContentEditable(selection.getNode()) === "false") {
  16261. node = selection.getNode();
  16262. for (var i = 0, l = formatList.length; i < l; i++) {
  16263. if (formatList[i].ceFalseOverride && dom.is(node, formatList[i].selector)) {
  16264. setElementFormat(node, formatList[i]);
  16265. return;
  16266. }
  16267. }
  16268. return;
  16269. }
  16270. if (format) {
  16271. if (node) {
  16272. if (node.nodeType) {
  16273. rng = dom.createRng();
  16274. rng.setStartBefore(node);
  16275. rng.setEndAfter(node);
  16276. applyRngStyle(expandRng(rng, formatList), null, true);
  16277. } else {
  16278. applyRngStyle(node, null, true);
  16279. }
  16280. } else {
  16281. if (!isCollapsed || !format.inline || dom.select('td[data-mce-selected],th[data-mce-selected]').length) {
  16282. // Obtain selection node before selection is unselected by applyRngStyle()
  16283. var curSelNode = ed.selection.getNode();
  16284. // If the formats have a default block and we can't find a parent block then
  16285. // start wrapping it with a DIV this is for forced_root_blocks: false
  16286. // It's kind of a hack but people should be using the default block type P since all desktop editors work that way
  16287. if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
  16288. apply(formatList[0].defaultBlock);
  16289. }
  16290. // Apply formatting to selection
  16291. ed.selection.setRng(adjustSelectionToVisibleSelection());
  16292. bookmark = selection.getBookmark();
  16293. applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
  16294. // Colored nodes should be underlined so that the color of the underline matches the text color.
  16295. if (format.styles && (format.styles.color || format.styles.textDecoration)) {
  16296. walk(curSelNode, processUnderlineAndColor, 'childNodes');
  16297. processUnderlineAndColor(curSelNode);
  16298. }
  16299. selection.moveToBookmark(bookmark);
  16300. moveStart(selection.getRng(TRUE));
  16301. ed.nodeChanged();
  16302. } else {
  16303. performCaretAction('apply', name, vars);
  16304. }
  16305. }
  16306. Hooks.postProcess(name, ed);
  16307. }
  16308. }
  16309. /**
  16310. * Removes the specified format from the current selection or specified node.
  16311. *
  16312. * @method remove
  16313. * @param {String} name Name of format to remove.
  16314. * @param {Object} vars Optional list of variables to replace within format before removing it.
  16315. * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
  16316. */
  16317. function remove(name, vars, node, similar) {
  16318. var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;
  16319. // Merges the styles for each node
  16320. function process(node) {
  16321. var children, i, l, lastContentEditable, hasContentEditableState;
  16322. // Node has a contentEditable value
  16323. if (node.nodeType === 1 && getContentEditable(node)) {
  16324. lastContentEditable = contentEditable;
  16325. contentEditable = getContentEditable(node) === "true";
  16326. hasContentEditableState = true; // We don't want to wrap the container only it's children
  16327. }
  16328. // Grab the children first since the nodelist might be changed
  16329. children = grep(node.childNodes);
  16330. // Process current node
  16331. if (contentEditable && !hasContentEditableState) {
  16332. for (i = 0, l = formatList.length; i < l; i++) {
  16333. if (removeFormat(formatList[i], vars, node, node)) {
  16334. break;
  16335. }
  16336. }
  16337. }
  16338. // Process the children
  16339. if (format.deep) {
  16340. if (children.length) {
  16341. for (i = 0, l = children.length; i < l; i++) {
  16342. process(children[i]);
  16343. }
  16344. if (hasContentEditableState) {
  16345. contentEditable = lastContentEditable; // Restore last contentEditable state from stack
  16346. }
  16347. }
  16348. }
  16349. }
  16350. function findFormatRoot(container) {
  16351. var formatRoot;
  16352. // Find format root
  16353. each(getParents(container.parentNode).reverse(), function(parent) {
  16354. var format;
  16355. // Find format root element
  16356. if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
  16357. // Is the node matching the format we are looking for
  16358. format = matchNode(parent, name, vars, similar);
  16359. if (format && format.split !== false) {
  16360. formatRoot = parent;
  16361. }
  16362. }
  16363. });
  16364. return formatRoot;
  16365. }
  16366. function wrapAndSplit(formatRoot, container, target, split) {
  16367. var parent, clone, lastClone, firstClone, i, formatRootParent;
  16368. // Format root found then clone formats and split it
  16369. if (formatRoot) {
  16370. formatRootParent = formatRoot.parentNode;
  16371. for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
  16372. clone = dom.clone(parent, FALSE);
  16373. for (i = 0; i < formatList.length; i++) {
  16374. if (removeFormat(formatList[i], vars, clone, clone)) {
  16375. clone = 0;
  16376. break;
  16377. }
  16378. }
  16379. // Build wrapper node
  16380. if (clone) {
  16381. if (lastClone) {
  16382. clone.appendChild(lastClone);
  16383. }
  16384. if (!firstClone) {
  16385. firstClone = clone;
  16386. }
  16387. lastClone = clone;
  16388. }
  16389. }
  16390. // Never split block elements if the format is mixed
  16391. if (split && (!format.mixed || !isBlock(formatRoot))) {
  16392. container = dom.split(formatRoot, container);
  16393. }
  16394. // Wrap container in cloned formats
  16395. if (lastClone) {
  16396. target.parentNode.insertBefore(lastClone, target);
  16397. firstClone.appendChild(target);
  16398. }
  16399. }
  16400. return container;
  16401. }
  16402. function splitToFormatRoot(container) {
  16403. return wrapAndSplit(findFormatRoot(container), container, container, true);
  16404. }
  16405. function unwrap(start) {
  16406. var node = dom.get(start ? '_start' : '_end'),
  16407. out = node[start ? 'firstChild' : 'lastChild'];
  16408. // If the end is placed within the start the result will be removed
  16409. // So this checks if the out node is a bookmark node if it is it
  16410. // checks for another more suitable node
  16411. if (isBookmarkNode(out)) {
  16412. out = out[start ? 'firstChild' : 'lastChild'];
  16413. }
  16414. // Since dom.remove removes empty text nodes then we need to try to find a better node
  16415. if (out.nodeType == 3 && out.data.length === 0) {
  16416. out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling;
  16417. }
  16418. dom.remove(node, true);
  16419. return out;
  16420. }
  16421. function removeRngStyle(rng) {
  16422. var startContainer, endContainer;
  16423. var commonAncestorContainer = rng.commonAncestorContainer;
  16424. rng = expandRng(rng, formatList, TRUE);
  16425. if (format.split) {
  16426. startContainer = getContainer(rng, TRUE);
  16427. endContainer = getContainer(rng);
  16428. if (startContainer != endContainer) {
  16429. // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN
  16430. // so let's see if we can use the first child instead
  16431. // This will happen if you triple click a table cell and use remove formatting
  16432. if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
  16433. if (startContainer.nodeName == "TR") {
  16434. startContainer = startContainer.firstChild.firstChild || startContainer;
  16435. } else {
  16436. startContainer = startContainer.firstChild || startContainer;
  16437. }
  16438. }
  16439. // Try to adjust endContainer as well if cells on the same row were selected - bug #6410
  16440. if (commonAncestorContainer &&
  16441. /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) &&
  16442. isTableCell(endContainer) && endContainer.firstChild) {
  16443. endContainer = endContainer.firstChild || endContainer;
  16444. }
  16445. if (dom.isChildOf(startContainer, endContainer) && !isBlock(endContainer) &&
  16446. !isTableCell(startContainer) && !isTableCell(endContainer)) {
  16447. startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
  16448. splitToFormatRoot(startContainer);
  16449. startContainer = unwrap(TRUE);
  16450. return;
  16451. }
  16452. // Wrap start/end nodes in span element since these might be cloned/moved
  16453. startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
  16454. endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'});
  16455. // Split start/end
  16456. splitToFormatRoot(startContainer);
  16457. splitToFormatRoot(endContainer);
  16458. // Unwrap start/end to get real elements again
  16459. startContainer = unwrap(TRUE);
  16460. endContainer = unwrap();
  16461. } else {
  16462. startContainer = endContainer = splitToFormatRoot(startContainer);
  16463. }
  16464. // Update range positions since they might have changed after the split operations
  16465. rng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer;
  16466. rng.startOffset = nodeIndex(startContainer);
  16467. rng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer;
  16468. rng.endOffset = nodeIndex(endContainer) + 1;
  16469. }
  16470. // Remove items between start/end
  16471. rangeUtils.walk(rng, function(nodes) {
  16472. each(nodes, function(node) {
  16473. process(node);
  16474. // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
  16475. if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' &&
  16476. node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
  16477. removeFormat({
  16478. 'deep': false,
  16479. 'exact': true,
  16480. 'inline': 'span',
  16481. 'styles': {
  16482. 'textDecoration': 'underline'
  16483. }
  16484. }, null, node);
  16485. }
  16486. });
  16487. });
  16488. }
  16489. // Handle node
  16490. if (node) {
  16491. if (node.nodeType) {
  16492. rng = dom.createRng();
  16493. rng.setStartBefore(node);
  16494. rng.setEndAfter(node);
  16495. removeRngStyle(rng);
  16496. } else {
  16497. removeRngStyle(node);
  16498. }
  16499. return;
  16500. }
  16501. if (getContentEditable(selection.getNode()) === "false") {
  16502. node = selection.getNode();
  16503. for (var i = 0, l = formatList.length; i < l; i++) {
  16504. if (formatList[i].ceFalseOverride) {
  16505. if (removeFormat(formatList[i], vars, node, node)) {
  16506. break;
  16507. }
  16508. }
  16509. }
  16510. return;
  16511. }
  16512. if (!selection.isCollapsed() || !format.inline || dom.select('td[data-mce-selected],th[data-mce-selected]').length) {
  16513. bookmark = selection.getBookmark();
  16514. removeRngStyle(selection.getRng(TRUE));
  16515. selection.moveToBookmark(bookmark);
  16516. // Check if start element still has formatting then we are at: "<b>text|</b>text"
  16517. // and need to move the start into the next text node
  16518. if (format.inline && match(name, vars, selection.getStart())) {
  16519. moveStart(selection.getRng(true));
  16520. }
  16521. ed.nodeChanged();
  16522. } else {
  16523. performCaretAction('remove', name, vars, similar);
  16524. }
  16525. }
  16526. /**
  16527. * Toggles the specified format on/off.
  16528. *
  16529. * @method toggle
  16530. * @param {String} name Name of format to apply/remove.
  16531. * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
  16532. * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
  16533. */
  16534. function toggle(name, vars, node) {
  16535. var fmt = get(name);
  16536. if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
  16537. remove(name, vars, node);
  16538. } else {
  16539. apply(name, vars, node);
  16540. }
  16541. }
  16542. /**
  16543. * Return true/false if the specified node has the specified format.
  16544. *
  16545. * @method matchNode
  16546. * @param {Node} node Node to check the format on.
  16547. * @param {String} name Format name to check.
  16548. * @param {Object} vars Optional list of variables to replace before checking it.
  16549. * @param {Boolean} similar Match format that has similar properties.
  16550. * @return {Object} Returns the format object it matches or undefined if it doesn't match.
  16551. */
  16552. function matchNode(node, name, vars, similar) {
  16553. var formatList = get(name), format, i, classes;
  16554. function matchItems(node, format, item_name) {
  16555. var key, value, items = format[item_name], i;
  16556. // Custom match
  16557. if (format.onmatch) {
  16558. return format.onmatch(node, format, item_name);
  16559. }
  16560. // Check all items
  16561. if (items) {
  16562. // Non indexed object
  16563. if (items.length === undef) {
  16564. for (key in items) {
  16565. if (items.hasOwnProperty(key)) {
  16566. if (item_name === 'attributes') {
  16567. value = dom.getAttrib(node, key);
  16568. } else {
  16569. value = getStyle(node, key);
  16570. }
  16571. if (similar && !value && !format.exact) {
  16572. return;
  16573. }
  16574. if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) {
  16575. return;
  16576. }
  16577. }
  16578. }
  16579. } else {
  16580. // Only one match needed for indexed arrays
  16581. for (i = 0; i < items.length; i++) {
  16582. if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) {
  16583. return format;
  16584. }
  16585. }
  16586. }
  16587. }
  16588. return format;
  16589. }
  16590. if (formatList && node) {
  16591. // Check each format in list
  16592. for (i = 0; i < formatList.length; i++) {
  16593. format = formatList[i];
  16594. // Name name, attributes, styles and classes
  16595. if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
  16596. // Match classes
  16597. if ((classes = format.classes)) {
  16598. for (i = 0; i < classes.length; i++) {
  16599. if (!dom.hasClass(node, classes[i])) {
  16600. return;
  16601. }
  16602. }
  16603. }
  16604. return format;
  16605. }
  16606. }
  16607. }
  16608. }
  16609. /**
  16610. * Matches the current selection or specified node against the specified format name.
  16611. *
  16612. * @method match
  16613. * @param {String} name Name of format to match.
  16614. * @param {Object} vars Optional list of variables to replace before checking it.
  16615. * @param {Node} node Optional node to check.
  16616. * @return {boolean} true/false if the specified selection/node matches the format.
  16617. */
  16618. function match(name, vars, node) {
  16619. var startNode;
  16620. function matchParents(node) {
  16621. var root = dom.getRoot();
  16622. if (node === root) {
  16623. return false;
  16624. }
  16625. // Find first node with similar format settings
  16626. node = dom.getParent(node, function(node) {
  16627. if (matchesUnInheritedFormatSelector(node, name)) {
  16628. return true;
  16629. }
  16630. return node.parentNode === root || !!matchNode(node, name, vars, true);
  16631. });
  16632. // Do an exact check on the similar format element
  16633. return matchNode(node, name, vars);
  16634. }
  16635. // Check specified node
  16636. if (node) {
  16637. return matchParents(node);
  16638. }
  16639. // Check selected node
  16640. node = selection.getNode();
  16641. if (matchParents(node)) {
  16642. return TRUE;
  16643. }
  16644. // Check start node if it's different
  16645. startNode = selection.getStart();
  16646. if (startNode != node) {
  16647. if (matchParents(startNode)) {
  16648. return TRUE;
  16649. }
  16650. }
  16651. return FALSE;
  16652. }
  16653. /**
  16654. * Matches the current selection against the array of formats and returns a new array with matching formats.
  16655. *
  16656. * @method matchAll
  16657. * @param {Array} names Name of format to match.
  16658. * @param {Object} vars Optional list of variables to replace before checking it.
  16659. * @return {Array} Array with matched formats.
  16660. */
  16661. function matchAll(names, vars) {
  16662. var startElement, matchedFormatNames = [], checkedMap = {};
  16663. // Check start of selection for formats
  16664. startElement = selection.getStart();
  16665. dom.getParent(startElement, function(node) {
  16666. var i, name;
  16667. for (i = 0; i < names.length; i++) {
  16668. name = names[i];
  16669. if (!checkedMap[name] && matchNode(node, name, vars)) {
  16670. checkedMap[name] = true;
  16671. matchedFormatNames.push(name);
  16672. }
  16673. }
  16674. }, dom.getRoot());
  16675. return matchedFormatNames;
  16676. }
  16677. /**
  16678. * Returns true/false if the specified format can be applied to the current selection or not. It
  16679. * will currently only check the state for selector formats, it returns true on all other format types.
  16680. *
  16681. * @method canApply
  16682. * @param {String} name Name of format to check.
  16683. * @return {boolean} true/false if the specified format can be applied to the current selection/node.
  16684. */
  16685. function canApply(name) {
  16686. var formatList = get(name), startNode, parents, i, x, selector;
  16687. if (formatList) {
  16688. startNode = selection.getStart();
  16689. parents = getParents(startNode);
  16690. for (x = formatList.length - 1; x >= 0; x--) {
  16691. selector = formatList[x].selector;
  16692. // Format is not selector based then always return TRUE
  16693. // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line
  16694. if (!selector || formatList[x].defaultBlock) {
  16695. return TRUE;
  16696. }
  16697. for (i = parents.length - 1; i >= 0; i--) {
  16698. if (dom.is(parents[i], selector)) {
  16699. return TRUE;
  16700. }
  16701. }
  16702. }
  16703. }
  16704. return FALSE;
  16705. }
  16706. /**
  16707. * Executes the specified callback when the current selection matches the formats or not.
  16708. *
  16709. * @method formatChanged
  16710. * @param {String} formats Comma separated list of formats to check for.
  16711. * @param {function} callback Callback with state and args when the format is changed/toggled on/off.
  16712. * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
  16713. */
  16714. function formatChanged(formats, callback, similar) {
  16715. var currentFormats;
  16716. // Setup format node change logic
  16717. if (!formatChangeData) {
  16718. formatChangeData = {};
  16719. currentFormats = {};
  16720. ed.on('NodeChange', function(e) {
  16721. var parents = getParents(e.element), matchedFormats = {};
  16722. // Ignore bogus nodes like the <a> tag created by moveStart()
  16723. parents = Tools.grep(parents, function(node) {
  16724. return node.nodeType == 1 && !node.getAttribute('data-mce-bogus');
  16725. });
  16726. // Check for new formats
  16727. each(formatChangeData, function(callbacks, format) {
  16728. each(parents, function(node) {
  16729. if (matchNode(node, format, {}, callbacks.similar)) {
  16730. if (!currentFormats[format]) {
  16731. // Execute callbacks
  16732. each(callbacks, function(callback) {
  16733. callback(true, {node: node, format: format, parents: parents});
  16734. });
  16735. currentFormats[format] = callbacks;
  16736. }
  16737. matchedFormats[format] = callbacks;
  16738. return false;
  16739. }
  16740. if (matchesUnInheritedFormatSelector(node, format)) {
  16741. return false;
  16742. }
  16743. });
  16744. });
  16745. // Check if current formats still match
  16746. each(currentFormats, function(callbacks, format) {
  16747. if (!matchedFormats[format]) {
  16748. delete currentFormats[format];
  16749. each(callbacks, function(callback) {
  16750. callback(false, {node: e.element, format: format, parents: parents});
  16751. });
  16752. }
  16753. });
  16754. });
  16755. }
  16756. // Add format listeners
  16757. each(formats.split(','), function(format) {
  16758. if (!formatChangeData[format]) {
  16759. formatChangeData[format] = [];
  16760. formatChangeData[format].similar = similar;
  16761. }
  16762. formatChangeData[format].push(callback);
  16763. });
  16764. return this;
  16765. }
  16766. /**
  16767. * Returns a preview css text for the specified format.
  16768. *
  16769. * @method getCssText
  16770. * @param {String/Object} format Format to generate preview css text for.
  16771. * @return {String} Css text for the specified format.
  16772. * @example
  16773. * var cssText1 = editor.formatter.getCssText('bold');
  16774. * var cssText2 = editor.formatter.getCssText({inline: 'b'});
  16775. */
  16776. function getCssText(format) {
  16777. return Preview.getCssText(ed, format);
  16778. }
  16779. // Expose to public
  16780. extend(this, {
  16781. get: get,
  16782. register: register,
  16783. unregister: unregister,
  16784. apply: apply,
  16785. remove: remove,
  16786. toggle: toggle,
  16787. match: match,
  16788. matchAll: matchAll,
  16789. matchNode: matchNode,
  16790. canApply: canApply,
  16791. formatChanged: formatChanged,
  16792. getCssText: getCssText
  16793. });
  16794. // Initialize
  16795. defaultFormats();
  16796. addKeyboardShortcuts();
  16797. ed.on('BeforeGetContent', function(e) {
  16798. if (markCaretContainersBogus && e.format != 'raw') {
  16799. markCaretContainersBogus();
  16800. }
  16801. });
  16802. ed.on('mouseup keydown', function(e) {
  16803. if (disableCaretContainer) {
  16804. disableCaretContainer(e);
  16805. }
  16806. });
  16807. // Private functions
  16808. /**
  16809. * Checks if the specified nodes name matches the format inline/block or selector.
  16810. *
  16811. * @private
  16812. * @param {Node} node Node to match against the specified format.
  16813. * @param {Object} format Format object o match with.
  16814. * @return {boolean} true/false if the format matches.
  16815. */
  16816. function matchName(node, format) {
  16817. // Check for inline match
  16818. if (isEq(node, format.inline)) {
  16819. return TRUE;
  16820. }
  16821. // Check for block match
  16822. if (isEq(node, format.block)) {
  16823. return TRUE;
  16824. }
  16825. // Check for selector match
  16826. if (format.selector) {
  16827. return node.nodeType == 1 && dom.is(node, format.selector);
  16828. }
  16829. }
  16830. /**
  16831. * Compares two string/nodes regardless of their case.
  16832. *
  16833. * @private
  16834. * @param {String/Node} str1 Node or string to compare.
  16835. * @param {String/Node} str2 Node or string to compare.
  16836. * @return {boolean} True/false if they match.
  16837. */
  16838. function isEq(str1, str2) {
  16839. str1 = str1 || '';
  16840. str2 = str2 || '';
  16841. str1 = '' + (str1.nodeName || str1);
  16842. str2 = '' + (str2.nodeName || str2);
  16843. return str1.toLowerCase() == str2.toLowerCase();
  16844. }
  16845. /**
  16846. * Returns the style by name on the specified node. This method modifies the style
  16847. * contents to make it more easy to match. This will resolve a few browser issues.
  16848. *
  16849. * @private
  16850. * @param {Node} node to get style from.
  16851. * @param {String} name Style name to get.
  16852. * @return {String} Style item value.
  16853. */
  16854. function getStyle(node, name) {
  16855. return normalizeStyleValue(dom.getStyle(node, name), name);
  16856. }
  16857. /**
  16858. * Normalize style value by name. This method modifies the style contents
  16859. * to make it more easy to match. This will resolve a few browser issues.
  16860. *
  16861. * @private
  16862. * @param {String} value Value to get style from.
  16863. * @param {String} name Style name to get.
  16864. * @return {String} Style item value.
  16865. */
  16866. function normalizeStyleValue(value, name) {
  16867. // Force the format to hex
  16868. if (name == 'color' || name == 'backgroundColor') {
  16869. value = dom.toHex(value);
  16870. }
  16871. // Opera will return bold as 700
  16872. if (name == 'fontWeight' && value == 700) {
  16873. value = 'bold';
  16874. }
  16875. // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
  16876. if (name == 'fontFamily') {
  16877. value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
  16878. }
  16879. return '' + value;
  16880. }
  16881. /**
  16882. * Replaces variables in the value. The variable format is %var.
  16883. *
  16884. * @private
  16885. * @param {String} value Value to replace variables in.
  16886. * @param {Object} vars Name/value array with variables to replace.
  16887. * @return {String} New value with replaced variables.
  16888. */
  16889. function replaceVars(value, vars) {
  16890. if (typeof value != "string") {
  16891. value = value(vars);
  16892. } else if (vars) {
  16893. value = value.replace(/%(\w+)/g, function(str, name) {
  16894. return vars[name] || str;
  16895. });
  16896. }
  16897. return value;
  16898. }
  16899. function isWhiteSpaceNode(node) {
  16900. return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
  16901. }
  16902. function wrap(node, name, attrs) {
  16903. var wrapper = dom.create(name, attrs);
  16904. node.parentNode.insertBefore(wrapper, node);
  16905. wrapper.appendChild(node);
  16906. return wrapper;
  16907. }
  16908. /**
  16909. * Expands the specified range like object to depending on format.
  16910. *
  16911. * For example on block formats it will move the start/end position
  16912. * to the beginning of the current block.
  16913. *
  16914. * @private
  16915. * @param {Object} rng Range like object.
  16916. * @param {Array} format Array with formats to expand by.
  16917. * @param {Boolean} remove
  16918. * @return {Object} Expanded range like object.
  16919. */
  16920. function expandRng(rng, format, remove) {
  16921. var lastIdx, leaf, endPoint,
  16922. startContainer = rng.startContainer,
  16923. startOffset = rng.startOffset,
  16924. endContainer = rng.endContainer,
  16925. endOffset = rng.endOffset;
  16926. // This function walks up the tree if there is no siblings before/after the node
  16927. function findParentContainer(start) {
  16928. var container, parent, sibling, siblingName, root;
  16929. container = parent = start ? startContainer : endContainer;
  16930. siblingName = start ? 'previousSibling' : 'nextSibling';
  16931. root = dom.getRoot();
  16932. function isBogusBr(node) {
  16933. return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
  16934. }
  16935. // If it's a text node and the offset is inside the text
  16936. if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
  16937. if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
  16938. return container;
  16939. }
  16940. }
  16941. /*eslint no-constant-condition:0 */
  16942. while (true) {
  16943. // Stop expanding on block elements
  16944. if (!format[0].block_expand && isBlock(parent)) {
  16945. return parent;
  16946. }
  16947. // Walk left/right
  16948. for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
  16949. if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
  16950. return parent;
  16951. }
  16952. }
  16953. // Check if we can move up are we at root level or body level
  16954. if (parent == root || parent.parentNode == root) {
  16955. container = parent;
  16956. break;
  16957. }
  16958. parent = parent.parentNode;
  16959. }
  16960. return container;
  16961. }
  16962. // This function walks down the tree to find the leaf at the selection.
  16963. // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
  16964. function findLeaf(node, offset) {
  16965. if (offset === undef) {
  16966. offset = node.nodeType === 3 ? node.length : node.childNodes.length;
  16967. }
  16968. while (node && node.hasChildNodes()) {
  16969. node = node.childNodes[offset];
  16970. if (node) {
  16971. offset = node.nodeType === 3 ? node.length : node.childNodes.length;
  16972. }
  16973. }
  16974. return {node: node, offset: offset};
  16975. }
  16976. // If index based start position then resolve it
  16977. if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
  16978. lastIdx = startContainer.childNodes.length - 1;
  16979. startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
  16980. if (startContainer.nodeType == 3) {
  16981. startOffset = 0;
  16982. }
  16983. }
  16984. // If index based end position then resolve it
  16985. if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
  16986. lastIdx = endContainer.childNodes.length - 1;
  16987. endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
  16988. if (endContainer.nodeType == 3) {
  16989. endOffset = endContainer.nodeValue.length;
  16990. }
  16991. }
  16992. // Expands the node to the closes contentEditable false element if it exists
  16993. function findParentContentEditable(node) {
  16994. var parent = node;
  16995. while (parent) {
  16996. if (parent.nodeType === 1 && getContentEditable(parent)) {
  16997. return getContentEditable(parent) === "false" ? parent : node;
  16998. }
  16999. parent = parent.parentNode;
  17000. }
  17001. return node;
  17002. }
  17003. function findWordEndPoint(container, offset, start) {
  17004. var walker, node, pos, lastTextNode;
  17005. function findSpace(node, offset) {
  17006. var pos, pos2, str = node.nodeValue;
  17007. if (typeof offset == "undefined") {
  17008. offset = start ? str.length : 0;
  17009. }
  17010. if (start) {
  17011. pos = str.lastIndexOf(' ', offset);
  17012. pos2 = str.lastIndexOf('\u00a0', offset);
  17013. pos = pos > pos2 ? pos : pos2;
  17014. // Include the space on remove to avoid tag soup
  17015. if (pos !== -1 && !remove) {
  17016. pos++;
  17017. }
  17018. } else {
  17019. pos = str.indexOf(' ', offset);
  17020. pos2 = str.indexOf('\u00a0', offset);
  17021. pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
  17022. }
  17023. return pos;
  17024. }
  17025. if (container.nodeType === 3) {
  17026. pos = findSpace(container, offset);
  17027. if (pos !== -1) {
  17028. return {container: container, offset: pos};
  17029. }
  17030. lastTextNode = container;
  17031. }
  17032. // Walk the nodes inside the block
  17033. walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
  17034. while ((node = walker[start ? 'prev' : 'next']())) {
  17035. if (node.nodeType === 3) {
  17036. lastTextNode = node;
  17037. pos = findSpace(node);
  17038. if (pos !== -1) {
  17039. return {container: node, offset: pos};
  17040. }
  17041. } else if (isBlock(node)) {
  17042. break;
  17043. }
  17044. }
  17045. if (lastTextNode) {
  17046. if (start) {
  17047. offset = 0;
  17048. } else {
  17049. offset = lastTextNode.length;
  17050. }
  17051. return {container: lastTextNode, offset: offset};
  17052. }
  17053. }
  17054. function findSelectorEndPoint(container, sibling_name) {
  17055. var parents, i, y, curFormat;
  17056. if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) {
  17057. container = container[sibling_name];
  17058. }
  17059. parents = getParents(container);
  17060. for (i = 0; i < parents.length; i++) {
  17061. for (y = 0; y < format.length; y++) {
  17062. curFormat = format[y];
  17063. // If collapsed state is set then skip formats that doesn't match that
  17064. if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) {
  17065. continue;
  17066. }
  17067. if (dom.is(parents[i], curFormat.selector)) {
  17068. return parents[i];
  17069. }
  17070. }
  17071. }
  17072. return container;
  17073. }
  17074. function findBlockEndPoint(container, sibling_name) {
  17075. var node, root = dom.getRoot();
  17076. // Expand to block of similar type
  17077. if (!format[0].wrapper) {
  17078. node = dom.getParent(container, format[0].block, root);
  17079. }
  17080. // Expand to first wrappable block element or any block element
  17081. if (!node) {
  17082. node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) {
  17083. // Fixes #6183 where it would expand to editable parent element in inline mode
  17084. return node != root && isTextBlock(node);
  17085. });
  17086. }
  17087. // Exclude inner lists from wrapping
  17088. if (node && format[0].wrapper) {
  17089. node = getParents(node, 'ul,ol').reverse()[0] || node;
  17090. }
  17091. // Didn't find a block element look for first/last wrappable element
  17092. if (!node) {
  17093. node = container;
  17094. while (node[sibling_name] && !isBlock(node[sibling_name])) {
  17095. node = node[sibling_name];
  17096. // Break on BR but include it will be removed later on
  17097. // we can't remove it now since we need to check if it can be wrapped
  17098. if (isEq(node, 'br')) {
  17099. break;
  17100. }
  17101. }
  17102. }
  17103. return node || container;
  17104. }
  17105. // Expand to closest contentEditable element
  17106. startContainer = findParentContentEditable(startContainer);
  17107. endContainer = findParentContentEditable(endContainer);
  17108. // Exclude bookmark nodes if possible
  17109. if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
  17110. startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
  17111. startContainer = startContainer.nextSibling || startContainer;
  17112. if (startContainer.nodeType == 3) {
  17113. startOffset = 0;
  17114. }
  17115. }
  17116. if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
  17117. endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
  17118. endContainer = endContainer.previousSibling || endContainer;
  17119. if (endContainer.nodeType == 3) {
  17120. endOffset = endContainer.length;
  17121. }
  17122. }
  17123. if (format[0].inline) {
  17124. if (rng.collapsed) {
  17125. // Expand left to closest word boundary
  17126. endPoint = findWordEndPoint(startContainer, startOffset, true);
  17127. if (endPoint) {
  17128. startContainer = endPoint.container;
  17129. startOffset = endPoint.offset;
  17130. }
  17131. // Expand right to closest word boundary
  17132. endPoint = findWordEndPoint(endContainer, endOffset);
  17133. if (endPoint) {
  17134. endContainer = endPoint.container;
  17135. endOffset = endPoint.offset;
  17136. }
  17137. }
  17138. // Avoid applying formatting to a trailing space.
  17139. leaf = findLeaf(endContainer, endOffset);
  17140. if (leaf.node) {
  17141. while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) {
  17142. leaf = findLeaf(leaf.node.previousSibling);
  17143. }
  17144. if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
  17145. leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
  17146. if (leaf.offset > 1) {
  17147. endContainer = leaf.node;
  17148. endContainer.splitText(leaf.offset - 1);
  17149. }
  17150. }
  17151. }
  17152. }
  17153. // Move start/end point up the tree if the leaves are sharp and if we are in different containers
  17154. // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
  17155. // This will reduce the number of wrapper elements that needs to be created
  17156. // Move start point up the tree
  17157. if (format[0].inline || format[0].block_expand) {
  17158. if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
  17159. startContainer = findParentContainer(true);
  17160. }
  17161. if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
  17162. endContainer = findParentContainer();
  17163. }
  17164. }
  17165. // Expand start/end container to matching selector
  17166. if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
  17167. // Find new startContainer/endContainer if there is better one
  17168. startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
  17169. endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
  17170. }
  17171. // Expand start/end container to matching block element or text node
  17172. if (format[0].block || format[0].selector) {
  17173. // Find new startContainer/endContainer if there is better one
  17174. startContainer = findBlockEndPoint(startContainer, 'previousSibling');
  17175. endContainer = findBlockEndPoint(endContainer, 'nextSibling');
  17176. // Non block element then try to expand up the leaf
  17177. if (format[0].block) {
  17178. if (!isBlock(startContainer)) {
  17179. startContainer = findParentContainer(true);
  17180. }
  17181. if (!isBlock(endContainer)) {
  17182. endContainer = findParentContainer();
  17183. }
  17184. }
  17185. }
  17186. // Setup index for startContainer
  17187. if (startContainer.nodeType == 1) {
  17188. startOffset = nodeIndex(startContainer);
  17189. startContainer = startContainer.parentNode;
  17190. }
  17191. // Setup index for endContainer
  17192. if (endContainer.nodeType == 1) {
  17193. endOffset = nodeIndex(endContainer) + 1;
  17194. endContainer = endContainer.parentNode;
  17195. }
  17196. // Return new range like object
  17197. return {
  17198. startContainer: startContainer,
  17199. startOffset: startOffset,
  17200. endContainer: endContainer,
  17201. endOffset: endOffset
  17202. };
  17203. }
  17204. function isColorFormatAndAnchor(node, format) {
  17205. return format.links && node.tagName == 'A';
  17206. }
  17207. /**
  17208. * Removes the specified format for the specified node. It will also remove the node if it doesn't have
  17209. * any attributes if the format specifies it to do so.
  17210. *
  17211. * @private
  17212. * @param {Object} format Format object with items to remove from node.
  17213. * @param {Object} vars Name/value object with variables to apply to format.
  17214. * @param {Node} node Node to remove the format styles on.
  17215. * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node.
  17216. * @return {Boolean} True/false if the node was removed or not.
  17217. */
  17218. function removeFormat(format, vars, node, compare_node) {
  17219. var i, attrs, stylesModified;
  17220. // Check if node matches format
  17221. if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) {
  17222. return FALSE;
  17223. }
  17224. // Should we compare with format attribs and styles
  17225. if (format.remove != 'all') {
  17226. // Remove styles
  17227. each(format.styles, function(value, name) {
  17228. value = normalizeStyleValue(replaceVars(value, vars), name);
  17229. // Indexed array
  17230. if (typeof name === 'number') {
  17231. name = value;
  17232. compare_node = 0;
  17233. }
  17234. if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) {
  17235. dom.setStyle(node, name, '');
  17236. }
  17237. stylesModified = 1;
  17238. });
  17239. // Remove style attribute if it's empty
  17240. if (stylesModified && dom.getAttrib(node, 'style') === '') {
  17241. node.removeAttribute('style');
  17242. node.removeAttribute('data-mce-style');
  17243. }
  17244. // Remove attributes
  17245. each(format.attributes, function(value, name) {
  17246. var valueOut;
  17247. value = replaceVars(value, vars);
  17248. // Indexed array
  17249. if (typeof name === 'number') {
  17250. name = value;
  17251. compare_node = 0;
  17252. }
  17253. if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
  17254. // Keep internal classes
  17255. if (name == 'class') {
  17256. value = dom.getAttrib(node, name);
  17257. if (value) {
  17258. // Build new class value where everything is removed except the internal prefixed classes
  17259. valueOut = '';
  17260. each(value.split(/\s+/), function(cls) {
  17261. if (/mce\-\w+/.test(cls)) {
  17262. valueOut += (valueOut ? ' ' : '') + cls;
  17263. }
  17264. });
  17265. // We got some internal classes left
  17266. if (valueOut) {
  17267. dom.setAttrib(node, name, valueOut);
  17268. return;
  17269. }
  17270. }
  17271. }
  17272. // IE6 has a bug where the attribute doesn't get removed correctly
  17273. if (name == "class") {
  17274. node.removeAttribute('className');
  17275. }
  17276. // Remove mce prefixed attributes
  17277. if (MCE_ATTR_RE.test(name)) {
  17278. node.removeAttribute('data-mce-' + name);
  17279. }
  17280. node.removeAttribute(name);
  17281. }
  17282. });
  17283. // Remove classes
  17284. each(format.classes, function(value) {
  17285. value = replaceVars(value, vars);
  17286. if (!compare_node || dom.hasClass(compare_node, value)) {
  17287. dom.removeClass(node, value);
  17288. }
  17289. });
  17290. // Check for non internal attributes
  17291. attrs = dom.getAttribs(node);
  17292. for (i = 0; i < attrs.length; i++) {
  17293. if (attrs[i].nodeName.indexOf('_') !== 0) {
  17294. return FALSE;
  17295. }
  17296. }
  17297. }
  17298. // Remove the inline child if it's empty for example <b> or <span>
  17299. if (format.remove != 'none') {
  17300. removeNode(node, format);
  17301. return TRUE;
  17302. }
  17303. }
  17304. /**
  17305. * Removes the node and wrap it's children in paragraphs before doing so or
  17306. * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
  17307. *
  17308. * If the div in the node below gets removed:
  17309. * text<div>text</div>text
  17310. *
  17311. * Output becomes:
  17312. * text<div><br />text<br /></div>text
  17313. *
  17314. * So when the div is removed the result is:
  17315. * text<br />text<br />text
  17316. *
  17317. * @private
  17318. * @param {Node} node Node to remove + apply BR/P elements to.
  17319. * @param {Object} format Format rule.
  17320. * @return {Node} Input node.
  17321. */
  17322. function removeNode(node, format) {
  17323. var parentNode = node.parentNode, rootBlockElm;
  17324. function find(node, next, inc) {
  17325. node = getNonWhiteSpaceSibling(node, next, inc);
  17326. return !node || (node.nodeName == 'BR' || isBlock(node));
  17327. }
  17328. if (format.block) {
  17329. if (!forcedRootBlock) {
  17330. // Append BR elements if needed before we remove the block
  17331. if (isBlock(node) && !isBlock(parentNode)) {
  17332. if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) {
  17333. node.insertBefore(dom.create('br'), node.firstChild);
  17334. }
  17335. if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) {
  17336. node.appendChild(dom.create('br'));
  17337. }
  17338. }
  17339. } else {
  17340. // Wrap the block in a forcedRootBlock if we are at the root of document
  17341. if (parentNode == dom.getRoot()) {
  17342. if (!format.list_block || !isEq(node, format.list_block)) {
  17343. each(grep(node.childNodes), function(node) {
  17344. if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
  17345. if (!rootBlockElm) {
  17346. rootBlockElm = wrap(node, forcedRootBlock);
  17347. dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs);
  17348. } else {
  17349. rootBlockElm.appendChild(node);
  17350. }
  17351. } else {
  17352. rootBlockElm = 0;
  17353. }
  17354. });
  17355. }
  17356. }
  17357. }
  17358. }
  17359. // Never remove nodes that isn't the specified inline element if a selector is specified too
  17360. if (format.selector && format.inline && !isEq(format.inline, node)) {
  17361. return;
  17362. }
  17363. dom.remove(node, 1);
  17364. }
  17365. /**
  17366. * Returns the next/previous non whitespace node.
  17367. *
  17368. * @private
  17369. * @param {Node} node Node to start at.
  17370. * @param {boolean} next (Optional) Include next or previous node defaults to previous.
  17371. * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
  17372. * @return {Node} Next or previous node or undefined if it wasn't found.
  17373. */
  17374. function getNonWhiteSpaceSibling(node, next, inc) {
  17375. if (node) {
  17376. next = next ? 'nextSibling' : 'previousSibling';
  17377. for (node = inc ? node : node[next]; node; node = node[next]) {
  17378. if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
  17379. return node;
  17380. }
  17381. }
  17382. }
  17383. }
  17384. /**
  17385. * Merges the next/previous sibling element if they match.
  17386. *
  17387. * @private
  17388. * @param {Node} prev Previous node to compare/merge.
  17389. * @param {Node} next Next node to compare/merge.
  17390. * @return {Node} Next node if we didn't merge and prev node if we did.
  17391. */
  17392. function mergeSiblings(prev, next) {
  17393. var sibling, tmpSibling, elementUtils = new ElementUtils(dom);
  17394. function findElementSibling(node, sibling_name) {
  17395. for (sibling = node; sibling; sibling = sibling[sibling_name]) {
  17396. if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) {
  17397. return node;
  17398. }
  17399. if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) {
  17400. return sibling;
  17401. }
  17402. }
  17403. return node;
  17404. }
  17405. // Check if next/prev exists and that they are elements
  17406. if (prev && next) {
  17407. // If previous sibling is empty then jump over it
  17408. prev = findElementSibling(prev, 'previousSibling');
  17409. next = findElementSibling(next, 'nextSibling');
  17410. // Compare next and previous nodes
  17411. if (elementUtils.compare(prev, next)) {
  17412. // Append nodes between
  17413. for (sibling = prev.nextSibling; sibling && sibling != next;) {
  17414. tmpSibling = sibling;
  17415. sibling = sibling.nextSibling;
  17416. prev.appendChild(tmpSibling);
  17417. }
  17418. // Remove next node
  17419. dom.remove(next);
  17420. // Move children into prev node
  17421. each(grep(next.childNodes), function(node) {
  17422. prev.appendChild(node);
  17423. });
  17424. return prev;
  17425. }
  17426. }
  17427. return next;
  17428. }
  17429. function getContainer(rng, start) {
  17430. var container, offset, lastIdx;
  17431. container = rng[start ? 'startContainer' : 'endContainer'];
  17432. offset = rng[start ? 'startOffset' : 'endOffset'];
  17433. if (container.nodeType == 1) {
  17434. lastIdx = container.childNodes.length - 1;
  17435. if (!start && offset) {
  17436. offset--;
  17437. }
  17438. container = container.childNodes[offset > lastIdx ? lastIdx : offset];
  17439. }
  17440. // If start text node is excluded then walk to the next node
  17441. if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
  17442. container = new TreeWalker(container, ed.getBody()).next() || container;
  17443. }
  17444. // If end text node is excluded then walk to the previous node
  17445. if (container.nodeType === 3 && !start && offset === 0) {
  17446. container = new TreeWalker(container, ed.getBody()).prev() || container;
  17447. }
  17448. return container;
  17449. }
  17450. function performCaretAction(type, name, vars, similar) {
  17451. var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
  17452. // Creates a caret container bogus element
  17453. function createCaretContainer(fill) {
  17454. var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
  17455. if (fill) {
  17456. caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
  17457. }
  17458. return caretContainer;
  17459. }
  17460. function isCaretContainerEmpty(node, nodes) {
  17461. while (node) {
  17462. if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
  17463. return false;
  17464. }
  17465. // Collect nodes
  17466. if (nodes && node.nodeType === 1) {
  17467. nodes.push(node);
  17468. }
  17469. node = node.firstChild;
  17470. }
  17471. return true;
  17472. }
  17473. // Returns any parent caret container element
  17474. function getParentCaretContainer(node) {
  17475. while (node) {
  17476. if (node.id === caretContainerId) {
  17477. return node;
  17478. }
  17479. node = node.parentNode;
  17480. }
  17481. }
  17482. // Finds the first text node in the specified node
  17483. function findFirstTextNode(node) {
  17484. var walker;
  17485. if (node) {
  17486. walker = new TreeWalker(node, node);
  17487. for (node = walker.current(); node; node = walker.next()) {
  17488. if (node.nodeType === 3) {
  17489. return node;
  17490. }
  17491. }
  17492. }
  17493. }
  17494. // Removes the caret container for the specified node or all on the current document
  17495. function removeCaretContainer(node, move_caret) {
  17496. var child, rng;
  17497. if (!node) {
  17498. node = getParentCaretContainer(selection.getStart());
  17499. if (!node) {
  17500. while ((node = dom.get(caretContainerId))) {
  17501. removeCaretContainer(node, false);
  17502. }
  17503. }
  17504. } else {
  17505. rng = selection.getRng(true);
  17506. if (isCaretContainerEmpty(node)) {
  17507. if (move_caret !== false) {
  17508. rng.setStartBefore(node);
  17509. rng.setEndBefore(node);
  17510. }
  17511. dom.remove(node);
  17512. } else {
  17513. child = findFirstTextNode(node);
  17514. if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
  17515. child.deleteData(0, 1);
  17516. // Fix for bug #6976
  17517. if (rng.startContainer == child && rng.startOffset > 0) {
  17518. rng.setStart(child, rng.startOffset - 1);
  17519. }
  17520. if (rng.endContainer == child && rng.endOffset > 0) {
  17521. rng.setEnd(child, rng.endOffset - 1);
  17522. }
  17523. }
  17524. dom.remove(node, 1);
  17525. }
  17526. selection.setRng(rng);
  17527. }
  17528. }
  17529. // Applies formatting to the caret position
  17530. function applyCaretFormat() {
  17531. var rng, caretContainer, textNode, offset, bookmark, container, text;
  17532. rng = selection.getRng(true);
  17533. offset = rng.startOffset;
  17534. container = rng.startContainer;
  17535. text = container.nodeValue;
  17536. caretContainer = getParentCaretContainer(selection.getStart());
  17537. if (caretContainer) {
  17538. textNode = findFirstTextNode(caretContainer);
  17539. }
  17540. // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
  17541. if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
  17542. // Get bookmark of caret position
  17543. bookmark = selection.getBookmark();
  17544. // Collapse bookmark range (WebKit)
  17545. rng.collapse(true);
  17546. // Expand the range to the closest word and split it at those points
  17547. rng = expandRng(rng, get(name));
  17548. rng = rangeUtils.split(rng);
  17549. // Apply the format to the range
  17550. apply(name, vars, rng);
  17551. // Move selection back to caret position
  17552. selection.moveToBookmark(bookmark);
  17553. } else {
  17554. if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
  17555. caretContainer = createCaretContainer(true);
  17556. textNode = caretContainer.firstChild;
  17557. rng.insertNode(caretContainer);
  17558. offset = 1;
  17559. apply(name, vars, caretContainer);
  17560. } else {
  17561. apply(name, vars, caretContainer);
  17562. }
  17563. // Move selection to text node
  17564. selection.setCursorLocation(textNode, offset);
  17565. }
  17566. }
  17567. function removeCaretFormat() {
  17568. var rng = selection.getRng(true), container, offset, bookmark,
  17569. hasContentAfter, node, formatNode, parents = [], i, caretContainer;
  17570. container = rng.startContainer;
  17571. offset = rng.startOffset;
  17572. node = container;
  17573. if (container.nodeType == 3) {
  17574. if (offset != container.nodeValue.length) {
  17575. hasContentAfter = true;
  17576. }
  17577. node = node.parentNode;
  17578. }
  17579. while (node) {
  17580. if (matchNode(node, name, vars, similar)) {
  17581. formatNode = node;
  17582. break;
  17583. }
  17584. if (node.nextSibling) {
  17585. hasContentAfter = true;
  17586. }
  17587. parents.push(node);
  17588. node = node.parentNode;
  17589. }
  17590. // Node doesn't have the specified format
  17591. if (!formatNode) {
  17592. return;
  17593. }
  17594. // Is there contents after the caret then remove the format on the element
  17595. if (hasContentAfter) {
  17596. // Get bookmark of caret position
  17597. bookmark = selection.getBookmark();
  17598. // Collapse bookmark range (WebKit)
  17599. rng.collapse(true);
  17600. // Expand the range to the closest word and split it at those points
  17601. rng = expandRng(rng, get(name), true);
  17602. rng = rangeUtils.split(rng);
  17603. // Remove the format from the range
  17604. remove(name, vars, rng);
  17605. // Move selection back to caret position
  17606. selection.moveToBookmark(bookmark);
  17607. } else {
  17608. caretContainer = createCaretContainer();
  17609. node = caretContainer;
  17610. for (i = parents.length - 1; i >= 0; i--) {
  17611. node.appendChild(dom.clone(parents[i], false));
  17612. node = node.firstChild;
  17613. }
  17614. // Insert invisible character into inner most format element
  17615. node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
  17616. node = node.firstChild;
  17617. var block = dom.getParent(formatNode, isTextBlock);
  17618. if (block && dom.isEmpty(block)) {
  17619. // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
  17620. formatNode.parentNode.replaceChild(caretContainer, formatNode);
  17621. } else {
  17622. // Insert caret container after the formatted node
  17623. dom.insertAfter(caretContainer, formatNode);
  17624. }
  17625. // Move selection to text node
  17626. selection.setCursorLocation(node, 1);
  17627. // If the formatNode is empty, we can remove it safely.
  17628. if (dom.isEmpty(formatNode)) {
  17629. dom.remove(formatNode);
  17630. }
  17631. }
  17632. }
  17633. // Checks if the parent caret container node isn't empty if that is the case it
  17634. // will remove the bogus state on all children that isn't empty
  17635. function unmarkBogusCaretParents() {
  17636. var caretContainer;
  17637. caretContainer = getParentCaretContainer(selection.getStart());
  17638. if (caretContainer && !dom.isEmpty(caretContainer)) {
  17639. walk(caretContainer, function(node) {
  17640. if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
  17641. dom.setAttrib(node, 'data-mce-bogus', null);
  17642. }
  17643. }, 'childNodes');
  17644. }
  17645. }
  17646. // Only bind the caret events once
  17647. if (!ed._hasCaretEvents) {
  17648. // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
  17649. markCaretContainersBogus = function() {
  17650. var nodes = [], i;
  17651. if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
  17652. // Mark children
  17653. i = nodes.length;
  17654. while (i--) {
  17655. dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
  17656. }
  17657. }
  17658. };
  17659. disableCaretContainer = function(e) {
  17660. var keyCode = e.keyCode;
  17661. removeCaretContainer();
  17662. // Remove caret container if it's empty
  17663. if (keyCode == 8 && selection.isCollapsed() && selection.getStart().innerHTML == INVISIBLE_CHAR) {
  17664. removeCaretContainer(getParentCaretContainer(selection.getStart()));
  17665. }
  17666. // Remove caret container on keydown and it's left/right arrow keys
  17667. if (keyCode == 37 || keyCode == 39) {
  17668. removeCaretContainer(getParentCaretContainer(selection.getStart()));
  17669. }
  17670. unmarkBogusCaretParents();
  17671. };
  17672. // Remove bogus state if they got filled by contents using editor.selection.setContent
  17673. ed.on('SetContent', function(e) {
  17674. if (e.selection) {
  17675. unmarkBogusCaretParents();
  17676. }
  17677. });
  17678. ed._hasCaretEvents = true;
  17679. }
  17680. // Do apply or remove caret format
  17681. if (type == "apply") {
  17682. applyCaretFormat();
  17683. } else {
  17684. removeCaretFormat();
  17685. }
  17686. }
  17687. /**
  17688. * Moves the start to the first suitable text node.
  17689. */
  17690. function moveStart(rng) {
  17691. var container = rng.startContainer,
  17692. offset = rng.startOffset, isAtEndOfText,
  17693. walker, node, nodes, tmpNode;
  17694. if (rng.startContainer == rng.endContainer) {
  17695. if (isInlineBlock(rng.startContainer.childNodes[rng.startOffset])) {
  17696. return;
  17697. }
  17698. }
  17699. // Convert text node into index if possible
  17700. if (container.nodeType == 3 && offset >= container.nodeValue.length) {
  17701. // Get the parent container location and walk from there
  17702. offset = nodeIndex(container);
  17703. container = container.parentNode;
  17704. isAtEndOfText = true;
  17705. }
  17706. // Move startContainer/startOffset in to a suitable node
  17707. if (container.nodeType == 1) {
  17708. nodes = container.childNodes;
  17709. container = nodes[Math.min(offset, nodes.length - 1)];
  17710. walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
  17711. // If offset is at end of the parent node walk to the next one
  17712. if (offset > nodes.length - 1 || isAtEndOfText) {
  17713. walker.next();
  17714. }
  17715. for (node = walker.current(); node; node = walker.next()) {
  17716. if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
  17717. // IE has a "neat" feature where it moves the start node into the closest element
  17718. // we can avoid this by inserting an element before it and then remove it after we set the selection
  17719. tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR);
  17720. node.parentNode.insertBefore(tmpNode, node);
  17721. // Set selection and remove tmpNode
  17722. rng.setStart(node, 0);
  17723. selection.setRng(rng);
  17724. dom.remove(tmpNode);
  17725. return;
  17726. }
  17727. }
  17728. }
  17729. }
  17730. };
  17731. });
  17732. // Included from: js/tinymce/classes/UndoManager.js
  17733. /**
  17734. * UndoManager.js
  17735. *
  17736. * Released under LGPL License.
  17737. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  17738. *
  17739. * License: http://www.tinymce.com/license
  17740. * Contributing: http://www.tinymce.com/contributing
  17741. */
  17742. /**
  17743. * This class handles the undo/redo history levels for the editor. Since the built-in undo/redo has major drawbacks a custom one was needed.
  17744. *
  17745. * @class tinymce.UndoManager
  17746. */
  17747. define("tinymce/UndoManager", [
  17748. "tinymce/util/VK",
  17749. "tinymce/Env"
  17750. ], function(VK, Env) {
  17751. return function(editor) {
  17752. var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
  17753. function getContent() {
  17754. return editor.serializer.getTrimmedContent();
  17755. }
  17756. function setDirty(state) {
  17757. editor.setDirty(state);
  17758. }
  17759. function addNonTypingUndoLevel(e) {
  17760. self.typing = false;
  17761. self.add({}, e);
  17762. }
  17763. // Add initial undo level when the editor is initialized
  17764. editor.on('init', function() {
  17765. self.add();
  17766. });
  17767. // Get position before an execCommand is processed
  17768. editor.on('BeforeExecCommand', function(e) {
  17769. var cmd = e.command;
  17770. if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
  17771. self.beforeChange();
  17772. }
  17773. });
  17774. // Add undo level after an execCommand call was made
  17775. editor.on('ExecCommand', function(e) {
  17776. var cmd = e.command;
  17777. if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
  17778. addNonTypingUndoLevel(e);
  17779. }
  17780. });
  17781. editor.on('ObjectResizeStart Cut', function() {
  17782. self.beforeChange();
  17783. });
  17784. editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
  17785. editor.on('DragEnd', addNonTypingUndoLevel);
  17786. editor.on('KeyUp', function(e) {
  17787. var keyCode = e.keyCode;
  17788. // If key is prevented then don't add undo level
  17789. // This would happen on keyboard shortcuts for example
  17790. if (e.isDefaultPrevented()) {
  17791. return;
  17792. }
  17793. if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
  17794. addNonTypingUndoLevel();
  17795. editor.nodeChanged();
  17796. }
  17797. if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
  17798. editor.nodeChanged();
  17799. }
  17800. // Fire a TypingUndo event on the first character entered
  17801. if (isFirstTypedCharacter && self.typing) {
  17802. // Make it dirty if the content was changed after typing the first character
  17803. if (!editor.isDirty()) {
  17804. setDirty(data[0] && getContent() != data[0].content);
  17805. // Fire initial change event
  17806. if (editor.isDirty()) {
  17807. editor.fire('change', {level: data[0], lastLevel: null});
  17808. }
  17809. }
  17810. editor.fire('TypingUndo');
  17811. isFirstTypedCharacter = false;
  17812. editor.nodeChanged();
  17813. }
  17814. });
  17815. editor.on('KeyDown', function(e) {
  17816. var keyCode = e.keyCode;
  17817. // If key is prevented then don't add undo level
  17818. // This would happen on keyboard shortcuts for example
  17819. if (e.isDefaultPrevented()) {
  17820. return;
  17821. }
  17822. // Is character position keys left,right,up,down,home,end,pgdown,pgup,enter
  17823. if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
  17824. if (self.typing) {
  17825. addNonTypingUndoLevel(e);
  17826. }
  17827. return;
  17828. }
  17829. // If key isn't Ctrl+Alt/AltGr
  17830. var modKey = (e.ctrlKey && !e.altKey) || e.metaKey;
  17831. if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing && !modKey) {
  17832. self.beforeChange();
  17833. self.typing = true;
  17834. self.add({}, e);
  17835. isFirstTypedCharacter = true;
  17836. }
  17837. });
  17838. editor.on('MouseDown', function(e) {
  17839. if (self.typing) {
  17840. addNonTypingUndoLevel(e);
  17841. }
  17842. });
  17843. // Add keyboard shortcuts for undo/redo keys
  17844. editor.addShortcut('meta+z', '', 'Undo');
  17845. editor.addShortcut('meta+y,meta+shift+z', '', 'Redo');
  17846. editor.on('AddUndo Undo Redo ClearUndos', function(e) {
  17847. if (!e.isDefaultPrevented()) {
  17848. editor.nodeChanged();
  17849. }
  17850. });
  17851. /*eslint consistent-this:0 */
  17852. self = {
  17853. // Explode for debugging reasons
  17854. data: data,
  17855. /**
  17856. * State if the user is currently typing or not. This will add a typing operation into one undo
  17857. * level instead of one new level for each keystroke.
  17858. *
  17859. * @field {Boolean} typing
  17860. */
  17861. typing: false,
  17862. /**
  17863. * Stores away a bookmark to be used when performing an undo action so that the selection is before
  17864. * the change has been made.
  17865. *
  17866. * @method beforeChange
  17867. */
  17868. beforeChange: function() {
  17869. if (!locks) {
  17870. beforeBookmark = editor.selection.getBookmark(2, true);
  17871. }
  17872. },
  17873. /**
  17874. * Adds a new undo level/snapshot to the undo list.
  17875. *
  17876. * @method add
  17877. * @param {Object} level Optional undo level object to add.
  17878. * @param {DOMEvent} event Optional event responsible for the creation of the undo level.
  17879. * @return {Object} Undo level that got added or null it a level wasn't needed.
  17880. */
  17881. add: function(level, event) {
  17882. var i, settings = editor.settings, lastLevel;
  17883. level = level || {};
  17884. level.content = getContent();
  17885. if (locks || editor.removed) {
  17886. return null;
  17887. }
  17888. lastLevel = data[index];
  17889. if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) {
  17890. return null;
  17891. }
  17892. // Add undo level if needed
  17893. if (lastLevel && lastLevel.content == level.content) {
  17894. return null;
  17895. }
  17896. // Set before bookmark on previous level
  17897. if (data[index]) {
  17898. data[index].beforeBookmark = beforeBookmark;
  17899. }
  17900. // Time to compress
  17901. if (settings.custom_undo_redo_levels) {
  17902. if (data.length > settings.custom_undo_redo_levels) {
  17903. for (i = 0; i < data.length - 1; i++) {
  17904. data[i] = data[i + 1];
  17905. }
  17906. data.length--;
  17907. index = data.length;
  17908. }
  17909. }
  17910. // Get a non intrusive normalized bookmark
  17911. level.bookmark = editor.selection.getBookmark(2, true);
  17912. // Crop array if needed
  17913. if (index < data.length - 1) {
  17914. data.length = index + 1;
  17915. }
  17916. data.push(level);
  17917. index = data.length - 1;
  17918. var args = {level: level, lastLevel: lastLevel, originalEvent: event};
  17919. editor.fire('AddUndo', args);
  17920. if (index > 0) {
  17921. setDirty(true);
  17922. editor.fire('change', args);
  17923. }
  17924. return level;
  17925. },
  17926. /**
  17927. * Undoes the last action.
  17928. *
  17929. * @method undo
  17930. * @return {Object} Undo level or null if no undo was performed.
  17931. */
  17932. undo: function() {
  17933. var level;
  17934. if (self.typing) {
  17935. self.add();
  17936. self.typing = false;
  17937. }
  17938. if (index > 0) {
  17939. level = data[--index];
  17940. editor.setContent(level.content, {format: 'raw'});
  17941. editor.selection.moveToBookmark(level.beforeBookmark);
  17942. setDirty(true);
  17943. editor.fire('undo', {level: level});
  17944. }
  17945. return level;
  17946. },
  17947. /**
  17948. * Redoes the last action.
  17949. *
  17950. * @method redo
  17951. * @return {Object} Redo level or null if no redo was performed.
  17952. */
  17953. redo: function() {
  17954. var level;
  17955. if (index < data.length - 1) {
  17956. level = data[++index];
  17957. editor.setContent(level.content, {format: 'raw'});
  17958. editor.selection.moveToBookmark(level.bookmark);
  17959. setDirty(true);
  17960. editor.fire('redo', {level: level});
  17961. }
  17962. return level;
  17963. },
  17964. /**
  17965. * Removes all undo levels.
  17966. *
  17967. * @method clear
  17968. */
  17969. clear: function() {
  17970. data = [];
  17971. index = 0;
  17972. self.typing = false;
  17973. self.data = data;
  17974. editor.fire('ClearUndos');
  17975. },
  17976. /**
  17977. * Returns true/false if the undo manager has any undo levels.
  17978. *
  17979. * @method hasUndo
  17980. * @return {Boolean} true/false if the undo manager has any undo levels.
  17981. */
  17982. hasUndo: function() {
  17983. // Has undo levels or typing and content isn't the same as the initial level
  17984. return index > 0 || (self.typing && data[0] && getContent() != data[0].content);
  17985. },
  17986. /**
  17987. * Returns true/false if the undo manager has any redo levels.
  17988. *
  17989. * @method hasRedo
  17990. * @return {Boolean} true/false if the undo manager has any redo levels.
  17991. */
  17992. hasRedo: function() {
  17993. return index < data.length - 1 && !this.typing;
  17994. },
  17995. /**
  17996. * Executes the specified mutator function as an undo transaction. The selection
  17997. * before the modification will be stored to the undo stack and if the DOM changes
  17998. * it will add a new undo level. Any methods within the translation that adds undo levels will
  17999. * be ignored. So a translation can include calls to execCommand or editor.insertContent.
  18000. *
  18001. * @method transact
  18002. * @param {function} callback Function that gets executed and has dom manipulation logic in it.
  18003. * @return {Object} Undo level that got added or null it a level wasn't needed.
  18004. */
  18005. transact: function(callback) {
  18006. self.beforeChange();
  18007. try {
  18008. locks++;
  18009. callback();
  18010. } finally {
  18011. locks--;
  18012. }
  18013. return self.add();
  18014. },
  18015. /**
  18016. * Adds an extra "hidden" undo level by first applying the first mutation and store that to the undo stack
  18017. * then roll back that change and do the second mutation on top of the stack. This will produce an extra
  18018. * undo level that the user doesn't see until they undo.
  18019. *
  18020. * @method extra
  18021. * @param {function} callback1 Function that does mutation but gets stored as a "hidden" extra undo level.
  18022. * @param {function} callback2 Function that does mutation but gets displayed to the user.
  18023. */
  18024. extra: function (callback1, callback2) {
  18025. var lastLevel, bookmark;
  18026. if (self.transact(callback1)) {
  18027. bookmark = data[index].bookmark;
  18028. lastLevel = data[index - 1];
  18029. editor.setContent(lastLevel.content, {format: 'raw'});
  18030. editor.selection.moveToBookmark(lastLevel.beforeBookmark);
  18031. if (self.transact(callback2)) {
  18032. data[index - 1].beforeBookmark = bookmark;
  18033. }
  18034. }
  18035. }
  18036. };
  18037. return self;
  18038. };
  18039. });
  18040. // Included from: js/tinymce/classes/EnterKey.js
  18041. /**
  18042. * EnterKey.js
  18043. *
  18044. * Released under LGPL License.
  18045. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  18046. *
  18047. * License: http://www.tinymce.com/license
  18048. * Contributing: http://www.tinymce.com/contributing
  18049. */
  18050. /**
  18051. * Contains logic for handling the enter key to split/generate block elements.
  18052. *
  18053. * @private
  18054. * @class tinymce.EnterKey
  18055. */
  18056. define("tinymce/EnterKey", [
  18057. "tinymce/dom/TreeWalker",
  18058. "tinymce/dom/RangeUtils",
  18059. "tinymce/Env"
  18060. ], function(TreeWalker, RangeUtils, Env) {
  18061. var isIE = Env.ie && Env.ie < 11;
  18062. return function(editor) {
  18063. var dom = editor.dom, selection = editor.selection, settings = editor.settings;
  18064. var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(),
  18065. moveCaretBeforeOnEnterElementsMap = schema.getMoveCaretBeforeOnEnterElements();
  18066. function handleEnterKey(evt) {
  18067. var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
  18068. newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
  18069. // Returns true if the block can be split into two blocks or not
  18070. function canSplitBlock(node) {
  18071. return node &&
  18072. dom.isBlock(node) &&
  18073. !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
  18074. !/^(fixed|absolute)/i.test(node.style.position) &&
  18075. dom.getContentEditable(node) !== "true";
  18076. }
  18077. function isTableCell(node) {
  18078. return node && /^(TD|TH|CAPTION)$/.test(node.nodeName);
  18079. }
  18080. // Renders empty block on IE
  18081. function renderBlockOnIE(block) {
  18082. var oldRng;
  18083. if (dom.isBlock(block)) {
  18084. oldRng = selection.getRng();
  18085. block.appendChild(dom.create('span', null, '\u00a0'));
  18086. selection.select(block);
  18087. block.lastChild.outerHTML = '';
  18088. selection.setRng(oldRng);
  18089. }
  18090. }
  18091. // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
  18092. function trimInlineElementsOnLeftSideOfBlock(block) {
  18093. var node = block, firstChilds = [], i;
  18094. if (!node) {
  18095. return;
  18096. }
  18097. // Find inner most first child ex: <p><i><b>*</b></i></p>
  18098. while ((node = node.firstChild)) {
  18099. if (dom.isBlock(node)) {
  18100. return;
  18101. }
  18102. if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  18103. firstChilds.push(node);
  18104. }
  18105. }
  18106. i = firstChilds.length;
  18107. while (i--) {
  18108. node = firstChilds[i];
  18109. if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
  18110. dom.remove(node);
  18111. } else {
  18112. // Remove <a> </a> see #5381
  18113. if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
  18114. dom.remove(node);
  18115. }
  18116. }
  18117. }
  18118. }
  18119. // Moves the caret to a suitable position within the root for example in the first non
  18120. // pure whitespace text node or before an image
  18121. function moveToCaretPosition(root) {
  18122. var walker, node, rng, lastNode = root, tempElm;
  18123. function firstNonWhiteSpaceNodeSibling(node) {
  18124. while (node) {
  18125. if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) {
  18126. return node;
  18127. }
  18128. node = node.nextSibling;
  18129. }
  18130. }
  18131. if (!root) {
  18132. return;
  18133. }
  18134. // Old IE versions doesn't properly render blocks with br elements in them
  18135. // For example <p><br></p> wont be rendered correctly in a contentEditable area
  18136. // until you remove the br producing <p></p>
  18137. if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
  18138. if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
  18139. dom.remove(parentBlock.firstChild);
  18140. }
  18141. }
  18142. if (/^(LI|DT|DD)$/.test(root.nodeName)) {
  18143. var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
  18144. if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
  18145. root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
  18146. }
  18147. }
  18148. rng = dom.createRng();
  18149. // Normalize whitespace to remove empty text nodes. Fix for: #6904
  18150. // Gecko will be able to place the caret in empty text nodes but it won't render propery
  18151. // Older IE versions will sometimes crash so for now ignore all IE versions
  18152. if (!Env.ie) {
  18153. root.normalize();
  18154. }
  18155. if (root.hasChildNodes()) {
  18156. walker = new TreeWalker(root, root);
  18157. while ((node = walker.current())) {
  18158. if (node.nodeType == 3) {
  18159. rng.setStart(node, 0);
  18160. rng.setEnd(node, 0);
  18161. break;
  18162. }
  18163. if (moveCaretBeforeOnEnterElementsMap[node.nodeName.toLowerCase()]) {
  18164. rng.setStartBefore(node);
  18165. rng.setEndBefore(node);
  18166. break;
  18167. }
  18168. lastNode = node;
  18169. node = walker.next();
  18170. }
  18171. if (!node) {
  18172. rng.setStart(lastNode, 0);
  18173. rng.setEnd(lastNode, 0);
  18174. }
  18175. } else {
  18176. if (root.nodeName == 'BR') {
  18177. if (root.nextSibling && dom.isBlock(root.nextSibling)) {
  18178. // Trick on older IE versions to render the caret before the BR between two lists
  18179. if (!documentMode || documentMode < 9) {
  18180. tempElm = dom.create('br');
  18181. root.parentNode.insertBefore(tempElm, root);
  18182. }
  18183. rng.setStartBefore(root);
  18184. rng.setEndBefore(root);
  18185. } else {
  18186. rng.setStartAfter(root);
  18187. rng.setEndAfter(root);
  18188. }
  18189. } else {
  18190. rng.setStart(root, 0);
  18191. rng.setEnd(root, 0);
  18192. }
  18193. }
  18194. selection.setRng(rng);
  18195. // Remove tempElm created for old IE:s
  18196. dom.remove(tempElm);
  18197. selection.scrollIntoView(root);
  18198. }
  18199. function setForcedBlockAttrs(node) {
  18200. var forcedRootBlockName = settings.forced_root_block;
  18201. if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
  18202. dom.setAttribs(node, settings.forced_root_block_attrs);
  18203. }
  18204. }
  18205. function emptyBlock(elm) {
  18206. // BR is needed in empty blocks on non IE browsers
  18207. elm.innerHTML = !isIE ? '<br data-mce-bogus="1">' : '';
  18208. }
  18209. // Creates a new block element by cloning the current one or creating a new one if the name is specified
  18210. // This function will also copy any text formatting from the parent block and add it to the new one
  18211. function createNewBlock(name) {
  18212. var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();
  18213. if (name || parentBlockName == "TABLE") {
  18214. block = dom.create(name || newBlockName);
  18215. setForcedBlockAttrs(block);
  18216. } else {
  18217. block = parentBlock.cloneNode(false);
  18218. }
  18219. caretNode = block;
  18220. // Clone any parent styles
  18221. if (settings.keep_styles !== false) {
  18222. do {
  18223. if (textInlineElements[node.nodeName]) {
  18224. // Never clone a caret containers
  18225. if (node.id == '_mce_caret') {
  18226. continue;
  18227. }
  18228. clonedNode = node.cloneNode(false);
  18229. dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
  18230. if (block.hasChildNodes()) {
  18231. clonedNode.appendChild(block.firstChild);
  18232. block.appendChild(clonedNode);
  18233. } else {
  18234. caretNode = clonedNode;
  18235. block.appendChild(clonedNode);
  18236. }
  18237. }
  18238. } while ((node = node.parentNode) && node != editableRoot);
  18239. }
  18240. // BR is needed in empty blocks on non IE browsers
  18241. if (!isIE) {
  18242. caretNode.innerHTML = '<br data-mce-bogus="1">';
  18243. }
  18244. return block;
  18245. }
  18246. // Returns true/false if the caret is at the start/end of the parent block element
  18247. function isCaretAtStartOrEndOfBlock(start) {
  18248. var walker, node, name;
  18249. // Caret is in the middle of a text node like "a|b"
  18250. if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
  18251. return false;
  18252. }
  18253. // If after the last element in block node edge case for #5091
  18254. if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
  18255. return true;
  18256. }
  18257. // If the caret if before the first element in parentBlock
  18258. if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
  18259. return true;
  18260. }
  18261. // Caret can be before/after a table
  18262. if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
  18263. return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
  18264. }
  18265. // Walk the DOM and look for text nodes or non empty elements
  18266. walker = new TreeWalker(container, parentBlock);
  18267. // If caret is in beginning or end of a text block then jump to the next/previous node
  18268. if (container.nodeType == 3) {
  18269. if (start && offset === 0) {
  18270. walker.prev();
  18271. } else if (!start && offset == container.nodeValue.length) {
  18272. walker.next();
  18273. }
  18274. }
  18275. while ((node = walker.current())) {
  18276. if (node.nodeType === 1) {
  18277. // Ignore bogus elements
  18278. if (!node.getAttribute('data-mce-bogus')) {
  18279. // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
  18280. name = node.nodeName.toLowerCase();
  18281. if (nonEmptyElementsMap[name] && name !== 'br') {
  18282. return false;
  18283. }
  18284. }
  18285. } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
  18286. return false;
  18287. }
  18288. if (start) {
  18289. walker.prev();
  18290. } else {
  18291. walker.next();
  18292. }
  18293. }
  18294. return true;
  18295. }
  18296. // Wraps any text nodes or inline elements in the specified forced root block name
  18297. function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
  18298. var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
  18299. // Not in a block element or in a table cell or caption
  18300. parentBlock = dom.getParent(container, dom.isBlock);
  18301. if (!parentBlock || !canSplitBlock(parentBlock)) {
  18302. parentBlock = parentBlock || editableRoot;
  18303. if (parentBlock == editor.getBody() || isTableCell(parentBlock)) {
  18304. rootBlockName = parentBlock.nodeName.toLowerCase();
  18305. } else {
  18306. rootBlockName = parentBlock.parentNode.nodeName.toLowerCase();
  18307. }
  18308. if (!parentBlock.hasChildNodes()) {
  18309. newBlock = dom.create(blockName);
  18310. setForcedBlockAttrs(newBlock);
  18311. parentBlock.appendChild(newBlock);
  18312. rng.setStart(newBlock, 0);
  18313. rng.setEnd(newBlock, 0);
  18314. return newBlock;
  18315. }
  18316. // Find parent that is the first child of parentBlock
  18317. node = container;
  18318. while (node.parentNode != parentBlock) {
  18319. node = node.parentNode;
  18320. }
  18321. // Loop left to find start node start wrapping at
  18322. while (node && !dom.isBlock(node)) {
  18323. startNode = node;
  18324. node = node.previousSibling;
  18325. }
  18326. if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
  18327. newBlock = dom.create(blockName);
  18328. setForcedBlockAttrs(newBlock);
  18329. startNode.parentNode.insertBefore(newBlock, startNode);
  18330. // Start wrapping until we hit a block
  18331. node = startNode;
  18332. while (node && !dom.isBlock(node)) {
  18333. next = node.nextSibling;
  18334. newBlock.appendChild(node);
  18335. node = next;
  18336. }
  18337. // Restore range to it's past location
  18338. rng.setStart(container, offset);
  18339. rng.setEnd(container, offset);
  18340. }
  18341. }
  18342. return container;
  18343. }
  18344. // Inserts a block or br before/after or in the middle of a split list of the LI is empty
  18345. function handleEmptyListItem() {
  18346. function isFirstOrLastLi(first) {
  18347. var node = containerBlock[first ? 'firstChild' : 'lastChild'];
  18348. // Find first/last element since there might be whitespace there
  18349. while (node) {
  18350. if (node.nodeType == 1) {
  18351. break;
  18352. }
  18353. node = node[first ? 'nextSibling' : 'previousSibling'];
  18354. }
  18355. return node === parentBlock;
  18356. }
  18357. function getContainerBlock() {
  18358. var containerBlockParent = containerBlock.parentNode;
  18359. if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
  18360. return containerBlockParent;
  18361. }
  18362. return containerBlock;
  18363. }
  18364. if (containerBlock == editor.getBody()) {
  18365. return;
  18366. }
  18367. // Check if we are in an nested list
  18368. var containerBlockParentName = containerBlock.parentNode.nodeName;
  18369. if (/^(OL|UL|LI)$/.test(containerBlockParentName)) {
  18370. newBlockName = 'LI';
  18371. }
  18372. newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
  18373. if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
  18374. if (containerBlockParentName == 'LI') {
  18375. // Nested list is inside a LI
  18376. dom.insertAfter(newBlock, getContainerBlock());
  18377. } else {
  18378. // Is first and last list item then replace the OL/UL with a text block
  18379. dom.replace(newBlock, containerBlock);
  18380. }
  18381. } else if (isFirstOrLastLi(true)) {
  18382. if (containerBlockParentName == 'LI') {
  18383. // List nested in an LI then move the list to a new sibling LI
  18384. dom.insertAfter(newBlock, getContainerBlock());
  18385. newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
  18386. newBlock.appendChild(containerBlock);
  18387. } else {
  18388. // First LI in list then remove LI and add text block before list
  18389. containerBlock.parentNode.insertBefore(newBlock, containerBlock);
  18390. }
  18391. } else if (isFirstOrLastLi()) {
  18392. // Last LI in list then remove LI and add text block after list
  18393. dom.insertAfter(newBlock, getContainerBlock());
  18394. renderBlockOnIE(newBlock);
  18395. } else {
  18396. // Middle LI in list the split the list and insert a text block in the middle
  18397. // Extract after fragment and insert it after the current block
  18398. containerBlock = getContainerBlock();
  18399. tmpRng = rng.cloneRange();
  18400. tmpRng.setStartAfter(parentBlock);
  18401. tmpRng.setEndAfter(containerBlock);
  18402. fragment = tmpRng.extractContents();
  18403. if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') {
  18404. newBlock = fragment.firstChild;
  18405. dom.insertAfter(fragment, containerBlock);
  18406. } else {
  18407. dom.insertAfter(fragment, containerBlock);
  18408. dom.insertAfter(newBlock, containerBlock);
  18409. }
  18410. }
  18411. dom.remove(parentBlock);
  18412. moveToCaretPosition(newBlock);
  18413. undoManager.add();
  18414. }
  18415. // Inserts a BR element if the forced_root_block option is set to false or empty string
  18416. function insertBr() {
  18417. editor.execCommand("InsertLineBreak", false, evt);
  18418. }
  18419. // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
  18420. function trimLeadingLineBreaks(node) {
  18421. do {
  18422. if (node.nodeType === 3) {
  18423. node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
  18424. }
  18425. node = node.firstChild;
  18426. } while (node);
  18427. }
  18428. function getEditableRoot(node) {
  18429. var root = dom.getRoot(), parent, editableRoot;
  18430. // Get all parents until we hit a non editable parent or the root
  18431. parent = node;
  18432. while (parent !== root && dom.getContentEditable(parent) !== "false") {
  18433. if (dom.getContentEditable(parent) === "true") {
  18434. editableRoot = parent;
  18435. }
  18436. parent = parent.parentNode;
  18437. }
  18438. return parent !== root ? editableRoot : root;
  18439. }
  18440. // Adds a BR at the end of blocks that only contains an IMG or INPUT since
  18441. // these might be floated and then they won't expand the block
  18442. function addBrToBlockIfNeeded(block) {
  18443. var lastChild;
  18444. // IE will render the blocks correctly other browsers needs a BR
  18445. if (!isIE) {
  18446. block.normalize(); // Remove empty text nodes that got left behind by the extract
  18447. // Check if the block is empty or contains a floated last child
  18448. lastChild = block.lastChild;
  18449. if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
  18450. dom.add(block, 'br');
  18451. }
  18452. }
  18453. }
  18454. function insertNewBlockAfter() {
  18455. // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
  18456. if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
  18457. newBlock = createNewBlock(newBlockName);
  18458. } else {
  18459. newBlock = createNewBlock();
  18460. }
  18461. // Split the current container block element if enter is pressed inside an empty inner block element
  18462. if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
  18463. // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
  18464. newBlock = dom.split(containerBlock, parentBlock);
  18465. } else {
  18466. dom.insertAfter(newBlock, parentBlock);
  18467. }
  18468. moveToCaretPosition(newBlock);
  18469. }
  18470. rng = selection.getRng(true);
  18471. // Event is blocked by some other handler for example the lists plugin
  18472. if (evt.isDefaultPrevented()) {
  18473. return;
  18474. }
  18475. // Delete any selected contents
  18476. if (!rng.collapsed) {
  18477. editor.execCommand('Delete');
  18478. return;
  18479. }
  18480. // Setup range items and newBlockName
  18481. new RangeUtils(dom).normalize(rng);
  18482. container = rng.startContainer;
  18483. offset = rng.startOffset;
  18484. newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
  18485. newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
  18486. documentMode = dom.doc.documentMode;
  18487. shiftKey = evt.shiftKey;
  18488. // Resolve node index
  18489. if (container.nodeType == 1 && container.hasChildNodes()) {
  18490. isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
  18491. container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
  18492. if (isAfterLastNodeInContainer && container.nodeType == 3) {
  18493. offset = container.nodeValue.length;
  18494. } else {
  18495. offset = 0;
  18496. }
  18497. }
  18498. // Get editable root node, normally the body element but sometimes a div or span
  18499. editableRoot = getEditableRoot(container);
  18500. // If there is no editable root then enter is done inside a contentEditable false element
  18501. if (!editableRoot) {
  18502. return;
  18503. }
  18504. undoManager.beforeChange();
  18505. // If editable root isn't block nor the root of the editor
  18506. if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
  18507. if (!newBlockName || shiftKey) {
  18508. insertBr();
  18509. }
  18510. return;
  18511. }
  18512. // Wrap the current node and it's sibling in a default block if it's needed.
  18513. // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
  18514. // This won't happen if root blocks are disabled or the shiftKey is pressed
  18515. if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
  18516. container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
  18517. }
  18518. // Find parent block and setup empty block paddings
  18519. parentBlock = dom.getParent(container, dom.isBlock);
  18520. containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
  18521. // Setup block names
  18522. parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  18523. containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  18524. // Enter inside block contained within a LI then split or insert before/after LI
  18525. if (containerBlockName == 'LI' && !evt.ctrlKey) {
  18526. parentBlock = containerBlock;
  18527. parentBlockName = containerBlockName;
  18528. }
  18529. // Handle enter in list item
  18530. if (/^(LI|DT|DD)$/.test(parentBlockName)) {
  18531. if (!newBlockName && shiftKey) {
  18532. insertBr();
  18533. return;
  18534. }
  18535. // Handle enter inside an empty list item
  18536. if (dom.isEmpty(parentBlock)) {
  18537. handleEmptyListItem();
  18538. return;
  18539. }
  18540. }
  18541. // Don't split PRE tags but insert a BR instead easier when writing code samples etc
  18542. if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
  18543. if (!shiftKey) {
  18544. insertBr();
  18545. return;
  18546. }
  18547. } else {
  18548. // If no root block is configured then insert a BR by default or if the shiftKey is pressed
  18549. if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
  18550. insertBr();
  18551. return;
  18552. }
  18553. }
  18554. // If parent block is root then never insert new blocks
  18555. if (newBlockName && parentBlock === editor.getBody()) {
  18556. return;
  18557. }
  18558. // Default block name if it's not configured
  18559. newBlockName = newBlockName || 'P';
  18560. // Insert new block before/after the parent block depending on caret location
  18561. if (isCaretAtStartOrEndOfBlock()) {
  18562. insertNewBlockAfter();
  18563. } else if (isCaretAtStartOrEndOfBlock(true)) {
  18564. // Insert new block before
  18565. newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
  18566. renderBlockOnIE(newBlock);
  18567. moveToCaretPosition(parentBlock);
  18568. } else {
  18569. // Extract after fragment and insert it after the current block
  18570. tmpRng = rng.cloneRange();
  18571. tmpRng.setEndAfter(parentBlock);
  18572. fragment = tmpRng.extractContents();
  18573. trimLeadingLineBreaks(fragment);
  18574. newBlock = fragment.firstChild;
  18575. dom.insertAfter(fragment, parentBlock);
  18576. trimInlineElementsOnLeftSideOfBlock(newBlock);
  18577. addBrToBlockIfNeeded(parentBlock);
  18578. if (dom.isEmpty(parentBlock)) {
  18579. emptyBlock(parentBlock);
  18580. }
  18581. newBlock.normalize();
  18582. // New block might become empty if it's <p><b>a |</b></p>
  18583. if (dom.isEmpty(newBlock)) {
  18584. dom.remove(newBlock);
  18585. insertNewBlockAfter();
  18586. } else {
  18587. moveToCaretPosition(newBlock);
  18588. }
  18589. }
  18590. dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
  18591. // Allow custom handling of new blocks
  18592. editor.fire('NewBlock', {newBlock: newBlock});
  18593. undoManager.add();
  18594. }
  18595. editor.on('keydown', function(evt) {
  18596. if (evt.keyCode == 13) {
  18597. if (handleEnterKey(evt) !== false) {
  18598. evt.preventDefault();
  18599. }
  18600. }
  18601. });
  18602. };
  18603. });
  18604. // Included from: js/tinymce/classes/ForceBlocks.js
  18605. /**
  18606. * ForceBlocks.js
  18607. *
  18608. * Released under LGPL License.
  18609. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  18610. *
  18611. * License: http://www.tinymce.com/license
  18612. * Contributing: http://www.tinymce.com/contributing
  18613. */
  18614. /**
  18615. * Makes sure that everything gets wrapped in paragraphs.
  18616. *
  18617. * @private
  18618. * @class tinymce.ForceBlocks
  18619. */
  18620. define("tinymce/ForceBlocks", [], function() {
  18621. return function(editor) {
  18622. var settings = editor.settings, dom = editor.dom, selection = editor.selection;
  18623. var schema = editor.schema, blockElements = schema.getBlockElements();
  18624. function addRootBlocks() {
  18625. var node = selection.getStart(), rootNode = editor.getBody(), rng;
  18626. var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
  18627. var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
  18628. var tmpRng, rootNodeName, forcedRootBlock;
  18629. forcedRootBlock = settings.forced_root_block;
  18630. if (!node || node.nodeType !== 1 || !forcedRootBlock) {
  18631. return;
  18632. }
  18633. // Check if node is wrapped in block
  18634. while (node && node != rootNode) {
  18635. if (blockElements[node.nodeName]) {
  18636. return;
  18637. }
  18638. node = node.parentNode;
  18639. }
  18640. // Get current selection
  18641. rng = selection.getRng();
  18642. if (rng.setStart) {
  18643. startContainer = rng.startContainer;
  18644. startOffset = rng.startOffset;
  18645. endContainer = rng.endContainer;
  18646. endOffset = rng.endOffset;
  18647. try {
  18648. restoreSelection = editor.getDoc().activeElement === rootNode;
  18649. } catch (ex) {
  18650. // IE throws unspecified error here sometimes
  18651. }
  18652. } else {
  18653. // Force control range into text range
  18654. if (rng.item) {
  18655. node = rng.item(0);
  18656. rng = editor.getDoc().body.createTextRange();
  18657. rng.moveToElementText(node);
  18658. }
  18659. restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
  18660. tmpRng = rng.duplicate();
  18661. tmpRng.collapse(true);
  18662. startOffset = tmpRng.move('character', offset) * -1;
  18663. if (!tmpRng.collapsed) {
  18664. tmpRng = rng.duplicate();
  18665. tmpRng.collapse(false);
  18666. endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
  18667. }
  18668. }
  18669. // Wrap non block elements and text nodes
  18670. node = rootNode.firstChild;
  18671. rootNodeName = rootNode.nodeName.toLowerCase();
  18672. while (node) {
  18673. // TODO: Break this up, too complex
  18674. if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
  18675. schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
  18676. // Remove empty text nodes
  18677. if (node.nodeType === 3 && node.nodeValue.length === 0) {
  18678. tempNode = node;
  18679. node = node.nextSibling;
  18680. dom.remove(tempNode);
  18681. continue;
  18682. }
  18683. if (!rootBlockNode) {
  18684. rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
  18685. node.parentNode.insertBefore(rootBlockNode, node);
  18686. wrapped = true;
  18687. }
  18688. tempNode = node;
  18689. node = node.nextSibling;
  18690. rootBlockNode.appendChild(tempNode);
  18691. } else {
  18692. rootBlockNode = null;
  18693. node = node.nextSibling;
  18694. }
  18695. }
  18696. if (wrapped && restoreSelection) {
  18697. if (rng.setStart) {
  18698. rng.setStart(startContainer, startOffset);
  18699. rng.setEnd(endContainer, endOffset);
  18700. selection.setRng(rng);
  18701. } else {
  18702. // Only select if the previous selection was inside the document to prevent auto focus in quirks mode
  18703. try {
  18704. rng = editor.getDoc().body.createTextRange();
  18705. rng.moveToElementText(rootNode);
  18706. rng.collapse(true);
  18707. rng.moveStart('character', startOffset);
  18708. if (endOffset > 0) {
  18709. rng.moveEnd('character', endOffset);
  18710. }
  18711. rng.select();
  18712. } catch (ex) {
  18713. // Ignore
  18714. }
  18715. }
  18716. editor.nodeChanged();
  18717. }
  18718. }
  18719. // Force root blocks
  18720. if (settings.forced_root_block) {
  18721. editor.on('NodeChange', addRootBlocks);
  18722. }
  18723. };
  18724. });
  18725. // Included from: js/tinymce/classes/caret/CaretUtils.js
  18726. /**
  18727. * CaretUtils.js
  18728. *
  18729. * Released under LGPL License.
  18730. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  18731. *
  18732. * License: http://www.tinymce.com/license
  18733. * Contributing: http://www.tinymce.com/contributing
  18734. */
  18735. /**
  18736. * Utility functions shared by the caret logic.
  18737. *
  18738. * @private
  18739. * @class tinymce.caret.CaretUtils
  18740. */
  18741. define("tinymce/caret/CaretUtils", [
  18742. "tinymce/util/Fun",
  18743. "tinymce/dom/TreeWalker",
  18744. "tinymce/dom/NodeType",
  18745. "tinymce/caret/CaretPosition",
  18746. "tinymce/caret/CaretContainer",
  18747. "tinymce/caret/CaretCandidate"
  18748. ], function(Fun, TreeWalker, NodeType, CaretPosition, CaretContainer, CaretCandidate) {
  18749. var isContentEditableTrue = NodeType.isContentEditableTrue,
  18750. isContentEditableFalse = NodeType.isContentEditableFalse,
  18751. isBlockLike = NodeType.matchStyleValues('display', 'block table table-cell table-caption'),
  18752. isCaretContainer = CaretContainer.isCaretContainer,
  18753. curry = Fun.curry,
  18754. isElement = NodeType.isElement,
  18755. isCaretCandidate = CaretCandidate.isCaretCandidate;
  18756. function isForwards(direction) {
  18757. return direction > 0;
  18758. }
  18759. function isBackwards(direction) {
  18760. return direction < 0;
  18761. }
  18762. function findNode(node, direction, predicateFn, rootNode, shallow) {
  18763. var walker = new TreeWalker(node, rootNode);
  18764. if (isBackwards(direction)) {
  18765. if (isContentEditableFalse(node)) {
  18766. node = walker.prev(true);
  18767. if (predicateFn(node)) {
  18768. return node;
  18769. }
  18770. }
  18771. while ((node = walker.prev(shallow))) {
  18772. if (predicateFn(node)) {
  18773. return node;
  18774. }
  18775. }
  18776. }
  18777. if (isForwards(direction)) {
  18778. if (isContentEditableFalse(node)) {
  18779. node = walker.next(true);
  18780. if (predicateFn(node)) {
  18781. return node;
  18782. }
  18783. }
  18784. while ((node = walker.next(shallow))) {
  18785. if (predicateFn(node)) {
  18786. return node;
  18787. }
  18788. }
  18789. }
  18790. return null;
  18791. }
  18792. function getEditingHost(node, rootNode) {
  18793. for (node = node.parentNode; node && node != rootNode; node = node.parentNode) {
  18794. if (isContentEditableTrue(node)) {
  18795. return node;
  18796. }
  18797. }
  18798. return rootNode;
  18799. }
  18800. function getParentBlock(node, rootNode) {
  18801. while (node && node != rootNode) {
  18802. if (isBlockLike(node)) {
  18803. return node;
  18804. }
  18805. node = node.parentNode;
  18806. }
  18807. return null;
  18808. }
  18809. function isInSameBlock(caretPosition1, caretPosition2, rootNode) {
  18810. return getParentBlock(caretPosition1.container(), rootNode) == getParentBlock(caretPosition2.container(), rootNode);
  18811. }
  18812. function isInSameEditingHost(caretPosition1, caretPosition2, rootNode) {
  18813. return getEditingHost(caretPosition1.container(), rootNode) == getEditingHost(caretPosition2.container(), rootNode);
  18814. }
  18815. function getChildNodeAtRelativeOffset(relativeOffset, caretPosition) {
  18816. var container, offset;
  18817. if (!caretPosition) {
  18818. return null;
  18819. }
  18820. container = caretPosition.container();
  18821. offset = caretPosition.offset();
  18822. if (!isElement(container)) {
  18823. return null;
  18824. }
  18825. return container.childNodes[offset + relativeOffset];
  18826. }
  18827. function beforeAfter(before, node) {
  18828. var range = node.ownerDocument.createRange();
  18829. if (before) {
  18830. range.setStartBefore(node);
  18831. range.setEndBefore(node);
  18832. } else {
  18833. range.setStartAfter(node);
  18834. range.setEndAfter(node);
  18835. }
  18836. return range;
  18837. }
  18838. function isNodesInSameBlock(rootNode, node1, node2) {
  18839. return getParentBlock(node1, rootNode) == getParentBlock(node2, rootNode);
  18840. }
  18841. function lean(left, rootNode, node) {
  18842. var sibling, siblingName;
  18843. if (left) {
  18844. siblingName = 'previousSibling';
  18845. } else {
  18846. siblingName = 'nextSibling';
  18847. }
  18848. while (node && node != rootNode) {
  18849. sibling = node[siblingName];
  18850. if (isCaretContainer(sibling)) {
  18851. sibling = sibling[siblingName];
  18852. }
  18853. if (isContentEditableFalse(sibling)) {
  18854. if (isNodesInSameBlock(rootNode, sibling, node)) {
  18855. return sibling;
  18856. }
  18857. break;
  18858. }
  18859. if (isCaretCandidate(sibling)) {
  18860. break;
  18861. }
  18862. node = node.parentNode;
  18863. }
  18864. return null;
  18865. }
  18866. var before = curry(beforeAfter, true);
  18867. var after = curry(beforeAfter, false);
  18868. function normalizeRange(direction, rootNode, range) {
  18869. var node, container, offset, location;
  18870. var leanLeft = curry(lean, true, rootNode);
  18871. var leanRight = curry(lean, false, rootNode);
  18872. container = range.startContainer;
  18873. offset = range.startOffset;
  18874. if (CaretContainer.isCaretContainerBlock(container)) {
  18875. if (!isElement(container)) {
  18876. container = container.parentNode;
  18877. }
  18878. location = container.getAttribute('data-mce-caret');
  18879. if (location == 'before') {
  18880. node = container.nextSibling;
  18881. if (isContentEditableFalse(node)) {
  18882. return before(node);
  18883. }
  18884. }
  18885. if (location == 'after') {
  18886. node = container.previousSibling;
  18887. if (isContentEditableFalse(node)) {
  18888. return after(node);
  18889. }
  18890. }
  18891. }
  18892. if (!range.collapsed) {
  18893. return range;
  18894. }
  18895. if (NodeType.isText(container)) {
  18896. if (isCaretContainer(container)) {
  18897. if (direction === 1) {
  18898. node = leanRight(container);
  18899. if (node) {
  18900. return before(node);
  18901. }
  18902. node = leanLeft(container);
  18903. if (node) {
  18904. return after(node);
  18905. }
  18906. }
  18907. if (direction === -1) {
  18908. node = leanLeft(container);
  18909. if (node) {
  18910. return after(node);
  18911. }
  18912. node = leanRight(container);
  18913. if (node) {
  18914. return before(node);
  18915. }
  18916. }
  18917. return range;
  18918. }
  18919. if (CaretContainer.endsWithCaretContainer(container) && offset >= container.data.length - 1) {
  18920. if (direction === 1) {
  18921. node = leanRight(container);
  18922. if (node) {
  18923. return before(node);
  18924. }
  18925. }
  18926. return range;
  18927. }
  18928. if (CaretContainer.startsWithCaretContainer(container) && offset <= 1) {
  18929. if (direction === -1) {
  18930. node = leanLeft(container);
  18931. if (node) {
  18932. return after(node);
  18933. }
  18934. }
  18935. return range;
  18936. }
  18937. if (offset === container.data.length) {
  18938. node = leanRight(container);
  18939. if (node) {
  18940. return before(node);
  18941. }
  18942. return range;
  18943. }
  18944. if (offset === 0) {
  18945. node = leanLeft(container);
  18946. if (node) {
  18947. return after(node);
  18948. }
  18949. return range;
  18950. }
  18951. }
  18952. return range;
  18953. }
  18954. function isNextToContentEditableFalse(relativeOffset, caretPosition) {
  18955. return isContentEditableFalse(getChildNodeAtRelativeOffset(relativeOffset, caretPosition));
  18956. }
  18957. return {
  18958. isForwards: isForwards,
  18959. isBackwards: isBackwards,
  18960. findNode: findNode,
  18961. getEditingHost: getEditingHost,
  18962. getParentBlock: getParentBlock,
  18963. isInSameBlock: isInSameBlock,
  18964. isInSameEditingHost: isInSameEditingHost,
  18965. isBeforeContentEditableFalse: curry(isNextToContentEditableFalse, 0),
  18966. isAfterContentEditableFalse: curry(isNextToContentEditableFalse, -1),
  18967. normalizeRange: normalizeRange
  18968. };
  18969. });
  18970. // Included from: js/tinymce/classes/caret/CaretWalker.js
  18971. /**
  18972. * CaretWalker.js
  18973. *
  18974. * Released under LGPL License.
  18975. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  18976. *
  18977. * License: http://www.tinymce.com/license
  18978. * Contributing: http://www.tinymce.com/contributing
  18979. */
  18980. /**
  18981. * This module contains logic for moving around a virtual caret in logical order within a DOM element.
  18982. *
  18983. * It ignores the most obvious invalid caret locations such as within a script element or within a
  18984. * contentEditable=false element but it will return locations that isn't possible to render visually.
  18985. *
  18986. * @private
  18987. * @class tinymce.caret.CaretWalker
  18988. * @example
  18989. * var caretWalker = new CaretWalker(rootElm);
  18990. *
  18991. * var prevLogicalCaretPosition = caretWalker.prev(CaretPosition.fromRangeStart(range));
  18992. * var nextLogicalCaretPosition = caretWalker.next(CaretPosition.fromRangeEnd(range));
  18993. */
  18994. define("tinymce/caret/CaretWalker", [
  18995. "tinymce/dom/NodeType",
  18996. "tinymce/caret/CaretCandidate",
  18997. "tinymce/caret/CaretPosition",
  18998. "tinymce/caret/CaretUtils",
  18999. "tinymce/util/Arr",
  19000. "tinymce/util/Fun"
  19001. ], function(NodeType, CaretCandidate, CaretPosition, CaretUtils, Arr, Fun) {
  19002. var isContentEditableFalse = NodeType.isContentEditableFalse,
  19003. isText = NodeType.isText,
  19004. isElement = NodeType.isElement,
  19005. isBr = NodeType.isBr,
  19006. isForwards = CaretUtils.isForwards,
  19007. isBackwards = CaretUtils.isBackwards,
  19008. isCaretCandidate = CaretCandidate.isCaretCandidate,
  19009. isAtomic = CaretCandidate.isAtomic,
  19010. isEditableCaretCandidate = CaretCandidate.isEditableCaretCandidate;
  19011. function getParents(node, rootNode) {
  19012. var parents = [];
  19013. while (node && node != rootNode) {
  19014. parents.push(node);
  19015. node = node.parentNode;
  19016. }
  19017. return parents;
  19018. }
  19019. function nodeAtIndex(container, offset) {
  19020. if (container.hasChildNodes() && offset < container.childNodes.length) {
  19021. return container.childNodes[offset];
  19022. }
  19023. return null;
  19024. }
  19025. function getCaretCandidatePosition(direction, node) {
  19026. if (isForwards(direction)) {
  19027. if (isCaretCandidate(node.previousSibling) && !isText(node.previousSibling)) {
  19028. return CaretPosition.before(node);
  19029. }
  19030. if (isText(node)) {
  19031. return CaretPosition(node, 0);
  19032. }
  19033. }
  19034. if (isBackwards(direction)) {
  19035. if (isCaretCandidate(node.nextSibling) && !isText(node.nextSibling)) {
  19036. return CaretPosition.after(node);
  19037. }
  19038. if (isText(node)) {
  19039. return CaretPosition(node, node.data.length);
  19040. }
  19041. }
  19042. if (isBackwards(direction)) {
  19043. if (isBr(node)) {
  19044. return CaretPosition.before(node);
  19045. }
  19046. return CaretPosition.after(node);
  19047. }
  19048. return CaretPosition.before(node);
  19049. }
  19050. // Jumps over BR elements <p>|<br></p><p>a</p> -> <p><br></p><p>|a</p>
  19051. function isBrBeforeBlock(node, rootNode) {
  19052. var next;
  19053. if (!NodeType.isBr(node)) {
  19054. return false;
  19055. }
  19056. next = findCaretPosition(1, CaretPosition.after(node), rootNode);
  19057. if (!next) {
  19058. return false;
  19059. }
  19060. return !CaretUtils.isInSameBlock(CaretPosition.before(node), CaretPosition.before(next), rootNode);
  19061. }
  19062. function findCaretPosition(direction, startCaretPosition, rootNode) {
  19063. var container, offset, node, nextNode, innerNode,
  19064. rootContentEditableFalseElm, caretPosition;
  19065. if (!isElement(rootNode) || !startCaretPosition) {
  19066. return null;
  19067. }
  19068. caretPosition = startCaretPosition;
  19069. container = caretPosition.container();
  19070. offset = caretPosition.offset();
  19071. if (isText(container)) {
  19072. if (isBackwards(direction) && offset > 0) {
  19073. return CaretPosition(container, --offset);
  19074. }
  19075. if (isForwards(direction) && offset < container.length) {
  19076. return CaretPosition(container, ++offset);
  19077. }
  19078. node = container;
  19079. } else {
  19080. if (isBackwards(direction) && offset > 0) {
  19081. nextNode = nodeAtIndex(container, offset - 1);
  19082. if (isCaretCandidate(nextNode)) {
  19083. if (!isAtomic(nextNode)) {
  19084. innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
  19085. if (innerNode) {
  19086. if (isText(innerNode)) {
  19087. return CaretPosition(innerNode, innerNode.data.length);
  19088. }
  19089. return CaretPosition.after(innerNode);
  19090. }
  19091. }
  19092. if (isText(nextNode)) {
  19093. return CaretPosition(nextNode, nextNode.data.length);
  19094. }
  19095. return CaretPosition.before(nextNode);
  19096. }
  19097. }
  19098. if (isForwards(direction) && offset < container.childNodes.length) {
  19099. nextNode = nodeAtIndex(container, offset);
  19100. if (isCaretCandidate(nextNode)) {
  19101. if (isBrBeforeBlock(nextNode, rootNode)) {
  19102. return findCaretPosition(direction, CaretPosition.after(nextNode), rootNode);
  19103. }
  19104. if (!isAtomic(nextNode)) {
  19105. innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
  19106. if (innerNode) {
  19107. if (isText(innerNode)) {
  19108. return CaretPosition(innerNode, 0);
  19109. }
  19110. return CaretPosition.before(innerNode);
  19111. }
  19112. }
  19113. if (isText(nextNode)) {
  19114. return CaretPosition(nextNode, 0);
  19115. }
  19116. return CaretPosition.after(nextNode);
  19117. }
  19118. }
  19119. node = caretPosition.getNode();
  19120. }
  19121. if ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart())) {
  19122. node = CaretUtils.findNode(node, direction, Fun.constant(true), rootNode, true);
  19123. if (isEditableCaretCandidate(node)) {
  19124. return getCaretCandidatePosition(direction, node);
  19125. }
  19126. }
  19127. nextNode = CaretUtils.findNode(node, direction, isEditableCaretCandidate, rootNode);
  19128. rootContentEditableFalseElm = Arr.last(Arr.filter(getParents(container, rootNode), isContentEditableFalse));
  19129. if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) {
  19130. if (isForwards(direction)) {
  19131. caretPosition = CaretPosition.after(rootContentEditableFalseElm);
  19132. } else {
  19133. caretPosition = CaretPosition.before(rootContentEditableFalseElm);
  19134. }
  19135. return caretPosition;
  19136. }
  19137. if (nextNode) {
  19138. return getCaretCandidatePosition(direction, nextNode);
  19139. }
  19140. return null;
  19141. }
  19142. return function(rootNode) {
  19143. return {
  19144. /**
  19145. * Returns the next logical caret position from the specificed input
  19146. * caretPoisiton or null if there isn't any more positions left for example
  19147. * at the end specified root element.
  19148. *
  19149. * @method next
  19150. * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
  19151. * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
  19152. */
  19153. next: function(caretPosition) {
  19154. return findCaretPosition(1, caretPosition, rootNode);
  19155. },
  19156. /**
  19157. * Returns the previous logical caret position from the specificed input
  19158. * caretPoisiton or null if there isn't any more positions left for example
  19159. * at the end specified root element.
  19160. *
  19161. * @method prev
  19162. * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
  19163. * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
  19164. */
  19165. prev: function(caretPosition) {
  19166. return findCaretPosition(-1, caretPosition, rootNode);
  19167. }
  19168. };
  19169. };
  19170. });
  19171. // Included from: js/tinymce/classes/InsertList.js
  19172. /**
  19173. * InsertList.js
  19174. *
  19175. * Released under LGPL License.
  19176. * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
  19177. *
  19178. * License: http://www.tinymce.com/license
  19179. * Contributing: http://www.tinymce.com/contributing
  19180. */
  19181. /**
  19182. * Handles inserts of lists into the editor instance.
  19183. *
  19184. * @class tinymce.InsertList
  19185. * @private
  19186. */
  19187. define("tinymce/InsertList", [
  19188. "tinymce/util/Tools",
  19189. "tinymce/caret/CaretWalker",
  19190. "tinymce/caret/CaretPosition"
  19191. ], function(Tools, CaretWalker, CaretPosition) {
  19192. var isListFragment = function(fragment) {
  19193. var firstChild = fragment.firstChild;
  19194. var lastChild = fragment.lastChild;
  19195. // Skip meta since it's likely <meta><ul>..</ul>
  19196. if (firstChild && firstChild.name === 'meta') {
  19197. firstChild = firstChild.next;
  19198. }
  19199. // Skip mce_marker since it's likely <ul>..</ul><span id="mce_marker"></span>
  19200. if (lastChild && lastChild.attr('id') === 'mce_marker') {
  19201. lastChild = lastChild.prev;
  19202. }
  19203. if (!firstChild || firstChild !== lastChild) {
  19204. return false;
  19205. }
  19206. return firstChild.name === 'ul' || firstChild.name === 'ol';
  19207. };
  19208. var cleanupDomFragment = function (domFragment) {
  19209. var firstChild = domFragment.firstChild;
  19210. var lastChild = domFragment.lastChild;
  19211. // TODO: remove the meta tag from paste logic
  19212. if (firstChild && firstChild.nodeName === 'META') {
  19213. firstChild.parentNode.removeChild(firstChild);
  19214. }
  19215. if (lastChild && lastChild.id === 'mce_marker') {
  19216. lastChild.parentNode.removeChild(lastChild);
  19217. }
  19218. return domFragment;
  19219. };
  19220. var toDomFragment = function(dom, serializer, fragment) {
  19221. var html = serializer.serialize(fragment);
  19222. var domFragment = dom.createFragment(html);
  19223. return cleanupDomFragment(domFragment);
  19224. };
  19225. var listItems = function(elm) {
  19226. return Tools.grep(elm.childNodes, function(child) {
  19227. return child.nodeName === 'LI';
  19228. });
  19229. };
  19230. var isEmpty = function (elm) {
  19231. return !elm.firstChild;
  19232. };
  19233. var trimListItems = function(elms) {
  19234. return elms.length > 0 && isEmpty(elms[elms.length - 1]) ? elms.slice(0, -1) : elms;
  19235. };
  19236. var getParentLi = function(dom, node) {
  19237. var parentBlock = dom.getParent(node, dom.isBlock);
  19238. return parentBlock && parentBlock.nodeName === 'LI' ? parentBlock : null;
  19239. };
  19240. var isParentBlockLi = function(dom, node) {
  19241. return !!getParentLi(dom, node);
  19242. };
  19243. var getSplit = function(parentNode, rng) {
  19244. var beforeRng = rng.cloneRange();
  19245. var afterRng = rng.cloneRange();
  19246. beforeRng.setStartBefore(parentNode);
  19247. afterRng.setEndAfter(parentNode);
  19248. return [
  19249. beforeRng.cloneContents(),
  19250. afterRng.cloneContents()
  19251. ];
  19252. };
  19253. var findFirstIn = function(node, rootNode) {
  19254. var caretPos = CaretPosition.before(node);
  19255. var caretWalker = new CaretWalker(rootNode);
  19256. var newCaretPos = caretWalker.next(caretPos);
  19257. return newCaretPos ? newCaretPos.toRange() : null;
  19258. };
  19259. var findLastOf = function(node, rootNode) {
  19260. var caretPos = CaretPosition.after(node);
  19261. var caretWalker = new CaretWalker(rootNode);
  19262. var newCaretPos = caretWalker.prev(caretPos);
  19263. return newCaretPos ? newCaretPos.toRange() : null;
  19264. };
  19265. var insertMiddle = function(target, elms, rootNode, rng) {
  19266. var parts = getSplit(target, rng);
  19267. var parentElm = target.parentNode;
  19268. parentElm.insertBefore(parts[0], target);
  19269. Tools.each(elms, function(li) {
  19270. parentElm.insertBefore(li, target);
  19271. });
  19272. parentElm.insertBefore(parts[1], target);
  19273. parentElm.removeChild(target);
  19274. return findLastOf(elms[elms.length - 1], rootNode);
  19275. };
  19276. var insertBefore = function(target, elms, rootNode) {
  19277. var parentElm = target.parentNode;
  19278. Tools.each(elms, function(elm) {
  19279. parentElm.insertBefore(elm, target);
  19280. });
  19281. return findFirstIn(target, rootNode);
  19282. };
  19283. var insertAfter = function(target, elms, rootNode, dom) {
  19284. dom.insertAfter(elms.reverse(), target);
  19285. return findLastOf(elms[0], rootNode);
  19286. };
  19287. var insertAtCaret = function(serializer, dom, rng, fragment) {
  19288. var domFragment = toDomFragment(dom, serializer, fragment);
  19289. var liTarget = getParentLi(dom, rng.startContainer);
  19290. var liElms = trimListItems(listItems(domFragment.firstChild));
  19291. var BEGINNING = 1, END = 2;
  19292. var rootNode = dom.getRoot();
  19293. var isAt = function(location) {
  19294. var caretPos = CaretPosition.fromRangeStart(rng);
  19295. var caretWalker = new CaretWalker(dom.getRoot());
  19296. var newPos = location === BEGINNING ? caretWalker.prev(caretPos) : caretWalker.next(caretPos);
  19297. return newPos ? getParentLi(dom, newPos.getNode()) !== liTarget : true;
  19298. };
  19299. if (isAt(BEGINNING)) {
  19300. return insertBefore(liTarget, liElms, rootNode);
  19301. } else if (isAt(END)) {
  19302. return insertAfter(liTarget, liElms, rootNode, dom);
  19303. }
  19304. return insertMiddle(liTarget, liElms, rootNode, rng);
  19305. };
  19306. return {
  19307. isListFragment: isListFragment,
  19308. insertAtCaret: insertAtCaret,
  19309. isParentBlockLi: isParentBlockLi,
  19310. trimListItems: trimListItems,
  19311. listItems: listItems
  19312. };
  19313. });
  19314. // Included from: js/tinymce/classes/InsertContent.js
  19315. /**
  19316. * InsertContent.js
  19317. *
  19318. * Released under LGPL License.
  19319. * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
  19320. *
  19321. * License: http://www.tinymce.com/license
  19322. * Contributing: http://www.tinymce.com/contributing
  19323. */
  19324. /**
  19325. * Handles inserts of contents into the editor instance.
  19326. *
  19327. * @class tinymce.InsertContent
  19328. * @private
  19329. */
  19330. define("tinymce/InsertContent", [
  19331. "tinymce/Env",
  19332. "tinymce/util/Tools",
  19333. "tinymce/html/Serializer",
  19334. "tinymce/caret/CaretWalker",
  19335. "tinymce/caret/CaretPosition",
  19336. "tinymce/dom/ElementUtils",
  19337. "tinymce/dom/NodeType",
  19338. "tinymce/InsertList"
  19339. ], function(Env, Tools, Serializer, CaretWalker, CaretPosition, ElementUtils, NodeType, InsertList) {
  19340. var isTableCell = NodeType.matchNodeNames('td th');
  19341. var insertHtmlAtCaret = function(editor, value, details) {
  19342. var parser, serializer, parentNode, rootNode, fragment, args;
  19343. var marker, rng, node, node2, bookmarkHtml, merge;
  19344. var textInlineElements = editor.schema.getTextInlineElements();
  19345. var selection = editor.selection, dom = editor.dom;
  19346. function trimOrPaddLeftRight(html) {
  19347. var rng, container, offset;
  19348. rng = selection.getRng(true);
  19349. container = rng.startContainer;
  19350. offset = rng.startOffset;
  19351. function hasSiblingText(siblingName) {
  19352. return container[siblingName] && container[siblingName].nodeType == 3;
  19353. }
  19354. if (container.nodeType == 3) {
  19355. if (offset > 0) {
  19356. html = html.replace(/^&nbsp;/, ' ');
  19357. } else if (!hasSiblingText('previousSibling')) {
  19358. html = html.replace(/^ /, '&nbsp;');
  19359. }
  19360. if (offset < container.length) {
  19361. html = html.replace(/&nbsp;(<br>|)$/, ' ');
  19362. } else if (!hasSiblingText('nextSibling')) {
  19363. html = html.replace(/(&nbsp;| )(<br>|)$/, '&nbsp;');
  19364. }
  19365. }
  19366. return html;
  19367. }
  19368. // Removes &nbsp; from a [b] c -> a &nbsp;c -> a c
  19369. function trimNbspAfterDeleteAndPaddValue() {
  19370. var rng, container, offset;
  19371. rng = selection.getRng(true);
  19372. container = rng.startContainer;
  19373. offset = rng.startOffset;
  19374. if (container.nodeType == 3 && rng.collapsed) {
  19375. if (container.data[offset] === '\u00a0') {
  19376. container.deleteData(offset, 1);
  19377. if (!/[\u00a0| ]$/.test(value)) {
  19378. value += ' ';
  19379. }
  19380. } else if (container.data[offset - 1] === '\u00a0') {
  19381. container.deleteData(offset - 1, 1);
  19382. if (!/[\u00a0| ]$/.test(value)) {
  19383. value = ' ' + value;
  19384. }
  19385. }
  19386. }
  19387. }
  19388. function reduceInlineTextElements() {
  19389. if (merge) {
  19390. var root = editor.getBody(), elementUtils = new ElementUtils(dom);
  19391. Tools.each(dom.select('*[data-mce-fragment]'), function(node) {
  19392. for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
  19393. if (textInlineElements[node.nodeName.toLowerCase()] && elementUtils.compare(testNode, node)) {
  19394. dom.remove(node, true);
  19395. }
  19396. }
  19397. });
  19398. }
  19399. }
  19400. function markFragmentElements(fragment) {
  19401. var node = fragment;
  19402. while ((node = node.walk())) {
  19403. if (node.type === 1) {
  19404. node.attr('data-mce-fragment', '1');
  19405. }
  19406. }
  19407. }
  19408. function umarkFragmentElements(elm) {
  19409. Tools.each(elm.getElementsByTagName('*'), function(elm) {
  19410. elm.removeAttribute('data-mce-fragment');
  19411. });
  19412. }
  19413. function isPartOfFragment(node) {
  19414. return !!node.getAttribute('data-mce-fragment');
  19415. }
  19416. function canHaveChildren(node) {
  19417. return node && !editor.schema.getShortEndedElements()[node.nodeName];
  19418. }
  19419. function moveSelectionToMarker(marker) {
  19420. var parentEditableFalseElm, parentBlock, nextRng;
  19421. function getContentEditableFalseParent(node) {
  19422. var root = editor.getBody();
  19423. for (; node && node !== root; node = node.parentNode) {
  19424. if (editor.dom.getContentEditable(node) === 'false') {
  19425. return node;
  19426. }
  19427. }
  19428. return null;
  19429. }
  19430. if (!marker) {
  19431. return;
  19432. }
  19433. selection.scrollIntoView(marker);
  19434. // If marker is in cE=false then move selection to that element instead
  19435. parentEditableFalseElm = getContentEditableFalseParent(marker);
  19436. if (parentEditableFalseElm) {
  19437. dom.remove(marker);
  19438. selection.select(parentEditableFalseElm);
  19439. return;
  19440. }
  19441. // Move selection before marker and remove it
  19442. rng = dom.createRng();
  19443. // If previous sibling is a text node set the selection to the end of that node
  19444. node = marker.previousSibling;
  19445. if (node && node.nodeType == 3) {
  19446. rng.setStart(node, node.nodeValue.length);
  19447. // TODO: Why can't we normalize on IE
  19448. if (!Env.ie) {
  19449. node2 = marker.nextSibling;
  19450. if (node2 && node2.nodeType == 3) {
  19451. node.appendData(node2.data);
  19452. node2.parentNode.removeChild(node2);
  19453. }
  19454. }
  19455. } else {
  19456. // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
  19457. rng.setStartBefore(marker);
  19458. rng.setEndBefore(marker);
  19459. }
  19460. function findNextCaretRng(rng) {
  19461. var caretPos = CaretPosition.fromRangeStart(rng);
  19462. var caretWalker = new CaretWalker(editor.getBody());
  19463. caretPos = caretWalker.next(caretPos);
  19464. if (caretPos) {
  19465. return caretPos.toRange();
  19466. }
  19467. }
  19468. // Remove the marker node and set the new range
  19469. parentBlock = dom.getParent(marker, dom.isBlock);
  19470. dom.remove(marker);
  19471. if (parentBlock && dom.isEmpty(parentBlock)) {
  19472. editor.$(parentBlock).empty();
  19473. rng.setStart(parentBlock, 0);
  19474. rng.setEnd(parentBlock, 0);
  19475. if (!isTableCell(parentBlock) && !isPartOfFragment(parentBlock) && (nextRng = findNextCaretRng(rng))) {
  19476. rng = nextRng;
  19477. dom.remove(parentBlock);
  19478. } else {
  19479. dom.add(parentBlock, dom.create('br', {'data-mce-bogus': '1'}));
  19480. }
  19481. }
  19482. selection.setRng(rng);
  19483. }
  19484. // Check for whitespace before/after value
  19485. if (/^ | $/.test(value)) {
  19486. value = trimOrPaddLeftRight(value);
  19487. }
  19488. // Setup parser and serializer
  19489. parser = editor.parser;
  19490. merge = details.merge;
  19491. serializer = new Serializer({
  19492. validate: editor.settings.validate
  19493. }, editor.schema);
  19494. bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#x200B;</span>';
  19495. // Run beforeSetContent handlers on the HTML to be inserted
  19496. args = {content: value, format: 'html', selection: true};
  19497. editor.fire('BeforeSetContent', args);
  19498. value = args.content;
  19499. // Add caret at end of contents if it's missing
  19500. if (value.indexOf('{$caret}') == -1) {
  19501. value += '{$caret}';
  19502. }
  19503. // Replace the caret marker with a span bookmark element
  19504. value = value.replace(/\{\$caret\}/, bookmarkHtml);
  19505. // If selection is at <body>|<p></p> then move it into <body><p>|</p>
  19506. rng = selection.getRng();
  19507. var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
  19508. var body = editor.getBody();
  19509. if (caretElement === body && selection.isCollapsed()) {
  19510. if (dom.isBlock(body.firstChild) && canHaveChildren(body.firstChild) && dom.isEmpty(body.firstChild)) {
  19511. rng = dom.createRng();
  19512. rng.setStart(body.firstChild, 0);
  19513. rng.setEnd(body.firstChild, 0);
  19514. selection.setRng(rng);
  19515. }
  19516. }
  19517. // Insert node maker where we will insert the new HTML and get it's parent
  19518. if (!selection.isCollapsed()) {
  19519. // Fix for #2595 seems that delete removes one extra character on
  19520. // WebKit for some odd reason if you double click select a word
  19521. editor.selection.setRng(editor.selection.getRng());
  19522. editor.getDoc().execCommand('Delete', false, null);
  19523. trimNbspAfterDeleteAndPaddValue();
  19524. }
  19525. parentNode = selection.getNode();
  19526. // Parse the fragment within the context of the parent node
  19527. var parserArgs = {context: parentNode.nodeName.toLowerCase(), data: details.data};
  19528. fragment = parser.parse(value, parserArgs);
  19529. // Custom handling of lists
  19530. if (details.paste === true && InsertList.isListFragment(fragment) && InsertList.isParentBlockLi(dom, parentNode)) {
  19531. rng = InsertList.insertAtCaret(serializer, dom, editor.selection.getRng(true), fragment);
  19532. editor.selection.setRng(rng);
  19533. editor.fire('SetContent', args);
  19534. return;
  19535. }
  19536. markFragmentElements(fragment);
  19537. // Move the caret to a more suitable location
  19538. node = fragment.lastChild;
  19539. if (node.attr('id') == 'mce_marker') {
  19540. marker = node;
  19541. for (node = node.prev; node; node = node.walk(true)) {
  19542. if (node.type == 3 || !dom.isBlock(node.name)) {
  19543. if (editor.schema.isValidChild(node.parent.name, 'span')) {
  19544. node.parent.insert(marker, node, node.name === 'br');
  19545. }
  19546. break;
  19547. }
  19548. }
  19549. }
  19550. editor._selectionOverrides.showBlockCaretContainer(parentNode);
  19551. // If parser says valid we can insert the contents into that parent
  19552. if (!parserArgs.invalid) {
  19553. value = serializer.serialize(fragment);
  19554. // Check if parent is empty or only has one BR element then set the innerHTML of that parent
  19555. node = parentNode.firstChild;
  19556. node2 = parentNode.lastChild;
  19557. if (!node || (node === node2 && node.nodeName === 'BR')) {
  19558. dom.setHTML(parentNode, value);
  19559. } else {
  19560. selection.setContent(value);
  19561. }
  19562. } else {
  19563. // If the fragment was invalid within that context then we need
  19564. // to parse and process the parent it's inserted into
  19565. // Insert bookmark node and get the parent
  19566. selection.setContent(bookmarkHtml);
  19567. parentNode = selection.getNode();
  19568. rootNode = editor.getBody();
  19569. // Opera will return the document node when selection is in root
  19570. if (parentNode.nodeType == 9) {
  19571. parentNode = node = rootNode;
  19572. } else {
  19573. node = parentNode;
  19574. }
  19575. // Find the ancestor just before the root element
  19576. while (node !== rootNode) {
  19577. parentNode = node;
  19578. node = node.parentNode;
  19579. }
  19580. // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
  19581. value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
  19582. value = serializer.serialize(
  19583. parser.parse(
  19584. // Need to replace by using a function since $ in the contents would otherwise be a problem
  19585. value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
  19586. return serializer.serialize(fragment);
  19587. })
  19588. )
  19589. );
  19590. // Set the inner/outer HTML depending on if we are in the root or not
  19591. if (parentNode == rootNode) {
  19592. dom.setHTML(rootNode, value);
  19593. } else {
  19594. dom.setOuterHTML(parentNode, value);
  19595. }
  19596. }
  19597. reduceInlineTextElements();
  19598. moveSelectionToMarker(dom.get('mce_marker'));
  19599. umarkFragmentElements(editor.getBody());
  19600. editor.fire('SetContent', args);
  19601. editor.addVisual();
  19602. };
  19603. var processValue = function (value) {
  19604. var details;
  19605. if (typeof value !== 'string') {
  19606. details = Tools.extend({
  19607. paste: value.paste,
  19608. data: {
  19609. paste: value.paste
  19610. }
  19611. }, value);
  19612. return {
  19613. content: value.content,
  19614. details: details
  19615. };
  19616. }
  19617. return {
  19618. content: value,
  19619. details: {}
  19620. };
  19621. };
  19622. var insertAtCaret = function (editor, value) {
  19623. var result = processValue(value);
  19624. insertHtmlAtCaret(editor, result.content, result.details);
  19625. };
  19626. return {
  19627. insertAtCaret: insertAtCaret
  19628. };
  19629. });
  19630. // Included from: js/tinymce/classes/EditorCommands.js
  19631. /**
  19632. * EditorCommands.js
  19633. *
  19634. * Released under LGPL License.
  19635. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  19636. *
  19637. * License: http://www.tinymce.com/license
  19638. * Contributing: http://www.tinymce.com/contributing
  19639. */
  19640. /**
  19641. * This class enables you to add custom editor commands and it contains
  19642. * overrides for native browser commands to address various bugs and issues.
  19643. *
  19644. * @class tinymce.EditorCommands
  19645. */
  19646. define("tinymce/EditorCommands", [
  19647. "tinymce/Env",
  19648. "tinymce/util/Tools",
  19649. "tinymce/dom/RangeUtils",
  19650. "tinymce/dom/TreeWalker",
  19651. "tinymce/InsertContent"
  19652. ], function(Env, Tools, RangeUtils, TreeWalker, InsertContent) {
  19653. // Added for compression purposes
  19654. var each = Tools.each, extend = Tools.extend;
  19655. var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
  19656. var isOldIE = Env.ie && Env.ie < 11;
  19657. var TRUE = true, FALSE = false;
  19658. return function(editor) {
  19659. var dom, selection, formatter,
  19660. commands = {state: {}, exec: {}, value: {}},
  19661. settings = editor.settings,
  19662. bookmark;
  19663. editor.on('PreInit', function() {
  19664. dom = editor.dom;
  19665. selection = editor.selection;
  19666. settings = editor.settings;
  19667. formatter = editor.formatter;
  19668. });
  19669. /**
  19670. * Executes the specified command.
  19671. *
  19672. * @method execCommand
  19673. * @param {String} command Command to execute.
  19674. * @param {Boolean} ui Optional user interface state.
  19675. * @param {Object} value Optional value for command.
  19676. * @param {Object} args Optional extra arguments to the execCommand.
  19677. * @return {Boolean} true/false if the command was found or not.
  19678. */
  19679. function execCommand(command, ui, value, args) {
  19680. var func, customCommand, state = 0;
  19681. if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) {
  19682. editor.focus();
  19683. }
  19684. args = editor.fire('BeforeExecCommand', {command: command, ui: ui, value: value});
  19685. if (args.isDefaultPrevented()) {
  19686. return false;
  19687. }
  19688. customCommand = command.toLowerCase();
  19689. if ((func = commands.exec[customCommand])) {
  19690. func(customCommand, ui, value);
  19691. editor.fire('ExecCommand', {command: command, ui: ui, value: value});
  19692. return true;
  19693. }
  19694. // Plugin commands
  19695. each(editor.plugins, function(p) {
  19696. if (p.execCommand && p.execCommand(command, ui, value)) {
  19697. editor.fire('ExecCommand', {command: command, ui: ui, value: value});
  19698. state = true;
  19699. return false;
  19700. }
  19701. });
  19702. if (state) {
  19703. return state;
  19704. }
  19705. // Theme commands
  19706. if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) {
  19707. editor.fire('ExecCommand', {command: command, ui: ui, value: value});
  19708. return true;
  19709. }
  19710. // Browser commands
  19711. try {
  19712. state = editor.getDoc().execCommand(command, ui, value);
  19713. } catch (ex) {
  19714. // Ignore old IE errors
  19715. }
  19716. if (state) {
  19717. editor.fire('ExecCommand', {command: command, ui: ui, value: value});
  19718. return true;
  19719. }
  19720. return false;
  19721. }
  19722. /**
  19723. * Queries the current state for a command for example if the current selection is "bold".
  19724. *
  19725. * @method queryCommandState
  19726. * @param {String} command Command to check the state of.
  19727. * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
  19728. */
  19729. function queryCommandState(command) {
  19730. var func;
  19731. // Is hidden then return undefined
  19732. if (editor.quirks.isHidden()) {
  19733. return;
  19734. }
  19735. command = command.toLowerCase();
  19736. if ((func = commands.state[command])) {
  19737. return func(command);
  19738. }
  19739. // Browser commands
  19740. try {
  19741. return editor.getDoc().queryCommandState(command);
  19742. } catch (ex) {
  19743. // Fails sometimes see bug: 1896577
  19744. }
  19745. return false;
  19746. }
  19747. /**
  19748. * Queries the command value for example the current fontsize.
  19749. *
  19750. * @method queryCommandValue
  19751. * @param {String} command Command to check the value of.
  19752. * @return {Object} Command value of false if it's not found.
  19753. */
  19754. function queryCommandValue(command) {
  19755. var func;
  19756. // Is hidden then return undefined
  19757. if (editor.quirks.isHidden()) {
  19758. return;
  19759. }
  19760. command = command.toLowerCase();
  19761. if ((func = commands.value[command])) {
  19762. return func(command);
  19763. }
  19764. // Browser commands
  19765. try {
  19766. return editor.getDoc().queryCommandValue(command);
  19767. } catch (ex) {
  19768. // Fails sometimes see bug: 1896577
  19769. }
  19770. }
  19771. /**
  19772. * Adds commands to the command collection.
  19773. *
  19774. * @method addCommands
  19775. * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
  19776. * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
  19777. */
  19778. function addCommands(command_list, type) {
  19779. type = type || 'exec';
  19780. each(command_list, function(callback, command) {
  19781. each(command.toLowerCase().split(','), function(command) {
  19782. commands[type][command] = callback;
  19783. });
  19784. });
  19785. }
  19786. function addCommand(command, callback, scope) {
  19787. command = command.toLowerCase();
  19788. commands.exec[command] = function(command, ui, value, args) {
  19789. return callback.call(scope || editor, ui, value, args);
  19790. };
  19791. }
  19792. /**
  19793. * Returns true/false if the command is supported or not.
  19794. *
  19795. * @method queryCommandSupported
  19796. * @param {String} command Command that we check support for.
  19797. * @return {Boolean} true/false if the command is supported or not.
  19798. */
  19799. function queryCommandSupported(command) {
  19800. command = command.toLowerCase();
  19801. if (commands.exec[command]) {
  19802. return true;
  19803. }
  19804. // Browser commands
  19805. try {
  19806. return editor.getDoc().queryCommandSupported(command);
  19807. } catch (ex) {
  19808. // Fails sometimes see bug: 1896577
  19809. }
  19810. return false;
  19811. }
  19812. function addQueryStateHandler(command, callback, scope) {
  19813. command = command.toLowerCase();
  19814. commands.state[command] = function() {
  19815. return callback.call(scope || editor);
  19816. };
  19817. }
  19818. function addQueryValueHandler(command, callback, scope) {
  19819. command = command.toLowerCase();
  19820. commands.value[command] = function() {
  19821. return callback.call(scope || editor);
  19822. };
  19823. }
  19824. function hasCustomCommand(command) {
  19825. command = command.toLowerCase();
  19826. return !!commands.exec[command];
  19827. }
  19828. // Expose public methods
  19829. extend(this, {
  19830. execCommand: execCommand,
  19831. queryCommandState: queryCommandState,
  19832. queryCommandValue: queryCommandValue,
  19833. queryCommandSupported: queryCommandSupported,
  19834. addCommands: addCommands,
  19835. addCommand: addCommand,
  19836. addQueryStateHandler: addQueryStateHandler,
  19837. addQueryValueHandler: addQueryValueHandler,
  19838. hasCustomCommand: hasCustomCommand
  19839. });
  19840. // Private methods
  19841. function execNativeCommand(command, ui, value) {
  19842. if (ui === undefined) {
  19843. ui = FALSE;
  19844. }
  19845. if (value === undefined) {
  19846. value = null;
  19847. }
  19848. return editor.getDoc().execCommand(command, ui, value);
  19849. }
  19850. function isFormatMatch(name) {
  19851. return formatter.match(name);
  19852. }
  19853. function toggleFormat(name, value) {
  19854. formatter.toggle(name, value ? {value: value} : undefined);
  19855. editor.nodeChanged();
  19856. }
  19857. function storeSelection(type) {
  19858. bookmark = selection.getBookmark(type);
  19859. }
  19860. function restoreSelection() {
  19861. selection.moveToBookmark(bookmark);
  19862. }
  19863. // Add execCommand overrides
  19864. addCommands({
  19865. // Ignore these, added for compatibility
  19866. 'mceResetDesignMode,mceBeginUndoLevel': function() {},
  19867. // Add undo manager logic
  19868. 'mceEndUndoLevel,mceAddUndoLevel': function() {
  19869. editor.undoManager.add();
  19870. },
  19871. 'Cut,Copy,Paste': function(command) {
  19872. var doc = editor.getDoc(), failed;
  19873. // Try executing the native command
  19874. try {
  19875. execNativeCommand(command);
  19876. } catch (ex) {
  19877. // Command failed
  19878. failed = TRUE;
  19879. }
  19880. // Chrome reports the paste command as supported however older IE:s will return false for cut/paste
  19881. if (command === 'paste' && !doc.queryCommandEnabled(command)) {
  19882. failed = true;
  19883. }
  19884. // Present alert message about clipboard access not being available
  19885. if (failed || !doc.queryCommandSupported(command)) {
  19886. var msg = editor.translate(
  19887. "Your browser doesn't support direct access to the clipboard. " +
  19888. "Please use the Ctrl+X/C/V keyboard shortcuts instead."
  19889. );
  19890. if (Env.mac) {
  19891. msg = msg.replace(/Ctrl\+/g, '\u2318+');
  19892. }
  19893. editor.notificationManager.open({text: msg, type: 'error'});
  19894. }
  19895. },
  19896. // Override unlink command
  19897. unlink: function() {
  19898. if (selection.isCollapsed()) {
  19899. var elm = selection.getNode();
  19900. if (elm.tagName == 'A') {
  19901. editor.dom.remove(elm, true);
  19902. }
  19903. return;
  19904. }
  19905. formatter.remove("link");
  19906. },
  19907. // Override justify commands to use the text formatter engine
  19908. 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull,JustifyNone': function(command) {
  19909. var align = command.substring(7);
  19910. if (align == 'full') {
  19911. align = 'justify';
  19912. }
  19913. // Remove all other alignments first
  19914. each('left,center,right,justify'.split(','), function(name) {
  19915. if (align != name) {
  19916. formatter.remove('align' + name);
  19917. }
  19918. });
  19919. if (align != 'none') {
  19920. toggleFormat('align' + align);
  19921. }
  19922. },
  19923. // Override list commands to fix WebKit bug
  19924. 'InsertUnorderedList,InsertOrderedList': function(command) {
  19925. var listElm, listParent;
  19926. execNativeCommand(command);
  19927. // WebKit produces lists within block elements so we need to split them
  19928. // we will replace the native list creation logic to custom logic later on
  19929. // TODO: Remove this when the list creation logic is removed
  19930. listElm = dom.getParent(selection.getNode(), 'ol,ul');
  19931. if (listElm) {
  19932. listParent = listElm.parentNode;
  19933. // If list is within a text block then split that block
  19934. if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
  19935. storeSelection();
  19936. dom.split(listParent, listElm);
  19937. restoreSelection();
  19938. }
  19939. }
  19940. },
  19941. // Override commands to use the text formatter engine
  19942. 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
  19943. toggleFormat(command);
  19944. },
  19945. // Override commands to use the text formatter engine
  19946. 'ForeColor,HiliteColor,FontName': function(command, ui, value) {
  19947. toggleFormat(command, value);
  19948. },
  19949. FontSize: function(command, ui, value) {
  19950. var fontClasses, fontSizes;
  19951. // Convert font size 1-7 to styles
  19952. if (value >= 1 && value <= 7) {
  19953. fontSizes = explode(settings.font_size_style_values);
  19954. fontClasses = explode(settings.font_size_classes);
  19955. if (fontClasses) {
  19956. value = fontClasses[value - 1] || value;
  19957. } else {
  19958. value = fontSizes[value - 1] || value;
  19959. }
  19960. }
  19961. toggleFormat(command, value);
  19962. },
  19963. RemoveFormat: function(command) {
  19964. formatter.remove(command);
  19965. },
  19966. mceBlockQuote: function() {
  19967. toggleFormat('blockquote');
  19968. },
  19969. FormatBlock: function(command, ui, value) {
  19970. return toggleFormat(value || 'p');
  19971. },
  19972. mceCleanup: function() {
  19973. var bookmark = selection.getBookmark();
  19974. editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
  19975. selection.moveToBookmark(bookmark);
  19976. },
  19977. mceRemoveNode: function(command, ui, value) {
  19978. var node = value || selection.getNode();
  19979. // Make sure that the body node isn't removed
  19980. if (node != editor.getBody()) {
  19981. storeSelection();
  19982. editor.dom.remove(node, TRUE);
  19983. restoreSelection();
  19984. }
  19985. },
  19986. mceSelectNodeDepth: function(command, ui, value) {
  19987. var counter = 0;
  19988. dom.getParent(selection.getNode(), function(node) {
  19989. if (node.nodeType == 1 && counter++ == value) {
  19990. selection.select(node);
  19991. return FALSE;
  19992. }
  19993. }, editor.getBody());
  19994. },
  19995. mceSelectNode: function(command, ui, value) {
  19996. selection.select(value);
  19997. },
  19998. mceInsertContent: function(command, ui, value) {
  19999. InsertContent.insertAtCaret(editor, value);
  20000. },
  20001. mceInsertRawHTML: function(command, ui, value) {
  20002. selection.setContent('tiny_mce_marker');
  20003. editor.setContent(
  20004. editor.getContent().replace(/tiny_mce_marker/g, function() {
  20005. return value;
  20006. })
  20007. );
  20008. },
  20009. mceToggleFormat: function(command, ui, value) {
  20010. toggleFormat(value);
  20011. },
  20012. mceSetContent: function(command, ui, value) {
  20013. editor.setContent(value);
  20014. },
  20015. 'Indent,Outdent': function(command) {
  20016. var intentValue, indentUnit, value;
  20017. // Setup indent level
  20018. intentValue = settings.indentation;
  20019. indentUnit = /[a-z%]+$/i.exec(intentValue);
  20020. intentValue = parseInt(intentValue, 10);
  20021. if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
  20022. // If forced_root_blocks is set to false we don't have a block to indent so lets create a div
  20023. if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
  20024. formatter.apply('div');
  20025. }
  20026. each(selection.getSelectedBlocks(), function(element) {
  20027. if (dom.getContentEditable(element) === "false") {
  20028. return;
  20029. }
  20030. if (element.nodeName != "LI") {
  20031. var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';
  20032. indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';
  20033. if (command == 'outdent') {
  20034. value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
  20035. dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
  20036. } else {
  20037. value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
  20038. dom.setStyle(element, indentStyleName, value);
  20039. }
  20040. }
  20041. });
  20042. } else {
  20043. execNativeCommand(command);
  20044. }
  20045. },
  20046. mceRepaint: function() {
  20047. },
  20048. InsertHorizontalRule: function() {
  20049. editor.execCommand('mceInsertContent', false, '<hr />');
  20050. },
  20051. mceToggleVisualAid: function() {
  20052. editor.hasVisual = !editor.hasVisual;
  20053. editor.addVisual();
  20054. },
  20055. mceReplaceContent: function(command, ui, value) {
  20056. editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
  20057. },
  20058. mceInsertLink: function(command, ui, value) {
  20059. var anchor;
  20060. if (typeof value == 'string') {
  20061. value = {href: value};
  20062. }
  20063. anchor = dom.getParent(selection.getNode(), 'a');
  20064. // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
  20065. value.href = value.href.replace(' ', '%20');
  20066. // Remove existing links if there could be child links or that the href isn't specified
  20067. if (!anchor || !value.href) {
  20068. formatter.remove('link');
  20069. }
  20070. // Apply new link to selection
  20071. if (value.href) {
  20072. formatter.apply('link', value, anchor);
  20073. }
  20074. },
  20075. selectAll: function() {
  20076. var root = dom.getRoot(), rng;
  20077. if (selection.getRng().setStart) {
  20078. rng = dom.createRng();
  20079. rng.setStart(root, 0);
  20080. rng.setEnd(root, root.childNodes.length);
  20081. selection.setRng(rng);
  20082. } else {
  20083. // IE will render it's own root level block elements and sometimes
  20084. // even put font elements in them when the user starts typing. So we need to
  20085. // move the selection to a more suitable element from this:
  20086. // <body>|<p></p></body> to this: <body><p>|</p></body>
  20087. rng = selection.getRng();
  20088. if (!rng.item) {
  20089. rng.moveToElementText(root);
  20090. rng.select();
  20091. }
  20092. }
  20093. },
  20094. "delete": function() {
  20095. execNativeCommand("Delete");
  20096. // Check if body is empty after the delete call if so then set the contents
  20097. // to an empty string and move the caret to any block produced by that operation
  20098. // this fixes the issue with root blocks not being properly produced after a delete call on IE
  20099. var body = editor.getBody();
  20100. if (dom.isEmpty(body)) {
  20101. editor.setContent('');
  20102. if (body.firstChild && dom.isBlock(body.firstChild)) {
  20103. editor.selection.setCursorLocation(body.firstChild, 0);
  20104. } else {
  20105. editor.selection.setCursorLocation(body, 0);
  20106. }
  20107. }
  20108. },
  20109. mceNewDocument: function() {
  20110. editor.setContent('');
  20111. },
  20112. InsertLineBreak: function(command, ui, value) {
  20113. // We load the current event in from EnterKey.js when appropriate to heed
  20114. // certain event-specific variations such as ctrl-enter in a list
  20115. var evt = value;
  20116. var brElm, extraBr, marker;
  20117. var rng = selection.getRng(true);
  20118. new RangeUtils(dom).normalize(rng);
  20119. var offset = rng.startOffset;
  20120. var container = rng.startContainer;
  20121. // Resolve node index
  20122. if (container.nodeType == 1 && container.hasChildNodes()) {
  20123. var isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
  20124. container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
  20125. if (isAfterLastNodeInContainer && container.nodeType == 3) {
  20126. offset = container.nodeValue.length;
  20127. } else {
  20128. offset = 0;
  20129. }
  20130. }
  20131. var parentBlock = dom.getParent(container, dom.isBlock);
  20132. var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  20133. var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
  20134. var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  20135. // Enter inside block contained within a LI then split or insert before/after LI
  20136. var isControlKey = evt && evt.ctrlKey;
  20137. if (containerBlockName == 'LI' && !isControlKey) {
  20138. parentBlock = containerBlock;
  20139. parentBlockName = containerBlockName;
  20140. }
  20141. // Walks the parent block to the right and look for BR elements
  20142. function hasRightSideContent() {
  20143. var walker = new TreeWalker(container, parentBlock), node;
  20144. var nonEmptyElementsMap = editor.schema.getNonEmptyElements();
  20145. while ((node = walker.next())) {
  20146. if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
  20147. return true;
  20148. }
  20149. }
  20150. }
  20151. if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
  20152. // Insert extra BR element at the end block elements
  20153. if (!isOldIE && !hasRightSideContent()) {
  20154. brElm = dom.create('br');
  20155. rng.insertNode(brElm);
  20156. rng.setStartAfter(brElm);
  20157. rng.setEndAfter(brElm);
  20158. extraBr = true;
  20159. }
  20160. }
  20161. brElm = dom.create('br');
  20162. rng.insertNode(brElm);
  20163. // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
  20164. var documentMode = dom.doc.documentMode;
  20165. if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
  20166. brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
  20167. }
  20168. // Insert temp marker and scroll to that
  20169. marker = dom.create('span', {}, '&nbsp;');
  20170. brElm.parentNode.insertBefore(marker, brElm);
  20171. selection.scrollIntoView(marker);
  20172. dom.remove(marker);
  20173. if (!extraBr) {
  20174. rng.setStartAfter(brElm);
  20175. rng.setEndAfter(brElm);
  20176. } else {
  20177. rng.setStartBefore(brElm);
  20178. rng.setEndBefore(brElm);
  20179. }
  20180. selection.setRng(rng);
  20181. editor.undoManager.add();
  20182. return TRUE;
  20183. }
  20184. });
  20185. // Add queryCommandState overrides
  20186. addCommands({
  20187. // Override justify commands
  20188. 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
  20189. var name = 'align' + command.substring(7);
  20190. var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
  20191. var matches = map(nodes, function(node) {
  20192. return !!formatter.matchNode(node, name);
  20193. });
  20194. return inArray(matches, TRUE) !== -1;
  20195. },
  20196. 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
  20197. return isFormatMatch(command);
  20198. },
  20199. mceBlockQuote: function() {
  20200. return isFormatMatch('blockquote');
  20201. },
  20202. Outdent: function() {
  20203. var node;
  20204. if (settings.inline_styles) {
  20205. if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
  20206. return TRUE;
  20207. }
  20208. if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
  20209. return TRUE;
  20210. }
  20211. }
  20212. return (
  20213. queryCommandState('InsertUnorderedList') ||
  20214. queryCommandState('InsertOrderedList') ||
  20215. (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
  20216. );
  20217. },
  20218. 'InsertUnorderedList,InsertOrderedList': function(command) {
  20219. var list = dom.getParent(selection.getNode(), 'ul,ol');
  20220. return list &&
  20221. (
  20222. command === 'insertunorderedlist' && list.tagName === 'UL' ||
  20223. command === 'insertorderedlist' && list.tagName === 'OL'
  20224. );
  20225. }
  20226. }, 'state');
  20227. // Add queryCommandValue overrides
  20228. addCommands({
  20229. 'FontSize,FontName': function(command) {
  20230. var value = 0, parent;
  20231. if ((parent = dom.getParent(selection.getNode(), 'span'))) {
  20232. if (command == 'fontsize') {
  20233. value = parent.style.fontSize;
  20234. } else {
  20235. value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
  20236. }
  20237. }
  20238. return value;
  20239. }
  20240. }, 'value');
  20241. // Add undo manager logic
  20242. addCommands({
  20243. Undo: function() {
  20244. editor.undoManager.undo();
  20245. },
  20246. Redo: function() {
  20247. editor.undoManager.redo();
  20248. }
  20249. });
  20250. };
  20251. });
  20252. // Included from: js/tinymce/classes/util/URI.js
  20253. /**
  20254. * URI.js
  20255. *
  20256. * Released under LGPL License.
  20257. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  20258. *
  20259. * License: http://www.tinymce.com/license
  20260. * Contributing: http://www.tinymce.com/contributing
  20261. */
  20262. /**
  20263. * This class handles parsing, modification and serialization of URI/URL strings.
  20264. * @class tinymce.util.URI
  20265. */
  20266. define("tinymce/util/URI", [
  20267. "tinymce/util/Tools"
  20268. ], function(Tools) {
  20269. var each = Tools.each, trim = Tools.trim;
  20270. var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' ');
  20271. var DEFAULT_PORTS = {
  20272. 'ftp': 21,
  20273. 'http': 80,
  20274. 'https': 443,
  20275. 'mailto': 25
  20276. };
  20277. /**
  20278. * Constructs a new URI instance.
  20279. *
  20280. * @constructor
  20281. * @method URI
  20282. * @param {String} url URI string to parse.
  20283. * @param {Object} settings Optional settings object.
  20284. */
  20285. function URI(url, settings) {
  20286. var self = this, baseUri, base_url;
  20287. url = trim(url);
  20288. settings = self.settings = settings || {};
  20289. baseUri = settings.base_uri;
  20290. // Strange app protocol that isn't http/https or local anchor
  20291. // For example: mailto,skype,tel etc.
  20292. if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
  20293. self.source = url;
  20294. return;
  20295. }
  20296. var isProtocolRelative = url.indexOf('//') === 0;
  20297. // Absolute path with no host, fake host and protocol
  20298. if (url.indexOf('/') === 0 && !isProtocolRelative) {
  20299. url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url;
  20300. }
  20301. // Relative path http:// or protocol relative //path
  20302. if (!/^[\w\-]*:?\/\//.test(url)) {
  20303. base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory;
  20304. if (settings.base_uri.protocol === "") {
  20305. url = '//mce_host' + self.toAbsPath(base_url, url);
  20306. } else {
  20307. url = /([^#?]*)([#?]?.*)/.exec(url);
  20308. url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2];
  20309. }
  20310. }
  20311. // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
  20312. url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
  20313. /*jshint maxlen: 255 */
  20314. /*eslint max-len: 0 */
  20315. url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);
  20316. each(queryParts, function(v, i) {
  20317. var part = url[i];
  20318. // Zope 3 workaround, they use @@something
  20319. if (part) {
  20320. part = part.replace(/\(mce_at\)/g, '@@');
  20321. }
  20322. self[v] = part;
  20323. });
  20324. if (baseUri) {
  20325. if (!self.protocol) {
  20326. self.protocol = baseUri.protocol;
  20327. }
  20328. if (!self.userInfo) {
  20329. self.userInfo = baseUri.userInfo;
  20330. }
  20331. if (!self.port && self.host === 'mce_host') {
  20332. self.port = baseUri.port;
  20333. }
  20334. if (!self.host || self.host === 'mce_host') {
  20335. self.host = baseUri.host;
  20336. }
  20337. self.source = '';
  20338. }
  20339. if (isProtocolRelative) {
  20340. self.protocol = '';
  20341. }
  20342. //t.path = t.path || '/';
  20343. }
  20344. URI.prototype = {
  20345. /**
  20346. * Sets the internal path part of the URI.
  20347. *
  20348. * @method setPath
  20349. * @param {string} path Path string to set.
  20350. */
  20351. setPath: function(path) {
  20352. var self = this;
  20353. path = /^(.*?)\/?(\w+)?$/.exec(path);
  20354. // Update path parts
  20355. self.path = path[0];
  20356. self.directory = path[1];
  20357. self.file = path[2];
  20358. // Rebuild source
  20359. self.source = '';
  20360. self.getURI();
  20361. },
  20362. /**
  20363. * Converts the specified URI into a relative URI based on the current URI instance location.
  20364. *
  20365. * @method toRelative
  20366. * @param {String} uri URI to convert into a relative path/URI.
  20367. * @return {String} Relative URI from the point specified in the current URI instance.
  20368. * @example
  20369. * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm
  20370. * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm');
  20371. */
  20372. toRelative: function(uri) {
  20373. var self = this, output;
  20374. if (uri === "./") {
  20375. return uri;
  20376. }
  20377. uri = new URI(uri, {base_uri: self});
  20378. // Not on same domain/port or protocol
  20379. if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port ||
  20380. (self.protocol != uri.protocol && uri.protocol !== "")) {
  20381. return uri.getURI();
  20382. }
  20383. var tu = self.getURI(), uu = uri.getURI();
  20384. // Allow usage of the base_uri when relative_urls = true
  20385. if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) {
  20386. return tu;
  20387. }
  20388. output = self.toRelPath(self.path, uri.path);
  20389. // Add query
  20390. if (uri.query) {
  20391. output += '?' + uri.query;
  20392. }
  20393. // Add anchor
  20394. if (uri.anchor) {
  20395. output += '#' + uri.anchor;
  20396. }
  20397. return output;
  20398. },
  20399. /**
  20400. * Converts the specified URI into a absolute URI based on the current URI instance location.
  20401. *
  20402. * @method toAbsolute
  20403. * @param {String} uri URI to convert into a relative path/URI.
  20404. * @param {Boolean} noHost No host and protocol prefix.
  20405. * @return {String} Absolute URI from the point specified in the current URI instance.
  20406. * @example
  20407. * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm
  20408. * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
  20409. */
  20410. toAbsolute: function(uri, noHost) {
  20411. uri = new URI(uri, {base_uri: this});
  20412. return uri.getURI(noHost && this.isSameOrigin(uri));
  20413. },
  20414. /**
  20415. * Determine whether the given URI has the same origin as this URI. Based on RFC-6454.
  20416. * Supports default ports for protocols listed in DEFAULT_PORTS. Unsupported protocols will fail safe: they
  20417. * won't match, if the port specifications differ.
  20418. *
  20419. * @method isSameOrigin
  20420. * @param {tinymce.util.URI} uri Uri instance to compare.
  20421. * @returns {Boolean} True if the origins are the same.
  20422. */
  20423. isSameOrigin: function(uri) {
  20424. if (this.host == uri.host && this.protocol == uri.protocol) {
  20425. if (this.port == uri.port) {
  20426. return true;
  20427. }
  20428. var defaultPort = DEFAULT_PORTS[this.protocol];
  20429. if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) {
  20430. return true;
  20431. }
  20432. }
  20433. return false;
  20434. },
  20435. /**
  20436. * Converts a absolute path into a relative path.
  20437. *
  20438. * @method toRelPath
  20439. * @param {String} base Base point to convert the path from.
  20440. * @param {String} path Absolute path to convert into a relative path.
  20441. */
  20442. toRelPath: function(base, path) {
  20443. var items, breakPoint = 0, out = '', i, l;
  20444. // Split the paths
  20445. base = base.substring(0, base.lastIndexOf('/'));
  20446. base = base.split('/');
  20447. items = path.split('/');
  20448. if (base.length >= items.length) {
  20449. for (i = 0, l = base.length; i < l; i++) {
  20450. if (i >= items.length || base[i] != items[i]) {
  20451. breakPoint = i + 1;
  20452. break;
  20453. }
  20454. }
  20455. }
  20456. if (base.length < items.length) {
  20457. for (i = 0, l = items.length; i < l; i++) {
  20458. if (i >= base.length || base[i] != items[i]) {
  20459. breakPoint = i + 1;
  20460. break;
  20461. }
  20462. }
  20463. }
  20464. if (breakPoint === 1) {
  20465. return path;
  20466. }
  20467. for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) {
  20468. out += "../";
  20469. }
  20470. for (i = breakPoint - 1, l = items.length; i < l; i++) {
  20471. if (i != breakPoint - 1) {
  20472. out += "/" + items[i];
  20473. } else {
  20474. out += items[i];
  20475. }
  20476. }
  20477. return out;
  20478. },
  20479. /**
  20480. * Converts a relative path into a absolute path.
  20481. *
  20482. * @method toAbsPath
  20483. * @param {String} base Base point to convert the path from.
  20484. * @param {String} path Relative path to convert into an absolute path.
  20485. */
  20486. toAbsPath: function(base, path) {
  20487. var i, nb = 0, o = [], tr, outPath;
  20488. // Split paths
  20489. tr = /\/$/.test(path) ? '/' : '';
  20490. base = base.split('/');
  20491. path = path.split('/');
  20492. // Remove empty chunks
  20493. each(base, function(k) {
  20494. if (k) {
  20495. o.push(k);
  20496. }
  20497. });
  20498. base = o;
  20499. // Merge relURLParts chunks
  20500. for (i = path.length - 1, o = []; i >= 0; i--) {
  20501. // Ignore empty or .
  20502. if (path[i].length === 0 || path[i] === ".") {
  20503. continue;
  20504. }
  20505. // Is parent
  20506. if (path[i] === '..') {
  20507. nb++;
  20508. continue;
  20509. }
  20510. // Move up
  20511. if (nb > 0) {
  20512. nb--;
  20513. continue;
  20514. }
  20515. o.push(path[i]);
  20516. }
  20517. i = base.length - nb;
  20518. // If /a/b/c or /
  20519. if (i <= 0) {
  20520. outPath = o.reverse().join('/');
  20521. } else {
  20522. outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
  20523. }
  20524. // Add front / if it's needed
  20525. if (outPath.indexOf('/') !== 0) {
  20526. outPath = '/' + outPath;
  20527. }
  20528. // Add traling / if it's needed
  20529. if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
  20530. outPath += tr;
  20531. }
  20532. return outPath;
  20533. },
  20534. /**
  20535. * Returns the full URI of the internal structure.
  20536. *
  20537. * @method getURI
  20538. * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false.
  20539. */
  20540. getURI: function(noProtoHost) {
  20541. var s, self = this;
  20542. // Rebuild source
  20543. if (!self.source || noProtoHost) {
  20544. s = '';
  20545. if (!noProtoHost) {
  20546. if (self.protocol) {
  20547. s += self.protocol + '://';
  20548. } else {
  20549. s += '//';
  20550. }
  20551. if (self.userInfo) {
  20552. s += self.userInfo + '@';
  20553. }
  20554. if (self.host) {
  20555. s += self.host;
  20556. }
  20557. if (self.port) {
  20558. s += ':' + self.port;
  20559. }
  20560. }
  20561. if (self.path) {
  20562. s += self.path;
  20563. }
  20564. if (self.query) {
  20565. s += '?' + self.query;
  20566. }
  20567. if (self.anchor) {
  20568. s += '#' + self.anchor;
  20569. }
  20570. self.source = s;
  20571. }
  20572. return self.source;
  20573. }
  20574. };
  20575. URI.parseDataUri = function(uri) {
  20576. var type, matches;
  20577. uri = decodeURIComponent(uri).split(',');
  20578. matches = /data:([^;]+)/.exec(uri[0]);
  20579. if (matches) {
  20580. type = matches[1];
  20581. }
  20582. return {
  20583. type: type,
  20584. data: uri[1]
  20585. };
  20586. };
  20587. URI.getDocumentBaseUrl = function(loc) {
  20588. var baseUrl;
  20589. // Pass applewebdata:// and other non web protocols though
  20590. if (loc.protocol.indexOf('http') !== 0 && loc.protocol !== 'file:') {
  20591. baseUrl = loc.href;
  20592. } else {
  20593. baseUrl = loc.protocol + '//' + loc.host + loc.pathname;
  20594. }
  20595. if (/^[^:]+:\/\/\/?[^\/]+\//.test(baseUrl)) {
  20596. baseUrl = baseUrl.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
  20597. if (!/[\/\\]$/.test(baseUrl)) {
  20598. baseUrl += '/';
  20599. }
  20600. }
  20601. return baseUrl;
  20602. };
  20603. return URI;
  20604. });
  20605. // Included from: js/tinymce/classes/util/Class.js
  20606. /**
  20607. * Class.js
  20608. *
  20609. * Released under LGPL License.
  20610. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  20611. *
  20612. * License: http://www.tinymce.com/license
  20613. * Contributing: http://www.tinymce.com/contributing
  20614. */
  20615. /**
  20616. * This utilitiy class is used for easier inheritance.
  20617. *
  20618. * Features:
  20619. * * Exposed super functions: this._super();
  20620. * * Mixins
  20621. * * Dummy functions
  20622. * * Property functions: var value = object.value(); and object.value(newValue);
  20623. * * Static functions
  20624. * * Defaults settings
  20625. */
  20626. define("tinymce/util/Class", [
  20627. "tinymce/util/Tools"
  20628. ], function(Tools) {
  20629. var each = Tools.each, extend = Tools.extend;
  20630. var extendClass, initializing;
  20631. function Class() {
  20632. }
  20633. // Provides classical inheritance, based on code made by John Resig
  20634. Class.extend = extendClass = function(prop) {
  20635. var self = this, _super = self.prototype, prototype, name, member;
  20636. // The dummy class constructor
  20637. function Class() {
  20638. var i, mixins, mixin, self = this;
  20639. // All construction is actually done in the init method
  20640. if (!initializing) {
  20641. // Run class constuctor
  20642. if (self.init) {
  20643. self.init.apply(self, arguments);
  20644. }
  20645. // Run mixin constructors
  20646. mixins = self.Mixins;
  20647. if (mixins) {
  20648. i = mixins.length;
  20649. while (i--) {
  20650. mixin = mixins[i];
  20651. if (mixin.init) {
  20652. mixin.init.apply(self, arguments);
  20653. }
  20654. }
  20655. }
  20656. }
  20657. }
  20658. // Dummy function, needs to be extended in order to provide functionality
  20659. function dummy() {
  20660. return this;
  20661. }
  20662. // Creates a overloaded method for the class
  20663. // this enables you to use this._super(); to call the super function
  20664. function createMethod(name, fn) {
  20665. return function() {
  20666. var self = this, tmp = self._super, ret;
  20667. self._super = _super[name];
  20668. ret = fn.apply(self, arguments);
  20669. self._super = tmp;
  20670. return ret;
  20671. };
  20672. }
  20673. // Instantiate a base class (but only create the instance,
  20674. // don't run the init constructor)
  20675. initializing = true;
  20676. /*eslint new-cap:0 */
  20677. prototype = new self();
  20678. initializing = false;
  20679. // Add mixins
  20680. if (prop.Mixins) {
  20681. each(prop.Mixins, function(mixin) {
  20682. for (var name in mixin) {
  20683. if (name !== "init") {
  20684. prop[name] = mixin[name];
  20685. }
  20686. }
  20687. });
  20688. if (_super.Mixins) {
  20689. prop.Mixins = _super.Mixins.concat(prop.Mixins);
  20690. }
  20691. }
  20692. // Generate dummy methods
  20693. if (prop.Methods) {
  20694. each(prop.Methods.split(','), function(name) {
  20695. prop[name] = dummy;
  20696. });
  20697. }
  20698. // Generate property methods
  20699. if (prop.Properties) {
  20700. each(prop.Properties.split(','), function(name) {
  20701. var fieldName = '_' + name;
  20702. prop[name] = function(value) {
  20703. var self = this, undef;
  20704. // Set value
  20705. if (value !== undef) {
  20706. self[fieldName] = value;
  20707. return self;
  20708. }
  20709. // Get value
  20710. return self[fieldName];
  20711. };
  20712. });
  20713. }
  20714. // Static functions
  20715. if (prop.Statics) {
  20716. each(prop.Statics, function(func, name) {
  20717. Class[name] = func;
  20718. });
  20719. }
  20720. // Default settings
  20721. if (prop.Defaults && _super.Defaults) {
  20722. prop.Defaults = extend({}, _super.Defaults, prop.Defaults);
  20723. }
  20724. // Copy the properties over onto the new prototype
  20725. for (name in prop) {
  20726. member = prop[name];
  20727. if (typeof member == "function" && _super[name]) {
  20728. prototype[name] = createMethod(name, member);
  20729. } else {
  20730. prototype[name] = member;
  20731. }
  20732. }
  20733. // Populate our constructed prototype object
  20734. Class.prototype = prototype;
  20735. // Enforce the constructor to be what we expect
  20736. Class.constructor = Class;
  20737. // And make this class extendible
  20738. Class.extend = extendClass;
  20739. return Class;
  20740. };
  20741. return Class;
  20742. });
  20743. // Included from: js/tinymce/classes/util/EventDispatcher.js
  20744. /**
  20745. * EventDispatcher.js
  20746. *
  20747. * Released under LGPL License.
  20748. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  20749. *
  20750. * License: http://www.tinymce.com/license
  20751. * Contributing: http://www.tinymce.com/contributing
  20752. */
  20753. /**
  20754. * This class lets you add/remove and fire events by name on the specified scope. This makes
  20755. * it easy to add event listener logic to any class.
  20756. *
  20757. * @class tinymce.util.EventDispatcher
  20758. * @example
  20759. * var eventDispatcher = new EventDispatcher();
  20760. *
  20761. * eventDispatcher.on('click', function() {console.log('data');});
  20762. * eventDispatcher.fire('click', {data: 123});
  20763. */
  20764. define("tinymce/util/EventDispatcher", [
  20765. "tinymce/util/Tools"
  20766. ], function(Tools) {
  20767. var nativeEvents = Tools.makeMap(
  20768. "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " +
  20769. "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " +
  20770. "draggesture dragdrop drop drag submit " +
  20771. "compositionstart compositionend compositionupdate touchstart touchmove touchend",
  20772. ' '
  20773. );
  20774. function Dispatcher(settings) {
  20775. var self = this, scope, bindings = {}, toggleEvent;
  20776. function returnFalse() {
  20777. return false;
  20778. }
  20779. function returnTrue() {
  20780. return true;
  20781. }
  20782. settings = settings || {};
  20783. scope = settings.scope || self;
  20784. toggleEvent = settings.toggleEvent || returnFalse;
  20785. /**
  20786. * Fires the specified event by name.
  20787. *
  20788. * @method fire
  20789. * @param {String} name Name of the event to fire.
  20790. * @param {Object?} args Event arguments.
  20791. * @return {Object} Event args instance passed in.
  20792. * @example
  20793. * instance.fire('event', {...});
  20794. */
  20795. function fire(name, args) {
  20796. var handlers, i, l, callback;
  20797. name = name.toLowerCase();
  20798. args = args || {};
  20799. args.type = name;
  20800. // Setup target is there isn't one
  20801. if (!args.target) {
  20802. args.target = scope;
  20803. }
  20804. // Add event delegation methods if they are missing
  20805. if (!args.preventDefault) {
  20806. // Add preventDefault method
  20807. args.preventDefault = function() {
  20808. args.isDefaultPrevented = returnTrue;
  20809. };
  20810. // Add stopPropagation
  20811. args.stopPropagation = function() {
  20812. args.isPropagationStopped = returnTrue;
  20813. };
  20814. // Add stopImmediatePropagation
  20815. args.stopImmediatePropagation = function() {
  20816. args.isImmediatePropagationStopped = returnTrue;
  20817. };
  20818. // Add event delegation states
  20819. args.isDefaultPrevented = returnFalse;
  20820. args.isPropagationStopped = returnFalse;
  20821. args.isImmediatePropagationStopped = returnFalse;
  20822. }
  20823. if (settings.beforeFire) {
  20824. settings.beforeFire(args);
  20825. }
  20826. handlers = bindings[name];
  20827. if (handlers) {
  20828. for (i = 0, l = handlers.length; i < l; i++) {
  20829. callback = handlers[i];
  20830. // Unbind handlers marked with "once"
  20831. if (callback.once) {
  20832. off(name, callback.func);
  20833. }
  20834. // Stop immediate propagation if needed
  20835. if (args.isImmediatePropagationStopped()) {
  20836. args.stopPropagation();
  20837. return args;
  20838. }
  20839. // If callback returns false then prevent default and stop all propagation
  20840. if (callback.func.call(scope, args) === false) {
  20841. args.preventDefault();
  20842. return args;
  20843. }
  20844. }
  20845. }
  20846. return args;
  20847. }
  20848. /**
  20849. * Binds an event listener to a specific event by name.
  20850. *
  20851. * @method on
  20852. * @param {String} name Event name or space separated list of events to bind.
  20853. * @param {callback} callback Callback to be executed when the event occurs.
  20854. * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
  20855. * @return {Object} Current class instance.
  20856. * @example
  20857. * instance.on('event', function(e) {
  20858. * // Callback logic
  20859. * });
  20860. */
  20861. function on(name, callback, prepend, extra) {
  20862. var handlers, names, i;
  20863. if (callback === false) {
  20864. callback = returnFalse;
  20865. }
  20866. if (callback) {
  20867. callback = {
  20868. func: callback
  20869. };
  20870. if (extra) {
  20871. Tools.extend(callback, extra);
  20872. }
  20873. names = name.toLowerCase().split(' ');
  20874. i = names.length;
  20875. while (i--) {
  20876. name = names[i];
  20877. handlers = bindings[name];
  20878. if (!handlers) {
  20879. handlers = bindings[name] = [];
  20880. toggleEvent(name, true);
  20881. }
  20882. if (prepend) {
  20883. handlers.unshift(callback);
  20884. } else {
  20885. handlers.push(callback);
  20886. }
  20887. }
  20888. }
  20889. return self;
  20890. }
  20891. /**
  20892. * Unbinds an event listener to a specific event by name.
  20893. *
  20894. * @method off
  20895. * @param {String?} name Name of the event to unbind.
  20896. * @param {callback?} callback Callback to unbind.
  20897. * @return {Object} Current class instance.
  20898. * @example
  20899. * // Unbind specific callback
  20900. * instance.off('event', handler);
  20901. *
  20902. * // Unbind all listeners by name
  20903. * instance.off('event');
  20904. *
  20905. * // Unbind all events
  20906. * instance.off();
  20907. */
  20908. function off(name, callback) {
  20909. var i, handlers, bindingName, names, hi;
  20910. if (name) {
  20911. names = name.toLowerCase().split(' ');
  20912. i = names.length;
  20913. while (i--) {
  20914. name = names[i];
  20915. handlers = bindings[name];
  20916. // Unbind all handlers
  20917. if (!name) {
  20918. for (bindingName in bindings) {
  20919. toggleEvent(bindingName, false);
  20920. delete bindings[bindingName];
  20921. }
  20922. return self;
  20923. }
  20924. if (handlers) {
  20925. // Unbind all by name
  20926. if (!callback) {
  20927. handlers.length = 0;
  20928. } else {
  20929. // Unbind specific ones
  20930. hi = handlers.length;
  20931. while (hi--) {
  20932. if (handlers[hi].func === callback) {
  20933. handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1));
  20934. bindings[name] = handlers;
  20935. }
  20936. }
  20937. }
  20938. if (!handlers.length) {
  20939. toggleEvent(name, false);
  20940. delete bindings[name];
  20941. }
  20942. }
  20943. }
  20944. } else {
  20945. for (name in bindings) {
  20946. toggleEvent(name, false);
  20947. }
  20948. bindings = {};
  20949. }
  20950. return self;
  20951. }
  20952. /**
  20953. * Binds an event listener to a specific event by name
  20954. * and automatically unbind the event once the callback fires.
  20955. *
  20956. * @method once
  20957. * @param {String} name Event name or space separated list of events to bind.
  20958. * @param {callback} callback Callback to be executed when the event occurs.
  20959. * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
  20960. * @return {Object} Current class instance.
  20961. * @example
  20962. * instance.once('event', function(e) {
  20963. * // Callback logic
  20964. * });
  20965. */
  20966. function once(name, callback, prepend) {
  20967. return on(name, callback, prepend, {once: true});
  20968. }
  20969. /**
  20970. * Returns true/false if the dispatcher has a event of the specified name.
  20971. *
  20972. * @method has
  20973. * @param {String} name Name of the event to check for.
  20974. * @return {Boolean} true/false if the event exists or not.
  20975. */
  20976. function has(name) {
  20977. name = name.toLowerCase();
  20978. return !(!bindings[name] || bindings[name].length === 0);
  20979. }
  20980. // Expose
  20981. self.fire = fire;
  20982. self.on = on;
  20983. self.off = off;
  20984. self.once = once;
  20985. self.has = has;
  20986. }
  20987. /**
  20988. * Returns true/false if the specified event name is a native browser event or not.
  20989. *
  20990. * @method isNative
  20991. * @param {String} name Name to check if it's native.
  20992. * @return {Boolean} true/false if the event is native or not.
  20993. * @static
  20994. */
  20995. Dispatcher.isNative = function(name) {
  20996. return !!nativeEvents[name.toLowerCase()];
  20997. };
  20998. return Dispatcher;
  20999. });
  21000. // Included from: js/tinymce/classes/data/Binding.js
  21001. /**
  21002. * Binding.js
  21003. *
  21004. * Released under LGPL License.
  21005. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  21006. *
  21007. * License: http://www.tinymce.com/license
  21008. * Contributing: http://www.tinymce.com/contributing
  21009. */
  21010. /**
  21011. * This class gets dynamically extended to provide a binding between two models. This makes it possible to
  21012. * sync the state of two properties in two models by a layer of abstraction.
  21013. *
  21014. * @private
  21015. * @class tinymce.data.Binding
  21016. */
  21017. define("tinymce/data/Binding", [], function() {
  21018. /**
  21019. * Constructs a new bidning.
  21020. *
  21021. * @constructor
  21022. * @method Binding
  21023. * @param {Object} settings Settings to the binding.
  21024. */
  21025. function Binding(settings) {
  21026. this.create = settings.create;
  21027. }
  21028. /**
  21029. * Creates a binding for a property on a model.
  21030. *
  21031. * @method create
  21032. * @param {tinymce.data.ObservableObject} model Model to create binding to.
  21033. * @param {String} name Name of property to bind.
  21034. * @return {tinymce.data.Binding} Binding instance.
  21035. */
  21036. Binding.create = function(model, name) {
  21037. return new Binding({
  21038. create: function(otherModel, otherName) {
  21039. var bindings;
  21040. function fromSelfToOther(e) {
  21041. otherModel.set(otherName, e.value);
  21042. }
  21043. function fromOtherToSelf(e) {
  21044. model.set(name, e.value);
  21045. }
  21046. otherModel.on('change:' + otherName, fromOtherToSelf);
  21047. model.on('change:' + name, fromSelfToOther);
  21048. // Keep track of the bindings
  21049. bindings = otherModel._bindings;
  21050. if (!bindings) {
  21051. bindings = otherModel._bindings = [];
  21052. otherModel.on('destroy', function() {
  21053. var i = bindings.length;
  21054. while (i--) {
  21055. bindings[i]();
  21056. }
  21057. });
  21058. }
  21059. bindings.push(function() {
  21060. model.off('change:' + name, fromSelfToOther);
  21061. });
  21062. return model.get(name);
  21063. }
  21064. });
  21065. };
  21066. return Binding;
  21067. });
  21068. // Included from: js/tinymce/classes/util/Observable.js
  21069. /**
  21070. * Observable.js
  21071. *
  21072. * Released under LGPL License.
  21073. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  21074. *
  21075. * License: http://www.tinymce.com/license
  21076. * Contributing: http://www.tinymce.com/contributing
  21077. */
  21078. /**
  21079. * This mixin will add event binding logic to classes.
  21080. *
  21081. * @mixin tinymce.util.Observable
  21082. */
  21083. define("tinymce/util/Observable", [
  21084. "tinymce/util/EventDispatcher"
  21085. ], function(EventDispatcher) {
  21086. function getEventDispatcher(obj) {
  21087. if (!obj._eventDispatcher) {
  21088. obj._eventDispatcher = new EventDispatcher({
  21089. scope: obj,
  21090. toggleEvent: function(name, state) {
  21091. if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) {
  21092. obj.toggleNativeEvent(name, state);
  21093. }
  21094. }
  21095. });
  21096. }
  21097. return obj._eventDispatcher;
  21098. }
  21099. return {
  21100. /**
  21101. * Fires the specified event by name.
  21102. *
  21103. * @method fire
  21104. * @param {String} name Name of the event to fire.
  21105. * @param {Object?} args Event arguments.
  21106. * @param {Boolean?} bubble True/false if the event is to be bubbled.
  21107. * @return {Object} Event args instance passed in.
  21108. * @example
  21109. * instance.fire('event', {...});
  21110. */
  21111. fire: function(name, args, bubble) {
  21112. var self = this;
  21113. // Prevent all events except the remove event after the instance has been removed
  21114. if (self.removed && name !== "remove") {
  21115. return args;
  21116. }
  21117. args = getEventDispatcher(self).fire(name, args, bubble);
  21118. // Bubble event up to parents
  21119. if (bubble !== false && self.parent) {
  21120. var parent = self.parent();
  21121. while (parent && !args.isPropagationStopped()) {
  21122. parent.fire(name, args, false);
  21123. parent = parent.parent();
  21124. }
  21125. }
  21126. return args;
  21127. },
  21128. /**
  21129. * Binds an event listener to a specific event by name.
  21130. *
  21131. * @method on
  21132. * @param {String} name Event name or space separated list of events to bind.
  21133. * @param {callback} callback Callback to be executed when the event occurs.
  21134. * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
  21135. * @return {Object} Current class instance.
  21136. * @example
  21137. * instance.on('event', function(e) {
  21138. * // Callback logic
  21139. * });
  21140. */
  21141. on: function(name, callback, prepend) {
  21142. return getEventDispatcher(this).on(name, callback, prepend);
  21143. },
  21144. /**
  21145. * Unbinds an event listener to a specific event by name.
  21146. *
  21147. * @method off
  21148. * @param {String?} name Name of the event to unbind.
  21149. * @param {callback?} callback Callback to unbind.
  21150. * @return {Object} Current class instance.
  21151. * @example
  21152. * // Unbind specific callback
  21153. * instance.off('event', handler);
  21154. *
  21155. * // Unbind all listeners by name
  21156. * instance.off('event');
  21157. *
  21158. * // Unbind all events
  21159. * instance.off();
  21160. */
  21161. off: function(name, callback) {
  21162. return getEventDispatcher(this).off(name, callback);
  21163. },
  21164. /**
  21165. * Bind the event callback and once it fires the callback is removed.
  21166. *
  21167. * @method once
  21168. * @param {String} name Name of the event to bind.
  21169. * @param {callback} callback Callback to bind only once.
  21170. * @return {Object} Current class instance.
  21171. */
  21172. once: function(name, callback) {
  21173. return getEventDispatcher(this).once(name, callback);
  21174. },
  21175. /**
  21176. * Returns true/false if the object has a event of the specified name.
  21177. *
  21178. * @method hasEventListeners
  21179. * @param {String} name Name of the event to check for.
  21180. * @return {Boolean} true/false if the event exists or not.
  21181. */
  21182. hasEventListeners: function(name) {
  21183. return getEventDispatcher(this).has(name);
  21184. }
  21185. };
  21186. });
  21187. // Included from: js/tinymce/classes/data/ObservableObject.js
  21188. /**
  21189. * ObservableObject.js
  21190. *
  21191. * Released under LGPL License.
  21192. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  21193. *
  21194. * License: http://www.tinymce.com/license
  21195. * Contributing: http://www.tinymce.com/contributing
  21196. */
  21197. /**
  21198. * This class is a object that is observable when properties changes a change event gets emitted.
  21199. *
  21200. * @private
  21201. * @class tinymce.data.ObservableObject
  21202. */
  21203. define("tinymce/data/ObservableObject", [
  21204. "tinymce/data/Binding",
  21205. "tinymce/util/Observable",
  21206. "tinymce/util/Class",
  21207. "tinymce/util/Tools"
  21208. ], function(Binding, Observable, Class, Tools) {
  21209. function isNode(node) {
  21210. return node.nodeType > 0;
  21211. }
  21212. // Todo: Maybe this should be shallow compare since it might be huge object references
  21213. function isEqual(a, b) {
  21214. var k, checked;
  21215. // Strict equals
  21216. if (a === b) {
  21217. return true;
  21218. }
  21219. // Compare null
  21220. if (a === null || b === null) {
  21221. return a === b;
  21222. }
  21223. // Compare number, boolean, string, undefined
  21224. if (typeof a !== "object" || typeof b !== "object") {
  21225. return a === b;
  21226. }
  21227. // Compare arrays
  21228. if (Tools.isArray(b)) {
  21229. if (a.length !== b.length) {
  21230. return false;
  21231. }
  21232. k = a.length;
  21233. while (k--) {
  21234. if (!isEqual(a[k], b[k])) {
  21235. return false;
  21236. }
  21237. }
  21238. }
  21239. // Shallow compare nodes
  21240. if (isNode(a) || isNode(b)) {
  21241. return a === b;
  21242. }
  21243. // Compare objects
  21244. checked = {};
  21245. for (k in b) {
  21246. if (!isEqual(a[k], b[k])) {
  21247. return false;
  21248. }
  21249. checked[k] = true;
  21250. }
  21251. for (k in a) {
  21252. if (!checked[k] && !isEqual(a[k], b[k])) {
  21253. return false;
  21254. }
  21255. }
  21256. return true;
  21257. }
  21258. return Class.extend({
  21259. Mixins: [Observable],
  21260. /**
  21261. * Constructs a new observable object instance.
  21262. *
  21263. * @constructor
  21264. * @param {Object} data Initial data for the object.
  21265. */
  21266. init: function(data) {
  21267. var name, value;
  21268. data = data || {};
  21269. for (name in data) {
  21270. value = data[name];
  21271. if (value instanceof Binding) {
  21272. data[name] = value.create(this, name);
  21273. }
  21274. }
  21275. this.data = data;
  21276. },
  21277. /**
  21278. * Sets a property on the value this will call
  21279. * observers if the value is a change from the current value.
  21280. *
  21281. * @method set
  21282. * @param {String/object} name Name of the property to set or a object of items to set.
  21283. * @param {Object} value Value to set for the property.
  21284. * @return {tinymce.data.ObservableObject} Observable object instance.
  21285. */
  21286. set: function(name, value) {
  21287. var key, args, oldValue = this.data[name];
  21288. if (value instanceof Binding) {
  21289. value = value.create(this, name);
  21290. }
  21291. if (typeof name === "object") {
  21292. for (key in name) {
  21293. this.set(key, name[key]);
  21294. }
  21295. return this;
  21296. }
  21297. if (!isEqual(oldValue, value)) {
  21298. this.data[name] = value;
  21299. args = {
  21300. target: this,
  21301. name: name,
  21302. value: value,
  21303. oldValue: oldValue
  21304. };
  21305. this.fire('change:' + name, args);
  21306. this.fire('change', args);
  21307. }
  21308. return this;
  21309. },
  21310. /**
  21311. * Gets a property by name.
  21312. *
  21313. * @method get
  21314. * @param {String} name Name of the property to get.
  21315. * @return {Object} Object value of propery.
  21316. */
  21317. get: function(name) {
  21318. return this.data[name];
  21319. },
  21320. /**
  21321. * Returns true/false if the specified property exists.
  21322. *
  21323. * @method has
  21324. * @param {String} name Name of the property to check for.
  21325. * @return {Boolean} true/false if the item exists.
  21326. */
  21327. has: function(name) {
  21328. return name in this.data;
  21329. },
  21330. /**
  21331. * Returns a dynamic property binding for the specified property name. This makes
  21332. * it possible to sync the state of two properties in two ObservableObject instances.
  21333. *
  21334. * @method bind
  21335. * @param {String} name Name of the property to sync with the property it's inserted to.
  21336. * @return {tinymce.data.Binding} Data binding instance.
  21337. */
  21338. bind: function(name) {
  21339. return Binding.create(this, name);
  21340. },
  21341. /**
  21342. * Destroys the observable object and fires the "destroy"
  21343. * event and clean up any internal resources.
  21344. *
  21345. * @method destroy
  21346. */
  21347. destroy: function() {
  21348. this.fire('destroy');
  21349. }
  21350. });
  21351. });
  21352. // Included from: js/tinymce/classes/ui/Selector.js
  21353. /**
  21354. * Selector.js
  21355. *
  21356. * Released under LGPL License.
  21357. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  21358. *
  21359. * License: http://www.tinymce.com/license
  21360. * Contributing: http://www.tinymce.com/contributing
  21361. */
  21362. /*eslint no-nested-ternary:0 */
  21363. /**
  21364. * Selector engine, enables you to select controls by using CSS like expressions.
  21365. * We currently only support basic CSS expressions to reduce the size of the core
  21366. * and the ones we support should be enough for most cases.
  21367. *
  21368. * @example
  21369. * Supported expressions:
  21370. * element
  21371. * element#name
  21372. * element.class
  21373. * element[attr]
  21374. * element[attr*=value]
  21375. * element[attr~=value]
  21376. * element[attr!=value]
  21377. * element[attr^=value]
  21378. * element[attr$=value]
  21379. * element:<state>
  21380. * element:not(<expression>)
  21381. * element:first
  21382. * element:last
  21383. * element:odd
  21384. * element:even
  21385. * element element
  21386. * element > element
  21387. *
  21388. * @class tinymce.ui.Selector
  21389. */
  21390. define("tinymce/ui/Selector", [
  21391. "tinymce/util/Class"
  21392. ], function(Class) {
  21393. "use strict";
  21394. /**
  21395. * Produces an array with a unique set of objects. It will not compare the values
  21396. * but the references of the objects.
  21397. *
  21398. * @private
  21399. * @method unqiue
  21400. * @param {Array} array Array to make into an array with unique items.
  21401. * @return {Array} Array with unique items.
  21402. */
  21403. function unique(array) {
  21404. var uniqueItems = [], i = array.length, item;
  21405. while (i--) {
  21406. item = array[i];
  21407. if (!item.__checked) {
  21408. uniqueItems.push(item);
  21409. item.__checked = 1;
  21410. }
  21411. }
  21412. i = uniqueItems.length;
  21413. while (i--) {
  21414. delete uniqueItems[i].__checked;
  21415. }
  21416. return uniqueItems;
  21417. }
  21418. var expression = /^([\w\\*]+)?(?:#([\w\-\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;
  21419. /*jshint maxlen:255 */
  21420. /*eslint max-len:0 */
  21421. var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
  21422. whiteSpace = /^\s*|\s*$/g,
  21423. Collection;
  21424. var Selector = Class.extend({
  21425. /**
  21426. * Constructs a new Selector instance.
  21427. *
  21428. * @constructor
  21429. * @method init
  21430. * @param {String} selector CSS like selector expression.
  21431. */
  21432. init: function(selector) {
  21433. var match = this.match;
  21434. function compileNameFilter(name) {
  21435. if (name) {
  21436. name = name.toLowerCase();
  21437. return function(item) {
  21438. return name === '*' || item.type === name;
  21439. };
  21440. }
  21441. }
  21442. function compileIdFilter(id) {
  21443. if (id) {
  21444. return function(item) {
  21445. return item._name === id;
  21446. };
  21447. }
  21448. }
  21449. function compileClassesFilter(classes) {
  21450. if (classes) {
  21451. classes = classes.split('.');
  21452. return function(item) {
  21453. var i = classes.length;
  21454. while (i--) {
  21455. if (!item.classes.contains(classes[i])) {
  21456. return false;
  21457. }
  21458. }
  21459. return true;
  21460. };
  21461. }
  21462. }
  21463. function compileAttrFilter(name, cmp, check) {
  21464. if (name) {
  21465. return function(item) {
  21466. var value = item[name] ? item[name]() : '';
  21467. return !cmp ? !!check :
  21468. cmp === "=" ? value === check :
  21469. cmp === "*=" ? value.indexOf(check) >= 0 :
  21470. cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 :
  21471. cmp === "!=" ? value != check :
  21472. cmp === "^=" ? value.indexOf(check) === 0 :
  21473. cmp === "$=" ? value.substr(value.length - check.length) === check :
  21474. false;
  21475. };
  21476. }
  21477. }
  21478. function compilePsuedoFilter(name) {
  21479. var notSelectors;
  21480. if (name) {
  21481. name = /(?:not\((.+)\))|(.+)/i.exec(name);
  21482. if (!name[1]) {
  21483. name = name[2];
  21484. return function(item, index, length) {
  21485. return name === 'first' ? index === 0 :
  21486. name === 'last' ? index === length - 1 :
  21487. name === 'even' ? index % 2 === 0 :
  21488. name === 'odd' ? index % 2 === 1 :
  21489. item[name] ? item[name]() :
  21490. false;
  21491. };
  21492. }
  21493. // Compile not expression
  21494. notSelectors = parseChunks(name[1], []);
  21495. return function(item) {
  21496. return !match(item, notSelectors);
  21497. };
  21498. }
  21499. }
  21500. function compile(selector, filters, direct) {
  21501. var parts;
  21502. function add(filter) {
  21503. if (filter) {
  21504. filters.push(filter);
  21505. }
  21506. }
  21507. // Parse expression into parts
  21508. parts = expression.exec(selector.replace(whiteSpace, ''));
  21509. add(compileNameFilter(parts[1]));
  21510. add(compileIdFilter(parts[2]));
  21511. add(compileClassesFilter(parts[3]));
  21512. add(compileAttrFilter(parts[4], parts[5], parts[6]));
  21513. add(compilePsuedoFilter(parts[7]));
  21514. // Mark the filter with pseudo for performance
  21515. filters.pseudo = !!parts[7];
  21516. filters.direct = direct;
  21517. return filters;
  21518. }
  21519. // Parser logic based on Sizzle by John Resig
  21520. function parseChunks(selector, selectors) {
  21521. var parts = [], extra, matches, i;
  21522. do {
  21523. chunker.exec("");
  21524. matches = chunker.exec(selector);
  21525. if (matches) {
  21526. selector = matches[3];
  21527. parts.push(matches[1]);
  21528. if (matches[2]) {
  21529. extra = matches[3];
  21530. break;
  21531. }
  21532. }
  21533. } while (matches);
  21534. if (extra) {
  21535. parseChunks(extra, selectors);
  21536. }
  21537. selector = [];
  21538. for (i = 0; i < parts.length; i++) {
  21539. if (parts[i] != '>') {
  21540. selector.push(compile(parts[i], [], parts[i - 1] === '>'));
  21541. }
  21542. }
  21543. selectors.push(selector);
  21544. return selectors;
  21545. }
  21546. this._selectors = parseChunks(selector, []);
  21547. },
  21548. /**
  21549. * Returns true/false if the selector matches the specified control.
  21550. *
  21551. * @method match
  21552. * @param {tinymce.ui.Control} control Control to match against the selector.
  21553. * @param {Array} selectors Optional array of selectors, mostly used internally.
  21554. * @return {Boolean} true/false state if the control matches or not.
  21555. */
  21556. match: function(control, selectors) {
  21557. var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item;
  21558. selectors = selectors || this._selectors;
  21559. for (i = 0, l = selectors.length; i < l; i++) {
  21560. selector = selectors[i];
  21561. sl = selector.length;
  21562. item = control;
  21563. count = 0;
  21564. for (si = sl - 1; si >= 0; si--) {
  21565. filters = selector[si];
  21566. while (item) {
  21567. // Find the index and length since a pseudo filter like :first needs it
  21568. if (filters.pseudo) {
  21569. siblings = item.parent().items();
  21570. index = length = siblings.length;
  21571. while (index--) {
  21572. if (siblings[index] === item) {
  21573. break;
  21574. }
  21575. }
  21576. }
  21577. for (fi = 0, fl = filters.length; fi < fl; fi++) {
  21578. if (!filters[fi](item, index, length)) {
  21579. fi = fl + 1;
  21580. break;
  21581. }
  21582. }
  21583. if (fi === fl) {
  21584. count++;
  21585. break;
  21586. } else {
  21587. // If it didn't match the right most expression then
  21588. // break since it's no point looking at the parents
  21589. if (si === sl - 1) {
  21590. break;
  21591. }
  21592. }
  21593. item = item.parent();
  21594. }
  21595. }
  21596. // If we found all selectors then return true otherwise continue looking
  21597. if (count === sl) {
  21598. return true;
  21599. }
  21600. }
  21601. return false;
  21602. },
  21603. /**
  21604. * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container.
  21605. *
  21606. * @method find
  21607. * @param {tinymce.ui.Control} container Container to look for items in.
  21608. * @return {tinymce.ui.Collection} Collection with matched elements.
  21609. */
  21610. find: function(container) {
  21611. var matches = [], i, l, selectors = this._selectors;
  21612. function collect(items, selector, index) {
  21613. var i, l, fi, fl, item, filters = selector[index];
  21614. for (i = 0, l = items.length; i < l; i++) {
  21615. item = items[i];
  21616. // Run each filter against the item
  21617. for (fi = 0, fl = filters.length; fi < fl; fi++) {
  21618. if (!filters[fi](item, i, l)) {
  21619. fi = fl + 1;
  21620. break;
  21621. }
  21622. }
  21623. // All filters matched the item
  21624. if (fi === fl) {
  21625. // Matched item is on the last expression like: panel toolbar [button]
  21626. if (index == selector.length - 1) {
  21627. matches.push(item);
  21628. } else {
  21629. // Collect next expression type
  21630. if (item.items) {
  21631. collect(item.items(), selector, index + 1);
  21632. }
  21633. }
  21634. } else if (filters.direct) {
  21635. return;
  21636. }
  21637. // Collect child items
  21638. if (item.items) {
  21639. collect(item.items(), selector, index);
  21640. }
  21641. }
  21642. }
  21643. if (container.items) {
  21644. for (i = 0, l = selectors.length; i < l; i++) {
  21645. collect(container.items(), selectors[i], 0);
  21646. }
  21647. // Unique the matches if needed
  21648. if (l > 1) {
  21649. matches = unique(matches);
  21650. }
  21651. }
  21652. // Fix for circular reference
  21653. if (!Collection) {
  21654. // TODO: Fix me!
  21655. Collection = Selector.Collection;
  21656. }
  21657. return new Collection(matches);
  21658. }
  21659. });
  21660. return Selector;
  21661. });
  21662. // Included from: js/tinymce/classes/ui/Collection.js
  21663. /**
  21664. * Collection.js
  21665. *
  21666. * Released under LGPL License.
  21667. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  21668. *
  21669. * License: http://www.tinymce.com/license
  21670. * Contributing: http://www.tinymce.com/contributing
  21671. */
  21672. /**
  21673. * Control collection, this class contains control instances and it enables you to
  21674. * perform actions on all the contained items. This is very similar to how jQuery works.
  21675. *
  21676. * @example
  21677. * someCollection.show().disabled(true);
  21678. *
  21679. * @class tinymce.ui.Collection
  21680. */
  21681. define("tinymce/ui/Collection", [
  21682. "tinymce/util/Tools",
  21683. "tinymce/ui/Selector",
  21684. "tinymce/util/Class"
  21685. ], function(Tools, Selector, Class) {
  21686. "use strict";
  21687. var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice;
  21688. proto = {
  21689. /**
  21690. * Current number of contained control instances.
  21691. *
  21692. * @field length
  21693. * @type Number
  21694. */
  21695. length: 0,
  21696. /**
  21697. * Constructor for the collection.
  21698. *
  21699. * @constructor
  21700. * @method init
  21701. * @param {Array} items Optional array with items to add.
  21702. */
  21703. init: function(items) {
  21704. if (items) {
  21705. this.add(items);
  21706. }
  21707. },
  21708. /**
  21709. * Adds new items to the control collection.
  21710. *
  21711. * @method add
  21712. * @param {Array} items Array if items to add to collection.
  21713. * @return {tinymce.ui.Collection} Current collection instance.
  21714. */
  21715. add: function(items) {
  21716. var self = this;
  21717. // Force single item into array
  21718. if (!Tools.isArray(items)) {
  21719. if (items instanceof Collection) {
  21720. self.add(items.toArray());
  21721. } else {
  21722. push.call(self, items);
  21723. }
  21724. } else {
  21725. push.apply(self, items);
  21726. }
  21727. return self;
  21728. },
  21729. /**
  21730. * Sets the contents of the collection. This will remove any existing items
  21731. * and replace them with the ones specified in the input array.
  21732. *
  21733. * @method set
  21734. * @param {Array} items Array with items to set into the Collection.
  21735. * @return {tinymce.ui.Collection} Collection instance.
  21736. */
  21737. set: function(items) {
  21738. var self = this, len = self.length, i;
  21739. self.length = 0;
  21740. self.add(items);
  21741. // Remove old entries
  21742. for (i = self.length; i < len; i++) {
  21743. delete self[i];
  21744. }
  21745. return self;
  21746. },
  21747. /**
  21748. * Filters the collection item based on the specified selector expression or selector function.
  21749. *
  21750. * @method filter
  21751. * @param {String} selector Selector expression to filter items by.
  21752. * @return {tinymce.ui.Collection} Collection containing the filtered items.
  21753. */
  21754. filter: function(selector) {
  21755. var self = this, i, l, matches = [], item, match;
  21756. // Compile string into selector expression
  21757. if (typeof selector === "string") {
  21758. selector = new Selector(selector);
  21759. match = function(item) {
  21760. return selector.match(item);
  21761. };
  21762. } else {
  21763. // Use selector as matching function
  21764. match = selector;
  21765. }
  21766. for (i = 0, l = self.length; i < l; i++) {
  21767. item = self[i];
  21768. if (match(item)) {
  21769. matches.push(item);
  21770. }
  21771. }
  21772. return new Collection(matches);
  21773. },
  21774. /**
  21775. * Slices the items within the collection.
  21776. *
  21777. * @method slice
  21778. * @param {Number} index Index to slice at.
  21779. * @param {Number} len Optional length to slice.
  21780. * @return {tinymce.ui.Collection} Current collection.
  21781. */
  21782. slice: function() {
  21783. return new Collection(slice.apply(this, arguments));
  21784. },
  21785. /**
  21786. * Makes the current collection equal to the specified index.
  21787. *
  21788. * @method eq
  21789. * @param {Number} index Index of the item to set the collection to.
  21790. * @return {tinymce.ui.Collection} Current collection.
  21791. */
  21792. eq: function(index) {
  21793. return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
  21794. },
  21795. /**
  21796. * Executes the specified callback on each item in collection.
  21797. *
  21798. * @method each
  21799. * @param {function} callback Callback to execute for each item in collection.
  21800. * @return {tinymce.ui.Collection} Current collection instance.
  21801. */
  21802. each: function(callback) {
  21803. Tools.each(this, callback);
  21804. return this;
  21805. },
  21806. /**
  21807. * Returns an JavaScript array object of the contents inside the collection.
  21808. *
  21809. * @method toArray
  21810. * @return {Array} Array with all items from collection.
  21811. */
  21812. toArray: function() {
  21813. return Tools.toArray(this);
  21814. },
  21815. /**
  21816. * Finds the index of the specified control or return -1 if it isn't in the collection.
  21817. *
  21818. * @method indexOf
  21819. * @param {Control} ctrl Control instance to look for.
  21820. * @return {Number} Index of the specified control or -1.
  21821. */
  21822. indexOf: function(ctrl) {
  21823. var self = this, i = self.length;
  21824. while (i--) {
  21825. if (self[i] === ctrl) {
  21826. break;
  21827. }
  21828. }
  21829. return i;
  21830. },
  21831. /**
  21832. * Returns a new collection of the contents in reverse order.
  21833. *
  21834. * @method reverse
  21835. * @return {tinymce.ui.Collection} Collection instance with reversed items.
  21836. */
  21837. reverse: function() {
  21838. return new Collection(Tools.toArray(this).reverse());
  21839. },
  21840. /**
  21841. * Returns true/false if the class exists or not.
  21842. *
  21843. * @method hasClass
  21844. * @param {String} cls Class to check for.
  21845. * @return {Boolean} true/false state if the class exists or not.
  21846. */
  21847. hasClass: function(cls) {
  21848. return this[0] ? this[0].classes.contains(cls) : false;
  21849. },
  21850. /**
  21851. * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>);
  21852. *
  21853. * @method prop
  21854. * @param {String} name Property name to get/set.
  21855. * @param {Object} value Optional object value to set.
  21856. * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation.
  21857. */
  21858. prop: function(name, value) {
  21859. var self = this, undef, item;
  21860. if (value !== undef) {
  21861. self.each(function(item) {
  21862. if (item[name]) {
  21863. item[name](value);
  21864. }
  21865. });
  21866. return self;
  21867. }
  21868. item = self[0];
  21869. if (item && item[name]) {
  21870. return item[name]();
  21871. }
  21872. },
  21873. /**
  21874. * Executes the specific function name with optional arguments an all items in collection if it exists.
  21875. *
  21876. * @example collection.exec("myMethod", arg1, arg2, arg3);
  21877. * @method exec
  21878. * @param {String} name Name of the function to execute.
  21879. * @param {Object} ... Multiple arguments to pass to each function.
  21880. * @return {tinymce.ui.Collection} Current collection.
  21881. */
  21882. exec: function(name) {
  21883. var self = this, args = Tools.toArray(arguments).slice(1);
  21884. self.each(function(item) {
  21885. if (item[name]) {
  21886. item[name].apply(item, args);
  21887. }
  21888. });
  21889. return self;
  21890. },
  21891. /**
  21892. * Remove all items from collection and DOM.
  21893. *
  21894. * @method remove
  21895. * @return {tinymce.ui.Collection} Current collection.
  21896. */
  21897. remove: function() {
  21898. var i = this.length;
  21899. while (i--) {
  21900. this[i].remove();
  21901. }
  21902. return this;
  21903. },
  21904. /**
  21905. * Adds a class to all items in the collection.
  21906. *
  21907. * @method addClass
  21908. * @param {String} cls Class to add to each item.
  21909. * @return {tinymce.ui.Collection} Current collection instance.
  21910. */
  21911. addClass: function(cls) {
  21912. return this.each(function(item) {
  21913. item.classes.add(cls);
  21914. });
  21915. },
  21916. /**
  21917. * Removes the specified class from all items in collection.
  21918. *
  21919. * @method removeClass
  21920. * @param {String} cls Class to remove from each item.
  21921. * @return {tinymce.ui.Collection} Current collection instance.
  21922. */
  21923. removeClass: function(cls) {
  21924. return this.each(function(item) {
  21925. item.classes.remove(cls);
  21926. });
  21927. }
  21928. /**
  21929. * Fires the specified event by name and arguments on the control. This will execute all
  21930. * bound event handlers.
  21931. *
  21932. * @method fire
  21933. * @param {String} name Name of the event to fire.
  21934. * @param {Object} args Optional arguments to pass to the event.
  21935. * @return {tinymce.ui.Collection} Current collection instance.
  21936. */
  21937. // fire: function(event, args) {}, -- Generated by code below
  21938. /**
  21939. * Binds a callback to the specified event. This event can both be
  21940. * native browser events like "click" or custom ones like PostRender.
  21941. *
  21942. * The callback function will have two parameters the first one being the control that received the event
  21943. * the second one will be the event object either the browsers native event object or a custom JS object.
  21944. *
  21945. * @method on
  21946. * @param {String} name Name of the event to bind. For example "click".
  21947. * @param {String/function} callback Callback function to execute ones the event occurs.
  21948. * @return {tinymce.ui.Collection} Current collection instance.
  21949. */
  21950. // on: function(name, callback) {}, -- Generated by code below
  21951. /**
  21952. * Unbinds the specified event and optionally a specific callback. If you omit the name
  21953. * parameter all event handlers will be removed. If you omit the callback all event handles
  21954. * by the specified name will be removed.
  21955. *
  21956. * @method off
  21957. * @param {String} name Optional name for the event to unbind.
  21958. * @param {function} callback Optional callback function to unbind.
  21959. * @return {tinymce.ui.Collection} Current collection instance.
  21960. */
  21961. // off: function(name, callback) {}, -- Generated by code below
  21962. /**
  21963. * Shows the items in the current collection.
  21964. *
  21965. * @method show
  21966. * @return {tinymce.ui.Collection} Current collection instance.
  21967. */
  21968. // show: function() {}, -- Generated by code below
  21969. /**
  21970. * Hides the items in the current collection.
  21971. *
  21972. * @method hide
  21973. * @return {tinymce.ui.Collection} Current collection instance.
  21974. */
  21975. // hide: function() {}, -- Generated by code below
  21976. /**
  21977. * Sets/gets the text contents of the items in the current collection.
  21978. *
  21979. * @method text
  21980. * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation.
  21981. */
  21982. // text: function(value) {}, -- Generated by code below
  21983. /**
  21984. * Sets/gets the name contents of the items in the current collection.
  21985. *
  21986. * @method name
  21987. * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation.
  21988. */
  21989. // name: function(value) {}, -- Generated by code below
  21990. /**
  21991. * Sets/gets the disabled state on the items in the current collection.
  21992. *
  21993. * @method disabled
  21994. * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation.
  21995. */
  21996. // disabled: function(state) {}, -- Generated by code below
  21997. /**
  21998. * Sets/gets the active state on the items in the current collection.
  21999. *
  22000. * @method active
  22001. * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation.
  22002. */
  22003. // active: function(state) {}, -- Generated by code below
  22004. /**
  22005. * Sets/gets the selected state on the items in the current collection.
  22006. *
  22007. * @method selected
  22008. * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation.
  22009. */
  22010. // selected: function(state) {}, -- Generated by code below
  22011. /**
  22012. * Sets/gets the selected state on the items in the current collection.
  22013. *
  22014. * @method visible
  22015. * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation.
  22016. */
  22017. // visible: function(state) {}, -- Generated by code below
  22018. };
  22019. // Extend tinymce.ui.Collection prototype with some generated control specific methods
  22020. Tools.each('fire on off show hide append prepend before after reflow'.split(' '), function(name) {
  22021. proto[name] = function() {
  22022. var args = Tools.toArray(arguments);
  22023. this.each(function(ctrl) {
  22024. if (name in ctrl) {
  22025. ctrl[name].apply(ctrl, args);
  22026. }
  22027. });
  22028. return this;
  22029. };
  22030. });
  22031. // Extend tinymce.ui.Collection prototype with some property methods
  22032. Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) {
  22033. proto[name] = function(value) {
  22034. return this.prop(name, value);
  22035. };
  22036. });
  22037. // Create class based on the new prototype
  22038. Collection = Class.extend(proto);
  22039. // Stick Collection into Selector to prevent circual references
  22040. Selector.Collection = Collection;
  22041. return Collection;
  22042. });
  22043. // Included from: js/tinymce/classes/ui/DomUtils.js
  22044. /**
  22045. * DomUtils.js
  22046. *
  22047. * Released under LGPL License.
  22048. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  22049. *
  22050. * License: http://www.tinymce.com/license
  22051. * Contributing: http://www.tinymce.com/contributing
  22052. */
  22053. /**
  22054. * Private UI DomUtils proxy.
  22055. *
  22056. * @private
  22057. * @class tinymce.ui.DomUtils
  22058. */
  22059. define("tinymce/ui/DomUtils", [
  22060. "tinymce/util/Tools",
  22061. "tinymce/dom/DOMUtils"
  22062. ], function(Tools, DOMUtils) {
  22063. "use strict";
  22064. var count = 0;
  22065. return {
  22066. id: function() {
  22067. return 'mceu_' + (count++);
  22068. },
  22069. create: function(name, attrs, children) {
  22070. var elm = document.createElement(name);
  22071. DOMUtils.DOM.setAttribs(elm, attrs);
  22072. if (typeof children === 'string') {
  22073. elm.innerHTML = children;
  22074. } else {
  22075. Tools.each(children, function(child) {
  22076. if (child.nodeType) {
  22077. elm.appendChild(child);
  22078. }
  22079. });
  22080. }
  22081. return elm;
  22082. },
  22083. createFragment: function(html) {
  22084. return DOMUtils.DOM.createFragment(html);
  22085. },
  22086. getWindowSize: function() {
  22087. return DOMUtils.DOM.getViewPort();
  22088. },
  22089. getSize: function(elm) {
  22090. var width, height;
  22091. if (elm.getBoundingClientRect) {
  22092. var rect = elm.getBoundingClientRect();
  22093. width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth);
  22094. height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight);
  22095. } else {
  22096. width = elm.offsetWidth;
  22097. height = elm.offsetHeight;
  22098. }
  22099. return {width: width, height: height};
  22100. },
  22101. getPos: function(elm, root) {
  22102. return DOMUtils.DOM.getPos(elm, root);
  22103. },
  22104. getViewPort: function(win) {
  22105. return DOMUtils.DOM.getViewPort(win);
  22106. },
  22107. get: function(id) {
  22108. return document.getElementById(id);
  22109. },
  22110. addClass: function(elm, cls) {
  22111. return DOMUtils.DOM.addClass(elm, cls);
  22112. },
  22113. removeClass: function(elm, cls) {
  22114. return DOMUtils.DOM.removeClass(elm, cls);
  22115. },
  22116. hasClass: function(elm, cls) {
  22117. return DOMUtils.DOM.hasClass(elm, cls);
  22118. },
  22119. toggleClass: function(elm, cls, state) {
  22120. return DOMUtils.DOM.toggleClass(elm, cls, state);
  22121. },
  22122. css: function(elm, name, value) {
  22123. return DOMUtils.DOM.setStyle(elm, name, value);
  22124. },
  22125. getRuntimeStyle: function(elm, name) {
  22126. return DOMUtils.DOM.getStyle(elm, name, true);
  22127. },
  22128. on: function(target, name, callback, scope) {
  22129. return DOMUtils.DOM.bind(target, name, callback, scope);
  22130. },
  22131. off: function(target, name, callback) {
  22132. return DOMUtils.DOM.unbind(target, name, callback);
  22133. },
  22134. fire: function(target, name, args) {
  22135. return DOMUtils.DOM.fire(target, name, args);
  22136. },
  22137. innerHtml: function(elm, html) {
  22138. // Workaround for <div> in <p> bug on IE 8 #6178
  22139. DOMUtils.DOM.setHTML(elm, html);
  22140. }
  22141. };
  22142. });
  22143. // Included from: js/tinymce/classes/ui/BoxUtils.js
  22144. /**
  22145. * BoxUtils.js
  22146. *
  22147. * Released under LGPL License.
  22148. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  22149. *
  22150. * License: http://www.tinymce.com/license
  22151. * Contributing: http://www.tinymce.com/contributing
  22152. */
  22153. /**
  22154. * Utility class for box parsing and measuring.
  22155. *
  22156. * @private
  22157. * @class tinymce.ui.BoxUtils
  22158. */
  22159. define("tinymce/ui/BoxUtils", [
  22160. ], function() {
  22161. "use strict";
  22162. return {
  22163. /**
  22164. * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
  22165. *
  22166. * @method parseBox
  22167. * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
  22168. * @return {Object} Object with top/right/bottom/left properties.
  22169. * @private
  22170. */
  22171. parseBox: function(value) {
  22172. var len, radix = 10;
  22173. if (!value) {
  22174. return;
  22175. }
  22176. if (typeof value === "number") {
  22177. value = value || 0;
  22178. return {
  22179. top: value,
  22180. left: value,
  22181. bottom: value,
  22182. right: value
  22183. };
  22184. }
  22185. value = value.split(' ');
  22186. len = value.length;
  22187. if (len === 1) {
  22188. value[1] = value[2] = value[3] = value[0];
  22189. } else if (len === 2) {
  22190. value[2] = value[0];
  22191. value[3] = value[1];
  22192. } else if (len === 3) {
  22193. value[3] = value[1];
  22194. }
  22195. return {
  22196. top: parseInt(value[0], radix) || 0,
  22197. right: parseInt(value[1], radix) || 0,
  22198. bottom: parseInt(value[2], radix) || 0,
  22199. left: parseInt(value[3], radix) || 0
  22200. };
  22201. },
  22202. measureBox: function(elm, prefix) {
  22203. function getStyle(name) {
  22204. var defaultView = document.defaultView;
  22205. if (defaultView) {
  22206. // Remove camelcase
  22207. name = name.replace(/[A-Z]/g, function(a) {
  22208. return '-' + a;
  22209. });
  22210. return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
  22211. }
  22212. return elm.currentStyle[name];
  22213. }
  22214. function getSide(name) {
  22215. var val = parseFloat(getStyle(name), 10);
  22216. return isNaN(val) ? 0 : val;
  22217. }
  22218. return {
  22219. top: getSide(prefix + "TopWidth"),
  22220. right: getSide(prefix + "RightWidth"),
  22221. bottom: getSide(prefix + "BottomWidth"),
  22222. left: getSide(prefix + "LeftWidth")
  22223. };
  22224. }
  22225. };
  22226. });
  22227. // Included from: js/tinymce/classes/ui/ClassList.js
  22228. /**
  22229. * ClassList.js
  22230. *
  22231. * Released under LGPL License.
  22232. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  22233. *
  22234. * License: http://www.tinymce.com/license
  22235. * Contributing: http://www.tinymce.com/contributing
  22236. */
  22237. /**
  22238. * Handles adding and removal of classes.
  22239. *
  22240. * @private
  22241. * @class tinymce.ui.ClassList
  22242. */
  22243. define("tinymce/ui/ClassList", [
  22244. "tinymce/util/Tools"
  22245. ], function(Tools) {
  22246. "use strict";
  22247. function noop() {
  22248. }
  22249. /**
  22250. * Constructs a new class list the specified onchange
  22251. * callback will be executed when the class list gets modifed.
  22252. *
  22253. * @constructor ClassList
  22254. * @param {function} onchange Onchange callback to be executed.
  22255. */
  22256. function ClassList(onchange) {
  22257. this.cls = [];
  22258. this.cls._map = {};
  22259. this.onchange = onchange || noop;
  22260. this.prefix = '';
  22261. }
  22262. Tools.extend(ClassList.prototype, {
  22263. /**
  22264. * Adds a new class to the class list.
  22265. *
  22266. * @method add
  22267. * @param {String} cls Class to be added.
  22268. * @return {tinymce.ui.ClassList} Current class list instance.
  22269. */
  22270. add: function(cls) {
  22271. if (cls && !this.contains(cls)) {
  22272. this.cls._map[cls] = true;
  22273. this.cls.push(cls);
  22274. this._change();
  22275. }
  22276. return this;
  22277. },
  22278. /**
  22279. * Removes the specified class from the class list.
  22280. *
  22281. * @method remove
  22282. * @param {String} cls Class to be removed.
  22283. * @return {tinymce.ui.ClassList} Current class list instance.
  22284. */
  22285. remove: function(cls) {
  22286. if (this.contains(cls)) {
  22287. for (var i = 0; i < this.cls.length; i++) {
  22288. if (this.cls[i] === cls) {
  22289. break;
  22290. }
  22291. }
  22292. this.cls.splice(i, 1);
  22293. delete this.cls._map[cls];
  22294. this._change();
  22295. }
  22296. return this;
  22297. },
  22298. /**
  22299. * Toggles a class in the class list.
  22300. *
  22301. * @method toggle
  22302. * @param {String} cls Class to be added/removed.
  22303. * @param {Boolean} state Optional state if it should be added/removed.
  22304. * @return {tinymce.ui.ClassList} Current class list instance.
  22305. */
  22306. toggle: function(cls, state) {
  22307. var curState = this.contains(cls);
  22308. if (curState !== state) {
  22309. if (curState) {
  22310. this.remove(cls);
  22311. } else {
  22312. this.add(cls);
  22313. }
  22314. this._change();
  22315. }
  22316. return this;
  22317. },
  22318. /**
  22319. * Returns true if the class list has the specified class.
  22320. *
  22321. * @method contains
  22322. * @param {String} cls Class to look for.
  22323. * @return {Boolean} true/false if the class exists or not.
  22324. */
  22325. contains: function(cls) {
  22326. return !!this.cls._map[cls];
  22327. },
  22328. /**
  22329. * Returns a space separated list of classes.
  22330. *
  22331. * @method toString
  22332. * @return {String} Space separated list of classes.
  22333. */
  22334. _change: function() {
  22335. delete this.clsValue;
  22336. this.onchange.call(this);
  22337. }
  22338. });
  22339. // IE 8 compatibility
  22340. ClassList.prototype.toString = function() {
  22341. var value;
  22342. if (this.clsValue) {
  22343. return this.clsValue;
  22344. }
  22345. value = '';
  22346. for (var i = 0; i < this.cls.length; i++) {
  22347. if (i > 0) {
  22348. value += ' ';
  22349. }
  22350. value += this.prefix + this.cls[i];
  22351. }
  22352. return value;
  22353. };
  22354. return ClassList;
  22355. });
  22356. // Included from: js/tinymce/classes/ui/ReflowQueue.js
  22357. /**
  22358. * ReflowQueue.js
  22359. *
  22360. * Released under LGPL License.
  22361. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  22362. *
  22363. * License: http://www.tinymce.com/license
  22364. * Contributing: http://www.tinymce.com/contributing
  22365. */
  22366. /**
  22367. * This class will automatically reflow controls on the next animation frame within a few milliseconds on older browsers.
  22368. * If the user manually reflows then the automatic reflow will be cancelled. This class is used internally when various control states
  22369. * changes that triggers a reflow.
  22370. *
  22371. * @class tinymce.ui.ReflowQueue
  22372. * @static
  22373. */
  22374. define("tinymce/ui/ReflowQueue", [
  22375. "tinymce/util/Delay"
  22376. ], function(Delay) {
  22377. var dirtyCtrls = {}, animationFrameRequested;
  22378. return {
  22379. /**
  22380. * Adds a control to the next automatic reflow call. This is the control that had a state
  22381. * change for example if the control was hidden/shown.
  22382. *
  22383. * @method add
  22384. * @param {tinymce.ui.Control} ctrl Control to add to queue.
  22385. */
  22386. add: function(ctrl) {
  22387. var parent = ctrl.parent();
  22388. if (parent) {
  22389. if (!parent._layout || parent._layout.isNative()) {
  22390. return;
  22391. }
  22392. if (!dirtyCtrls[parent._id]) {
  22393. dirtyCtrls[parent._id] = parent;
  22394. }
  22395. if (!animationFrameRequested) {
  22396. animationFrameRequested = true;
  22397. Delay.requestAnimationFrame(function() {
  22398. var id, ctrl;
  22399. animationFrameRequested = false;
  22400. for (id in dirtyCtrls) {
  22401. ctrl = dirtyCtrls[id];
  22402. if (ctrl.state.get('rendered')) {
  22403. ctrl.reflow();
  22404. }
  22405. }
  22406. dirtyCtrls = {};
  22407. }, document.body);
  22408. }
  22409. }
  22410. },
  22411. /**
  22412. * Removes the specified control from the automatic reflow. This will happen when for example the user
  22413. * manually triggers a reflow.
  22414. *
  22415. * @method remove
  22416. * @param {tinymce.ui.Control} ctrl Control to remove from queue.
  22417. */
  22418. remove: function(ctrl) {
  22419. if (dirtyCtrls[ctrl._id]) {
  22420. delete dirtyCtrls[ctrl._id];
  22421. }
  22422. }
  22423. };
  22424. });
  22425. // Included from: js/tinymce/classes/ui/Control.js
  22426. /**
  22427. * Control.js
  22428. *
  22429. * Released under LGPL License.
  22430. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  22431. *
  22432. * License: http://www.tinymce.com/license
  22433. * Contributing: http://www.tinymce.com/contributing
  22434. */
  22435. /*eslint consistent-this:0 */
  22436. /**
  22437. * This is the base class for all controls and containers. All UI control instances inherit
  22438. * from this one as it has the base logic needed by all of them.
  22439. *
  22440. * @class tinymce.ui.Control
  22441. */
  22442. define("tinymce/ui/Control", [
  22443. "tinymce/util/Class",
  22444. "tinymce/util/Tools",
  22445. "tinymce/util/EventDispatcher",
  22446. "tinymce/data/ObservableObject",
  22447. "tinymce/ui/Collection",
  22448. "tinymce/ui/DomUtils",
  22449. "tinymce/dom/DomQuery",
  22450. "tinymce/ui/BoxUtils",
  22451. "tinymce/ui/ClassList",
  22452. "tinymce/ui/ReflowQueue"
  22453. ], function(Class, Tools, EventDispatcher, ObservableObject, Collection, DomUtils, $, BoxUtils, ClassList, ReflowQueue) {
  22454. "use strict";
  22455. var hasMouseWheelEventSupport = "onmousewheel" in document;
  22456. var hasWheelEventSupport = false;
  22457. var classPrefix = "mce-";
  22458. var Control, idCounter = 0;
  22459. var proto = {
  22460. Statics: {
  22461. classPrefix: classPrefix
  22462. },
  22463. isRtl: function() {
  22464. return Control.rtl;
  22465. },
  22466. /**
  22467. * Class/id prefix to use for all controls.
  22468. *
  22469. * @final
  22470. * @field {String} classPrefix
  22471. */
  22472. classPrefix: classPrefix,
  22473. /**
  22474. * Constructs a new control instance with the specified settings.
  22475. *
  22476. * @constructor
  22477. * @param {Object} settings Name/value object with settings.
  22478. * @setting {String} style Style CSS properties to add.
  22479. * @setting {String} border Border box values example: 1 1 1 1
  22480. * @setting {String} padding Padding box values example: 1 1 1 1
  22481. * @setting {String} margin Margin box values example: 1 1 1 1
  22482. * @setting {Number} minWidth Minimal width for the control.
  22483. * @setting {Number} minHeight Minimal height for the control.
  22484. * @setting {String} classes Space separated list of classes to add.
  22485. * @setting {String} role WAI-ARIA role to use for control.
  22486. * @setting {Boolean} hidden Is the control hidden by default.
  22487. * @setting {Boolean} disabled Is the control disabled by default.
  22488. * @setting {String} name Name of the control instance.
  22489. */
  22490. init: function(settings) {
  22491. var self = this, classes, defaultClasses;
  22492. function applyClasses(classes) {
  22493. var i;
  22494. classes = classes.split(' ');
  22495. for (i = 0; i < classes.length; i++) {
  22496. self.classes.add(classes[i]);
  22497. }
  22498. }
  22499. self.settings = settings = Tools.extend({}, self.Defaults, settings);
  22500. // Initial states
  22501. self._id = settings.id || ('mceu_' + (idCounter++));
  22502. self._aria = {role: settings.role};
  22503. self._elmCache = {};
  22504. self.$ = $;
  22505. self.state = new ObservableObject({
  22506. visible: true,
  22507. active: false,
  22508. disabled: false,
  22509. value: ''
  22510. });
  22511. self.data = new ObservableObject(settings.data);
  22512. self.classes = new ClassList(function() {
  22513. if (self.state.get('rendered')) {
  22514. self.getEl().className = this.toString();
  22515. }
  22516. });
  22517. self.classes.prefix = self.classPrefix;
  22518. // Setup classes
  22519. classes = settings.classes;
  22520. if (classes) {
  22521. if (self.Defaults) {
  22522. defaultClasses = self.Defaults.classes;
  22523. if (defaultClasses && classes != defaultClasses) {
  22524. applyClasses(defaultClasses);
  22525. }
  22526. }
  22527. applyClasses(classes);
  22528. }
  22529. Tools.each('title text name visible disabled active value'.split(' '), function(name) {
  22530. if (name in settings) {
  22531. self[name](settings[name]);
  22532. }
  22533. });
  22534. self.on('click', function() {
  22535. if (self.disabled()) {
  22536. return false;
  22537. }
  22538. });
  22539. /**
  22540. * Name/value object with settings for the current control.
  22541. *
  22542. * @field {Object} settings
  22543. */
  22544. self.settings = settings;
  22545. self.borderBox = BoxUtils.parseBox(settings.border);
  22546. self.paddingBox = BoxUtils.parseBox(settings.padding);
  22547. self.marginBox = BoxUtils.parseBox(settings.margin);
  22548. if (settings.hidden) {
  22549. self.hide();
  22550. }
  22551. },
  22552. // Will generate getter/setter methods for these properties
  22553. Properties: 'parent,name',
  22554. /**
  22555. * Returns the root element to render controls into.
  22556. *
  22557. * @method getContainerElm
  22558. * @return {Element} HTML DOM element to render into.
  22559. */
  22560. getContainerElm: function() {
  22561. return document.body;
  22562. },
  22563. /**
  22564. * Returns a control instance for the current DOM element.
  22565. *
  22566. * @method getParentCtrl
  22567. * @param {Element} elm HTML dom element to get parent control from.
  22568. * @return {tinymce.ui.Control} Control instance or undefined.
  22569. */
  22570. getParentCtrl: function(elm) {
  22571. var ctrl, lookup = this.getRoot().controlIdLookup;
  22572. while (elm && lookup) {
  22573. ctrl = lookup[elm.id];
  22574. if (ctrl) {
  22575. break;
  22576. }
  22577. elm = elm.parentNode;
  22578. }
  22579. return ctrl;
  22580. },
  22581. /**
  22582. * Initializes the current controls layout rect.
  22583. * This will be executed by the layout managers to determine the
  22584. * default minWidth/minHeight etc.
  22585. *
  22586. * @method initLayoutRect
  22587. * @return {Object} Layout rect instance.
  22588. */
  22589. initLayoutRect: function() {
  22590. var self = this, settings = self.settings, borderBox, layoutRect;
  22591. var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
  22592. var startMinWidth, startMinHeight, initialSize;
  22593. // Measure the current element
  22594. borderBox = self.borderBox = self.borderBox || BoxUtils.measureBox(elm, 'border');
  22595. self.paddingBox = self.paddingBox || BoxUtils.measureBox(elm, 'padding');
  22596. self.marginBox = self.marginBox || BoxUtils.measureBox(elm, 'margin');
  22597. initialSize = DomUtils.getSize(elm);
  22598. // Setup minWidth/minHeight and width/height
  22599. startMinWidth = settings.minWidth;
  22600. startMinHeight = settings.minHeight;
  22601. minWidth = startMinWidth || initialSize.width;
  22602. minHeight = startMinHeight || initialSize.height;
  22603. width = settings.width;
  22604. height = settings.height;
  22605. autoResize = settings.autoResize;
  22606. autoResize = typeof autoResize != "undefined" ? autoResize : !width && !height;
  22607. width = width || minWidth;
  22608. height = height || minHeight;
  22609. var deltaW = borderBox.left + borderBox.right;
  22610. var deltaH = borderBox.top + borderBox.bottom;
  22611. var maxW = settings.maxWidth || 0xFFFF;
  22612. var maxH = settings.maxHeight || 0xFFFF;
  22613. // Setup initial layout rect
  22614. self._layoutRect = layoutRect = {
  22615. x: settings.x || 0,
  22616. y: settings.y || 0,
  22617. w: width,
  22618. h: height,
  22619. deltaW: deltaW,
  22620. deltaH: deltaH,
  22621. contentW: width - deltaW,
  22622. contentH: height - deltaH,
  22623. innerW: width - deltaW,
  22624. innerH: height - deltaH,
  22625. startMinWidth: startMinWidth || 0,
  22626. startMinHeight: startMinHeight || 0,
  22627. minW: Math.min(minWidth, maxW),
  22628. minH: Math.min(minHeight, maxH),
  22629. maxW: maxW,
  22630. maxH: maxH,
  22631. autoResize: autoResize,
  22632. scrollW: 0
  22633. };
  22634. self._lastLayoutRect = {};
  22635. return layoutRect;
  22636. },
  22637. /**
  22638. * Getter/setter for the current layout rect.
  22639. *
  22640. * @method layoutRect
  22641. * @param {Object} [newRect] Optional new layout rect.
  22642. * @return {tinymce.ui.Control/Object} Current control or rect object.
  22643. */
  22644. layoutRect: function(newRect) {
  22645. var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
  22646. // Initialize default layout rect
  22647. if (!curRect) {
  22648. curRect = self.initLayoutRect();
  22649. }
  22650. // Set new rect values
  22651. if (newRect) {
  22652. // Calc deltas between inner and outer sizes
  22653. deltaWidth = curRect.deltaW;
  22654. deltaHeight = curRect.deltaH;
  22655. // Set x position
  22656. if (newRect.x !== undef) {
  22657. curRect.x = newRect.x;
  22658. }
  22659. // Set y position
  22660. if (newRect.y !== undef) {
  22661. curRect.y = newRect.y;
  22662. }
  22663. // Set minW
  22664. if (newRect.minW !== undef) {
  22665. curRect.minW = newRect.minW;
  22666. }
  22667. // Set minH
  22668. if (newRect.minH !== undef) {
  22669. curRect.minH = newRect.minH;
  22670. }
  22671. // Set new width and calculate inner width
  22672. size = newRect.w;
  22673. if (size !== undef) {
  22674. size = size < curRect.minW ? curRect.minW : size;
  22675. size = size > curRect.maxW ? curRect.maxW : size;
  22676. curRect.w = size;
  22677. curRect.innerW = size - deltaWidth;
  22678. }
  22679. // Set new height and calculate inner height
  22680. size = newRect.h;
  22681. if (size !== undef) {
  22682. size = size < curRect.minH ? curRect.minH : size;
  22683. size = size > curRect.maxH ? curRect.maxH : size;
  22684. curRect.h = size;
  22685. curRect.innerH = size - deltaHeight;
  22686. }
  22687. // Set new inner width and calculate width
  22688. size = newRect.innerW;
  22689. if (size !== undef) {
  22690. size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
  22691. size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
  22692. curRect.innerW = size;
  22693. curRect.w = size + deltaWidth;
  22694. }
  22695. // Set new height and calculate inner height
  22696. size = newRect.innerH;
  22697. if (size !== undef) {
  22698. size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
  22699. size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
  22700. curRect.innerH = size;
  22701. curRect.h = size + deltaHeight;
  22702. }
  22703. // Set new contentW
  22704. if (newRect.contentW !== undef) {
  22705. curRect.contentW = newRect.contentW;
  22706. }
  22707. // Set new contentH
  22708. if (newRect.contentH !== undef) {
  22709. curRect.contentH = newRect.contentH;
  22710. }
  22711. // Compare last layout rect with the current one to see if we need to repaint or not
  22712. lastLayoutRect = self._lastLayoutRect;
  22713. if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
  22714. lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
  22715. repaintControls = Control.repaintControls;
  22716. if (repaintControls) {
  22717. if (repaintControls.map && !repaintControls.map[self._id]) {
  22718. repaintControls.push(self);
  22719. repaintControls.map[self._id] = true;
  22720. }
  22721. }
  22722. lastLayoutRect.x = curRect.x;
  22723. lastLayoutRect.y = curRect.y;
  22724. lastLayoutRect.w = curRect.w;
  22725. lastLayoutRect.h = curRect.h;
  22726. }
  22727. return self;
  22728. }
  22729. return curRect;
  22730. },
  22731. /**
  22732. * Repaints the control after a layout operation.
  22733. *
  22734. * @method repaint
  22735. */
  22736. repaint: function() {
  22737. var self = this, style, bodyStyle, bodyElm, rect, borderBox;
  22738. var borderW, borderH, lastRepaintRect, round, value;
  22739. // Use Math.round on all values on IE < 9
  22740. round = !document.createRange ? Math.round : function(value) {
  22741. return value;
  22742. };
  22743. style = self.getEl().style;
  22744. rect = self._layoutRect;
  22745. lastRepaintRect = self._lastRepaintRect || {};
  22746. borderBox = self.borderBox;
  22747. borderW = borderBox.left + borderBox.right;
  22748. borderH = borderBox.top + borderBox.bottom;
  22749. if (rect.x !== lastRepaintRect.x) {
  22750. style.left = round(rect.x) + 'px';
  22751. lastRepaintRect.x = rect.x;
  22752. }
  22753. if (rect.y !== lastRepaintRect.y) {
  22754. style.top = round(rect.y) + 'px';
  22755. lastRepaintRect.y = rect.y;
  22756. }
  22757. if (rect.w !== lastRepaintRect.w) {
  22758. value = round(rect.w - borderW);
  22759. style.width = (value >= 0 ? value : 0) + 'px';
  22760. lastRepaintRect.w = rect.w;
  22761. }
  22762. if (rect.h !== lastRepaintRect.h) {
  22763. value = round(rect.h - borderH);
  22764. style.height = (value >= 0 ? value : 0) + 'px';
  22765. lastRepaintRect.h = rect.h;
  22766. }
  22767. // Update body if needed
  22768. if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
  22769. value = round(rect.innerW);
  22770. bodyElm = self.getEl('body');
  22771. if (bodyElm) {
  22772. bodyStyle = bodyElm.style;
  22773. bodyStyle.width = (value >= 0 ? value : 0) + 'px';
  22774. }
  22775. lastRepaintRect.innerW = rect.innerW;
  22776. }
  22777. if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
  22778. value = round(rect.innerH);
  22779. bodyElm = bodyElm || self.getEl('body');
  22780. if (bodyElm) {
  22781. bodyStyle = bodyStyle || bodyElm.style;
  22782. bodyStyle.height = (value >= 0 ? value : 0) + 'px';
  22783. }
  22784. lastRepaintRect.innerH = rect.innerH;
  22785. }
  22786. self._lastRepaintRect = lastRepaintRect;
  22787. self.fire('repaint', {}, false);
  22788. },
  22789. /**
  22790. * Updates the controls layout rect by re-measuing it.
  22791. */
  22792. updateLayoutRect: function() {
  22793. var self = this;
  22794. self.parent()._lastRect = null;
  22795. DomUtils.css(self.getEl(), {width: '', height: ''});
  22796. self._layoutRect = self._lastRepaintRect = self._lastLayoutRect = null;
  22797. self.initLayoutRect();
  22798. },
  22799. /**
  22800. * Binds a callback to the specified event. This event can both be
  22801. * native browser events like "click" or custom ones like PostRender.
  22802. *
  22803. * The callback function will be passed a DOM event like object that enables yout do stop propagation.
  22804. *
  22805. * @method on
  22806. * @param {String} name Name of the event to bind. For example "click".
  22807. * @param {String/function} callback Callback function to execute ones the event occurs.
  22808. * @return {tinymce.ui.Control} Current control object.
  22809. */
  22810. on: function(name, callback) {
  22811. var self = this;
  22812. function resolveCallbackName(name) {
  22813. var callback, scope;
  22814. if (typeof name != 'string') {
  22815. return name;
  22816. }
  22817. return function(e) {
  22818. if (!callback) {
  22819. self.parentsAndSelf().each(function(ctrl) {
  22820. var callbacks = ctrl.settings.callbacks;
  22821. if (callbacks && (callback = callbacks[name])) {
  22822. scope = ctrl;
  22823. return false;
  22824. }
  22825. });
  22826. }
  22827. if (!callback) {
  22828. e.action = name;
  22829. this.fire('execute', e);
  22830. return;
  22831. }
  22832. return callback.call(scope, e);
  22833. };
  22834. }
  22835. getEventDispatcher(self).on(name, resolveCallbackName(callback));
  22836. return self;
  22837. },
  22838. /**
  22839. * Unbinds the specified event and optionally a specific callback. If you omit the name
  22840. * parameter all event handlers will be removed. If you omit the callback all event handles
  22841. * by the specified name will be removed.
  22842. *
  22843. * @method off
  22844. * @param {String} [name] Name for the event to unbind.
  22845. * @param {function} [callback] Callback function to unbind.
  22846. * @return {tinymce.ui.Control} Current control object.
  22847. */
  22848. off: function(name, callback) {
  22849. getEventDispatcher(this).off(name, callback);
  22850. return this;
  22851. },
  22852. /**
  22853. * Fires the specified event by name and arguments on the control. This will execute all
  22854. * bound event handlers.
  22855. *
  22856. * @method fire
  22857. * @param {String} name Name of the event to fire.
  22858. * @param {Object} [args] Arguments to pass to the event.
  22859. * @param {Boolean} [bubble] Value to control bubbling. Defaults to true.
  22860. * @return {Object} Current arguments object.
  22861. */
  22862. fire: function(name, args, bubble) {
  22863. var self = this;
  22864. args = args || {};
  22865. if (!args.control) {
  22866. args.control = self;
  22867. }
  22868. args = getEventDispatcher(self).fire(name, args);
  22869. // Bubble event up to parents
  22870. if (bubble !== false && self.parent) {
  22871. var parent = self.parent();
  22872. while (parent && !args.isPropagationStopped()) {
  22873. parent.fire(name, args, false);
  22874. parent = parent.parent();
  22875. }
  22876. }
  22877. return args;
  22878. },
  22879. /**
  22880. * Returns true/false if the specified event has any listeners.
  22881. *
  22882. * @method hasEventListeners
  22883. * @param {String} name Name of the event to check for.
  22884. * @return {Boolean} True/false state if the event has listeners.
  22885. */
  22886. hasEventListeners: function(name) {
  22887. return getEventDispatcher(this).has(name);
  22888. },
  22889. /**
  22890. * Returns a control collection with all parent controls.
  22891. *
  22892. * @method parents
  22893. * @param {String} selector Optional selector expression to find parents.
  22894. * @return {tinymce.ui.Collection} Collection with all parent controls.
  22895. */
  22896. parents: function(selector) {
  22897. var self = this, ctrl, parents = new Collection();
  22898. // Add each parent to collection
  22899. for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
  22900. parents.add(ctrl);
  22901. }
  22902. // Filter away everything that doesn't match the selector
  22903. if (selector) {
  22904. parents = parents.filter(selector);
  22905. }
  22906. return parents;
  22907. },
  22908. /**
  22909. * Returns the current control and it's parents.
  22910. *
  22911. * @method parentsAndSelf
  22912. * @param {String} selector Optional selector expression to find parents.
  22913. * @return {tinymce.ui.Collection} Collection with all parent controls.
  22914. */
  22915. parentsAndSelf: function(selector) {
  22916. return new Collection(this).add(this.parents(selector));
  22917. },
  22918. /**
  22919. * Returns the control next to the current control.
  22920. *
  22921. * @method next
  22922. * @return {tinymce.ui.Control} Next control instance.
  22923. */
  22924. next: function() {
  22925. var parentControls = this.parent().items();
  22926. return parentControls[parentControls.indexOf(this) + 1];
  22927. },
  22928. /**
  22929. * Returns the control previous to the current control.
  22930. *
  22931. * @method prev
  22932. * @return {tinymce.ui.Control} Previous control instance.
  22933. */
  22934. prev: function() {
  22935. var parentControls = this.parent().items();
  22936. return parentControls[parentControls.indexOf(this) - 1];
  22937. },
  22938. /**
  22939. * Sets the inner HTML of the control element.
  22940. *
  22941. * @method innerHtml
  22942. * @param {String} html Html string to set as inner html.
  22943. * @return {tinymce.ui.Control} Current control object.
  22944. */
  22945. innerHtml: function(html) {
  22946. this.$el.html(html);
  22947. return this;
  22948. },
  22949. /**
  22950. * Returns the control DOM element or sub element.
  22951. *
  22952. * @method getEl
  22953. * @param {String} [suffix] Suffix to get element by.
  22954. * @return {Element} HTML DOM element for the current control or it's children.
  22955. */
  22956. getEl: function(suffix) {
  22957. var id = suffix ? this._id + '-' + suffix : this._id;
  22958. if (!this._elmCache[id]) {
  22959. this._elmCache[id] = $('#' + id)[0];
  22960. }
  22961. return this._elmCache[id];
  22962. },
  22963. /**
  22964. * Sets the visible state to true.
  22965. *
  22966. * @method show
  22967. * @return {tinymce.ui.Control} Current control instance.
  22968. */
  22969. show: function() {
  22970. return this.visible(true);
  22971. },
  22972. /**
  22973. * Sets the visible state to false.
  22974. *
  22975. * @method hide
  22976. * @return {tinymce.ui.Control} Current control instance.
  22977. */
  22978. hide: function() {
  22979. return this.visible(false);
  22980. },
  22981. /**
  22982. * Focuses the current control.
  22983. *
  22984. * @method focus
  22985. * @return {tinymce.ui.Control} Current control instance.
  22986. */
  22987. focus: function() {
  22988. try {
  22989. this.getEl().focus();
  22990. } catch (ex) {
  22991. // Ignore IE error
  22992. }
  22993. return this;
  22994. },
  22995. /**
  22996. * Blurs the current control.
  22997. *
  22998. * @method blur
  22999. * @return {tinymce.ui.Control} Current control instance.
  23000. */
  23001. blur: function() {
  23002. this.getEl().blur();
  23003. return this;
  23004. },
  23005. /**
  23006. * Sets the specified aria property.
  23007. *
  23008. * @method aria
  23009. * @param {String} name Name of the aria property to set.
  23010. * @param {String} value Value of the aria property.
  23011. * @return {tinymce.ui.Control} Current control instance.
  23012. */
  23013. aria: function(name, value) {
  23014. var self = this, elm = self.getEl(self.ariaTarget);
  23015. if (typeof value === "undefined") {
  23016. return self._aria[name];
  23017. }
  23018. self._aria[name] = value;
  23019. if (self.state.get('rendered')) {
  23020. elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
  23021. }
  23022. return self;
  23023. },
  23024. /**
  23025. * Encodes the specified string with HTML entities. It will also
  23026. * translate the string to different languages.
  23027. *
  23028. * @method encode
  23029. * @param {String/Object/Array} text Text to entity encode.
  23030. * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
  23031. * @return {String} Encoded and possible traslated string.
  23032. */
  23033. encode: function(text, translate) {
  23034. if (translate !== false) {
  23035. text = this.translate(text);
  23036. }
  23037. return (text || '').replace(/[&<>"]/g, function(match) {
  23038. return '&#' + match.charCodeAt(0) + ';';
  23039. });
  23040. },
  23041. /**
  23042. * Returns the translated string.
  23043. *
  23044. * @method translate
  23045. * @param {String} text Text to translate.
  23046. * @return {String} Translated string or the same as the input.
  23047. */
  23048. translate: function(text) {
  23049. return Control.translate ? Control.translate(text) : text;
  23050. },
  23051. /**
  23052. * Adds items before the current control.
  23053. *
  23054. * @method before
  23055. * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
  23056. * @return {tinymce.ui.Control} Current control instance.
  23057. */
  23058. before: function(items) {
  23059. var self = this, parent = self.parent();
  23060. if (parent) {
  23061. parent.insert(items, parent.items().indexOf(self), true);
  23062. }
  23063. return self;
  23064. },
  23065. /**
  23066. * Adds items after the current control.
  23067. *
  23068. * @method after
  23069. * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
  23070. * @return {tinymce.ui.Control} Current control instance.
  23071. */
  23072. after: function(items) {
  23073. var self = this, parent = self.parent();
  23074. if (parent) {
  23075. parent.insert(items, parent.items().indexOf(self));
  23076. }
  23077. return self;
  23078. },
  23079. /**
  23080. * Removes the current control from DOM and from UI collections.
  23081. *
  23082. * @method remove
  23083. * @return {tinymce.ui.Control} Current control instance.
  23084. */
  23085. remove: function() {
  23086. var self = this, elm = self.getEl(), parent = self.parent(), newItems, i;
  23087. if (self.items) {
  23088. var controls = self.items().toArray();
  23089. i = controls.length;
  23090. while (i--) {
  23091. controls[i].remove();
  23092. }
  23093. }
  23094. if (parent && parent.items) {
  23095. newItems = [];
  23096. parent.items().each(function(item) {
  23097. if (item !== self) {
  23098. newItems.push(item);
  23099. }
  23100. });
  23101. parent.items().set(newItems);
  23102. parent._lastRect = null;
  23103. }
  23104. if (self._eventsRoot && self._eventsRoot == self) {
  23105. $(elm).off();
  23106. }
  23107. var lookup = self.getRoot().controlIdLookup;
  23108. if (lookup) {
  23109. delete lookup[self._id];
  23110. }
  23111. if (elm && elm.parentNode) {
  23112. elm.parentNode.removeChild(elm);
  23113. }
  23114. self.state.set('rendered', false);
  23115. self.state.destroy();
  23116. self.fire('remove');
  23117. return self;
  23118. },
  23119. /**
  23120. * Renders the control before the specified element.
  23121. *
  23122. * @method renderBefore
  23123. * @param {Element} elm Element to render before.
  23124. * @return {tinymce.ui.Control} Current control instance.
  23125. */
  23126. renderBefore: function(elm) {
  23127. $(elm).before(this.renderHtml());
  23128. this.postRender();
  23129. return this;
  23130. },
  23131. /**
  23132. * Renders the control to the specified element.
  23133. *
  23134. * @method renderBefore
  23135. * @param {Element} elm Element to render to.
  23136. * @return {tinymce.ui.Control} Current control instance.
  23137. */
  23138. renderTo: function(elm) {
  23139. $(elm || this.getContainerElm()).append(this.renderHtml());
  23140. this.postRender();
  23141. return this;
  23142. },
  23143. preRender: function() {
  23144. },
  23145. render: function() {
  23146. },
  23147. renderHtml: function() {
  23148. return '<div id="' + this._id + '" class="' + this.classes + '"></div>';
  23149. },
  23150. /**
  23151. * Post render method. Called after the control has been rendered to the target.
  23152. *
  23153. * @method postRender
  23154. * @return {tinymce.ui.Control} Current control instance.
  23155. */
  23156. postRender: function() {
  23157. var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
  23158. self.$el = $(self.getEl());
  23159. self.state.set('rendered', true);
  23160. // Bind on<event> settings
  23161. for (name in settings) {
  23162. if (name.indexOf("on") === 0) {
  23163. self.on(name.substr(2), settings[name]);
  23164. }
  23165. }
  23166. if (self._eventsRoot) {
  23167. for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) {
  23168. parentEventsRoot = parent._eventsRoot;
  23169. }
  23170. if (parentEventsRoot) {
  23171. for (name in parentEventsRoot._nativeEvents) {
  23172. self._nativeEvents[name] = true;
  23173. }
  23174. }
  23175. }
  23176. bindPendingEvents(self);
  23177. if (settings.style) {
  23178. elm = self.getEl();
  23179. if (elm) {
  23180. elm.setAttribute('style', settings.style);
  23181. elm.style.cssText = settings.style;
  23182. }
  23183. }
  23184. if (self.settings.border) {
  23185. box = self.borderBox;
  23186. self.$el.css({
  23187. 'border-top-width': box.top,
  23188. 'border-right-width': box.right,
  23189. 'border-bottom-width': box.bottom,
  23190. 'border-left-width': box.left
  23191. });
  23192. }
  23193. // Add instance to lookup
  23194. var root = self.getRoot();
  23195. if (!root.controlIdLookup) {
  23196. root.controlIdLookup = {};
  23197. }
  23198. root.controlIdLookup[self._id] = self;
  23199. for (var key in self._aria) {
  23200. self.aria(key, self._aria[key]);
  23201. }
  23202. if (self.state.get('visible') === false) {
  23203. self.getEl().style.display = 'none';
  23204. }
  23205. self.bindStates();
  23206. self.state.on('change:visible', function(e) {
  23207. var state = e.value, parentCtrl;
  23208. if (self.state.get('rendered')) {
  23209. self.getEl().style.display = state === false ? 'none' : '';
  23210. // Need to force a reflow here on IE 8
  23211. self.getEl().getBoundingClientRect();
  23212. }
  23213. // Parent container needs to reflow
  23214. parentCtrl = self.parent();
  23215. if (parentCtrl) {
  23216. parentCtrl._lastRect = null;
  23217. }
  23218. self.fire(state ? 'show' : 'hide');
  23219. ReflowQueue.add(self);
  23220. });
  23221. self.fire('postrender', {}, false);
  23222. },
  23223. bindStates: function() {
  23224. },
  23225. /**
  23226. * Scrolls the current control into view.
  23227. *
  23228. * @method scrollIntoView
  23229. * @param {String} align Alignment in view top|center|bottom.
  23230. * @return {tinymce.ui.Control} Current control instance.
  23231. */
  23232. scrollIntoView: function(align) {
  23233. function getOffset(elm, rootElm) {
  23234. var x, y, parent = elm;
  23235. x = y = 0;
  23236. while (parent && parent != rootElm && parent.nodeType) {
  23237. x += parent.offsetLeft || 0;
  23238. y += parent.offsetTop || 0;
  23239. parent = parent.offsetParent;
  23240. }
  23241. return {x: x, y: y};
  23242. }
  23243. var elm = this.getEl(), parentElm = elm.parentNode;
  23244. var x, y, width, height, parentWidth, parentHeight;
  23245. var pos = getOffset(elm, parentElm);
  23246. x = pos.x;
  23247. y = pos.y;
  23248. width = elm.offsetWidth;
  23249. height = elm.offsetHeight;
  23250. parentWidth = parentElm.clientWidth;
  23251. parentHeight = parentElm.clientHeight;
  23252. if (align == "end") {
  23253. x -= parentWidth - width;
  23254. y -= parentHeight - height;
  23255. } else if (align == "center") {
  23256. x -= (parentWidth / 2) - (width / 2);
  23257. y -= (parentHeight / 2) - (height / 2);
  23258. }
  23259. parentElm.scrollLeft = x;
  23260. parentElm.scrollTop = y;
  23261. return this;
  23262. },
  23263. getRoot: function() {
  23264. var ctrl = this, rootControl, parents = [];
  23265. while (ctrl) {
  23266. if (ctrl.rootControl) {
  23267. rootControl = ctrl.rootControl;
  23268. break;
  23269. }
  23270. parents.push(ctrl);
  23271. rootControl = ctrl;
  23272. ctrl = ctrl.parent();
  23273. }
  23274. if (!rootControl) {
  23275. rootControl = this;
  23276. }
  23277. var i = parents.length;
  23278. while (i--) {
  23279. parents[i].rootControl = rootControl;
  23280. }
  23281. return rootControl;
  23282. },
  23283. /**
  23284. * Reflows the current control and it's parents.
  23285. * This should be used after you for example append children to the current control so
  23286. * that the layout managers know that they need to reposition everything.
  23287. *
  23288. * @example
  23289. * container.append({type: 'button', text: 'My button'}).reflow();
  23290. *
  23291. * @method reflow
  23292. * @return {tinymce.ui.Control} Current control instance.
  23293. */
  23294. reflow: function() {
  23295. ReflowQueue.remove(this);
  23296. var parent = this.parent();
  23297. if (parent._layout && !parent._layout.isNative()) {
  23298. parent.reflow();
  23299. }
  23300. return this;
  23301. }
  23302. /**
  23303. * Sets/gets the parent container for the control.
  23304. *
  23305. * @method parent
  23306. * @param {tinymce.ui.Container} parent Optional parent to set.
  23307. * @return {tinymce.ui.Control} Parent control or the current control on a set action.
  23308. */
  23309. // parent: function(parent) {} -- Generated
  23310. /**
  23311. * Sets/gets the text for the control.
  23312. *
  23313. * @method text
  23314. * @param {String} value Value to set to control.
  23315. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
  23316. */
  23317. // text: function(value) {} -- Generated
  23318. /**
  23319. * Sets/gets the disabled state on the control.
  23320. *
  23321. * @method disabled
  23322. * @param {Boolean} state Value to set to control.
  23323. * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
  23324. */
  23325. // disabled: function(state) {} -- Generated
  23326. /**
  23327. * Sets/gets the active for the control.
  23328. *
  23329. * @method active
  23330. * @param {Boolean} state Value to set to control.
  23331. * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
  23332. */
  23333. // active: function(state) {} -- Generated
  23334. /**
  23335. * Sets/gets the name for the control.
  23336. *
  23337. * @method name
  23338. * @param {String} value Value to set to control.
  23339. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
  23340. */
  23341. // name: function(value) {} -- Generated
  23342. /**
  23343. * Sets/gets the title for the control.
  23344. *
  23345. * @method title
  23346. * @param {String} value Value to set to control.
  23347. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
  23348. */
  23349. // title: function(value) {} -- Generated
  23350. /**
  23351. * Sets/gets the visible for the control.
  23352. *
  23353. * @method visible
  23354. * @param {Boolean} state Value to set to control.
  23355. * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
  23356. */
  23357. // visible: function(value) {} -- Generated
  23358. };
  23359. /**
  23360. * Setup state properties.
  23361. */
  23362. Tools.each('text title visible disabled active value'.split(' '), function(name) {
  23363. proto[name] = function(value) {
  23364. if (arguments.length === 0) {
  23365. return this.state.get(name);
  23366. }
  23367. if (typeof value != "undefined") {
  23368. this.state.set(name, value);
  23369. }
  23370. return this;
  23371. };
  23372. });
  23373. Control = Class.extend(proto);
  23374. function getEventDispatcher(obj) {
  23375. if (!obj._eventDispatcher) {
  23376. obj._eventDispatcher = new EventDispatcher({
  23377. scope: obj,
  23378. toggleEvent: function(name, state) {
  23379. if (state && EventDispatcher.isNative(name)) {
  23380. if (!obj._nativeEvents) {
  23381. obj._nativeEvents = {};
  23382. }
  23383. obj._nativeEvents[name] = true;
  23384. if (obj.state.get('rendered')) {
  23385. bindPendingEvents(obj);
  23386. }
  23387. }
  23388. }
  23389. });
  23390. }
  23391. return obj._eventDispatcher;
  23392. }
  23393. function bindPendingEvents(eventCtrl) {
  23394. var i, l, parents, eventRootCtrl, nativeEvents, name;
  23395. function delegate(e) {
  23396. var control = eventCtrl.getParentCtrl(e.target);
  23397. if (control) {
  23398. control.fire(e.type, e);
  23399. }
  23400. }
  23401. function mouseLeaveHandler() {
  23402. var ctrl = eventRootCtrl._lastHoverCtrl;
  23403. if (ctrl) {
  23404. ctrl.fire("mouseleave", {target: ctrl.getEl()});
  23405. ctrl.parents().each(function(ctrl) {
  23406. ctrl.fire("mouseleave", {target: ctrl.getEl()});
  23407. });
  23408. eventRootCtrl._lastHoverCtrl = null;
  23409. }
  23410. }
  23411. function mouseEnterHandler(e) {
  23412. var ctrl = eventCtrl.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents;
  23413. // Over on a new control
  23414. if (ctrl !== lastCtrl) {
  23415. eventRootCtrl._lastHoverCtrl = ctrl;
  23416. parents = ctrl.parents().toArray().reverse();
  23417. parents.push(ctrl);
  23418. if (lastCtrl) {
  23419. lastParents = lastCtrl.parents().toArray().reverse();
  23420. lastParents.push(lastCtrl);
  23421. for (idx = 0; idx < lastParents.length; idx++) {
  23422. if (parents[idx] !== lastParents[idx]) {
  23423. break;
  23424. }
  23425. }
  23426. for (i = lastParents.length - 1; i >= idx; i--) {
  23427. lastCtrl = lastParents[i];
  23428. lastCtrl.fire("mouseleave", {
  23429. target: lastCtrl.getEl()
  23430. });
  23431. }
  23432. }
  23433. for (i = idx; i < parents.length; i++) {
  23434. ctrl = parents[i];
  23435. ctrl.fire("mouseenter", {
  23436. target: ctrl.getEl()
  23437. });
  23438. }
  23439. }
  23440. }
  23441. function fixWheelEvent(e) {
  23442. e.preventDefault();
  23443. if (e.type == "mousewheel") {
  23444. e.deltaY = -1 / 40 * e.wheelDelta;
  23445. if (e.wheelDeltaX) {
  23446. e.deltaX = -1 / 40 * e.wheelDeltaX;
  23447. }
  23448. } else {
  23449. e.deltaX = 0;
  23450. e.deltaY = e.detail;
  23451. }
  23452. e = eventCtrl.fire("wheel", e);
  23453. }
  23454. nativeEvents = eventCtrl._nativeEvents;
  23455. if (nativeEvents) {
  23456. // Find event root element if it exists
  23457. parents = eventCtrl.parents().toArray();
  23458. parents.unshift(eventCtrl);
  23459. for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) {
  23460. eventRootCtrl = parents[i]._eventsRoot;
  23461. }
  23462. // Event root wasn't found the use the root control
  23463. if (!eventRootCtrl) {
  23464. eventRootCtrl = parents[parents.length - 1] || eventCtrl;
  23465. }
  23466. // Set the eventsRoot property on children that didn't have it
  23467. eventCtrl._eventsRoot = eventRootCtrl;
  23468. for (l = i, i = 0; i < l; i++) {
  23469. parents[i]._eventsRoot = eventRootCtrl;
  23470. }
  23471. var eventRootDelegates = eventRootCtrl._delegates;
  23472. if (!eventRootDelegates) {
  23473. eventRootDelegates = eventRootCtrl._delegates = {};
  23474. }
  23475. // Bind native event delegates
  23476. for (name in nativeEvents) {
  23477. if (!nativeEvents) {
  23478. return false;
  23479. }
  23480. if (name === "wheel" && !hasWheelEventSupport) {
  23481. if (hasMouseWheelEventSupport) {
  23482. $(eventCtrl.getEl()).on("mousewheel", fixWheelEvent);
  23483. } else {
  23484. $(eventCtrl.getEl()).on("DOMMouseScroll", fixWheelEvent);
  23485. }
  23486. continue;
  23487. }
  23488. // Special treatment for mousenter/mouseleave since these doesn't bubble
  23489. if (name === "mouseenter" || name === "mouseleave") {
  23490. // Fake mousenter/mouseleave
  23491. if (!eventRootCtrl._hasMouseEnter) {
  23492. $(eventRootCtrl.getEl()).on("mouseleave", mouseLeaveHandler).on("mouseover", mouseEnterHandler);
  23493. eventRootCtrl._hasMouseEnter = 1;
  23494. }
  23495. } else if (!eventRootDelegates[name]) {
  23496. $(eventRootCtrl.getEl()).on(name, delegate);
  23497. eventRootDelegates[name] = true;
  23498. }
  23499. // Remove the event once it's bound
  23500. nativeEvents[name] = false;
  23501. }
  23502. }
  23503. }
  23504. return Control;
  23505. });
  23506. // Included from: js/tinymce/classes/ui/Factory.js
  23507. /**
  23508. * Factory.js
  23509. *
  23510. * Released under LGPL License.
  23511. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  23512. *
  23513. * License: http://www.tinymce.com/license
  23514. * Contributing: http://www.tinymce.com/contributing
  23515. */
  23516. /*global tinymce:true */
  23517. /**
  23518. * This class is a factory for control instances. This enables you
  23519. * to create instances of controls without having to require the UI controls directly.
  23520. *
  23521. * It also allow you to override or add new control types.
  23522. *
  23523. * @class tinymce.ui.Factory
  23524. */
  23525. define("tinymce/ui/Factory", [], function() {
  23526. "use strict";
  23527. var types = {}, namespaceInit;
  23528. return {
  23529. /**
  23530. * Adds a new control instance type to the factory.
  23531. *
  23532. * @method add
  23533. * @param {String} type Type name for example "button".
  23534. * @param {function} typeClass Class type function.
  23535. */
  23536. add: function(type, typeClass) {
  23537. types[type.toLowerCase()] = typeClass;
  23538. },
  23539. /**
  23540. * Returns true/false if the specified type exists or not.
  23541. *
  23542. * @method has
  23543. * @param {String} type Type to look for.
  23544. * @return {Boolean} true/false if the control by name exists.
  23545. */
  23546. has: function(type) {
  23547. return !!types[type.toLowerCase()];
  23548. },
  23549. /**
  23550. * Creates a new control instance based on the settings provided. The instance created will be
  23551. * based on the specified type property it can also create whole structures of components out of
  23552. * the specified JSON object.
  23553. *
  23554. * @example
  23555. * tinymce.ui.Factory.create({
  23556. * type: 'button',
  23557. * text: 'Hello world!'
  23558. * });
  23559. *
  23560. * @method create
  23561. * @param {Object/String} settings Name/Value object with items used to create the type.
  23562. * @return {tinymce.ui.Control} Control instance based on the specified type.
  23563. */
  23564. create: function(type, settings) {
  23565. var ControlType, name, namespace;
  23566. // Build type lookup
  23567. if (!namespaceInit) {
  23568. namespace = tinymce.ui;
  23569. for (name in namespace) {
  23570. types[name.toLowerCase()] = namespace[name];
  23571. }
  23572. namespaceInit = true;
  23573. }
  23574. // If string is specified then use it as the type
  23575. if (typeof type == 'string') {
  23576. settings = settings || {};
  23577. settings.type = type;
  23578. } else {
  23579. settings = type;
  23580. type = settings.type;
  23581. }
  23582. // Find control type
  23583. type = type.toLowerCase();
  23584. ControlType = types[type];
  23585. // #if debug
  23586. if (!ControlType) {
  23587. throw new Error("Could not find control by type: " + type);
  23588. }
  23589. // #endif
  23590. ControlType = new ControlType(settings);
  23591. ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine
  23592. return ControlType;
  23593. }
  23594. };
  23595. });
  23596. // Included from: js/tinymce/classes/ui/KeyboardNavigation.js
  23597. /**
  23598. * KeyboardNavigation.js
  23599. *
  23600. * Released under LGPL License.
  23601. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  23602. *
  23603. * License: http://www.tinymce.com/license
  23604. * Contributing: http://www.tinymce.com/contributing
  23605. */
  23606. /**
  23607. * This class handles keyboard navigation of controls and elements.
  23608. *
  23609. * @class tinymce.ui.KeyboardNavigation
  23610. */
  23611. define("tinymce/ui/KeyboardNavigation", [
  23612. ], function() {
  23613. "use strict";
  23614. /**
  23615. * This class handles all keyboard navigation for WAI-ARIA support. Each root container
  23616. * gets an instance of this class.
  23617. *
  23618. * @constructor
  23619. */
  23620. return function(settings) {
  23621. var root = settings.root, focusedElement, focusedControl;
  23622. function isElement(node) {
  23623. return node && node.nodeType === 1;
  23624. }
  23625. try {
  23626. focusedElement = document.activeElement;
  23627. } catch (ex) {
  23628. // IE sometimes fails to return a proper element
  23629. focusedElement = document.body;
  23630. }
  23631. focusedControl = root.getParentCtrl(focusedElement);
  23632. /**
  23633. * Returns the currently focused elements wai aria role of the currently
  23634. * focused element or specified element.
  23635. *
  23636. * @private
  23637. * @param {Element} elm Optional element to get role from.
  23638. * @return {String} Role of specified element.
  23639. */
  23640. function getRole(elm) {
  23641. elm = elm || focusedElement;
  23642. if (isElement(elm)) {
  23643. return elm.getAttribute('role');
  23644. }
  23645. return null;
  23646. }
  23647. /**
  23648. * Returns the wai role of the parent element of the currently
  23649. * focused element or specified element.
  23650. *
  23651. * @private
  23652. * @param {Element} elm Optional element to get parent role from.
  23653. * @return {String} Role of the first parent that has a role.
  23654. */
  23655. function getParentRole(elm) {
  23656. var role, parent = elm || focusedElement;
  23657. while ((parent = parent.parentNode)) {
  23658. if ((role = getRole(parent))) {
  23659. return role;
  23660. }
  23661. }
  23662. }
  23663. /**
  23664. * Returns a wai aria property by name for example aria-selected.
  23665. *
  23666. * @private
  23667. * @param {String} name Name of the aria property to get for example "disabled".
  23668. * @return {String} Aria property value.
  23669. */
  23670. function getAriaProp(name) {
  23671. var elm = focusedElement;
  23672. if (isElement(elm)) {
  23673. return elm.getAttribute('aria-' + name);
  23674. }
  23675. }
  23676. /**
  23677. * Is the element a text input element or not.
  23678. *
  23679. * @private
  23680. * @param {Element} elm Element to check if it's an text input element or not.
  23681. * @return {Boolean} True/false if the element is a text element or not.
  23682. */
  23683. function isTextInputElement(elm) {
  23684. var tagName = elm.tagName.toUpperCase();
  23685. // Notice: since type can be "email" etc we don't check the type
  23686. // So all input elements gets treated as text input elements
  23687. return tagName == "INPUT" || tagName == "TEXTAREA" || tagName == "SELECT";
  23688. }
  23689. /**
  23690. * Returns true/false if the specified element can be focused or not.
  23691. *
  23692. * @private
  23693. * @param {Element} elm DOM element to check if it can be focused or not.
  23694. * @return {Boolean} True/false if the element can have focus.
  23695. */
  23696. function canFocus(elm) {
  23697. if (isTextInputElement(elm) && !elm.hidden) {
  23698. return true;
  23699. }
  23700. if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell|slider)$/.test(getRole(elm))) {
  23701. return true;
  23702. }
  23703. return false;
  23704. }
  23705. /**
  23706. * Returns an array of focusable visible elements within the specified container element.
  23707. *
  23708. * @private
  23709. * @param {Element} elm DOM element to find focusable elements within.
  23710. * @return {Array} Array of focusable elements.
  23711. */
  23712. function getFocusElements(elm) {
  23713. var elements = [];
  23714. function collect(elm) {
  23715. if (elm.nodeType != 1 || elm.style.display == 'none') {
  23716. return;
  23717. }
  23718. if (canFocus(elm)) {
  23719. elements.push(elm);
  23720. }
  23721. for (var i = 0; i < elm.childNodes.length; i++) {
  23722. collect(elm.childNodes[i]);
  23723. }
  23724. }
  23725. collect(elm || root.getEl());
  23726. return elements;
  23727. }
  23728. /**
  23729. * Returns the navigation root control for the specified control. The navigation root
  23730. * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
  23731. * It will look for parents of the specified target control or the currently focused control if this option is omitted.
  23732. *
  23733. * @private
  23734. * @param {tinymce.ui.Control} targetControl Optional target control to find root of.
  23735. * @return {tinymce.ui.Control} Navigation root control.
  23736. */
  23737. function getNavigationRoot(targetControl) {
  23738. var navigationRoot, controls;
  23739. targetControl = targetControl || focusedControl;
  23740. controls = targetControl.parents().toArray();
  23741. controls.unshift(targetControl);
  23742. for (var i = 0; i < controls.length; i++) {
  23743. navigationRoot = controls[i];
  23744. if (navigationRoot.settings.ariaRoot) {
  23745. break;
  23746. }
  23747. }
  23748. return navigationRoot;
  23749. }
  23750. /**
  23751. * Focuses the first item in the specified targetControl element or the last aria index if the
  23752. * navigation root has the ariaRemember option enabled.
  23753. *
  23754. * @private
  23755. * @param {tinymce.ui.Control} targetControl Target control to focus the first item in.
  23756. */
  23757. function focusFirst(targetControl) {
  23758. var navigationRoot = getNavigationRoot(targetControl);
  23759. var focusElements = getFocusElements(navigationRoot.getEl());
  23760. if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
  23761. moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
  23762. } else {
  23763. moveFocusToIndex(0, focusElements);
  23764. }
  23765. }
  23766. /**
  23767. * Moves the focus to the specified index within the elements list.
  23768. * This will scope the index to the size of the element list if it changed.
  23769. *
  23770. * @private
  23771. * @param {Number} idx Specified index to move to.
  23772. * @param {Array} elements Array with dom elements to move focus within.
  23773. * @return {Number} Input index or a changed index if it was out of range.
  23774. */
  23775. function moveFocusToIndex(idx, elements) {
  23776. if (idx < 0) {
  23777. idx = elements.length - 1;
  23778. } else if (idx >= elements.length) {
  23779. idx = 0;
  23780. }
  23781. if (elements[idx]) {
  23782. elements[idx].focus();
  23783. }
  23784. return idx;
  23785. }
  23786. /**
  23787. * Moves the focus forwards or backwards.
  23788. *
  23789. * @private
  23790. * @param {Number} dir Direction to move in positive means forward, negative means backwards.
  23791. * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
  23792. */
  23793. function moveFocus(dir, elements) {
  23794. var idx = -1, navigationRoot = getNavigationRoot();
  23795. elements = elements || getFocusElements(navigationRoot.getEl());
  23796. for (var i = 0; i < elements.length; i++) {
  23797. if (elements[i] === focusedElement) {
  23798. idx = i;
  23799. }
  23800. }
  23801. idx += dir;
  23802. navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
  23803. }
  23804. /**
  23805. * Moves the focus to the left this is called by the left key.
  23806. *
  23807. * @private
  23808. */
  23809. function left() {
  23810. var parentRole = getParentRole();
  23811. if (parentRole == "tablist") {
  23812. moveFocus(-1, getFocusElements(focusedElement.parentNode));
  23813. } else if (focusedControl.parent().submenu) {
  23814. cancel();
  23815. } else {
  23816. moveFocus(-1);
  23817. }
  23818. }
  23819. /**
  23820. * Moves the focus to the right this is called by the right key.
  23821. *
  23822. * @private
  23823. */
  23824. function right() {
  23825. var role = getRole(), parentRole = getParentRole();
  23826. if (parentRole == "tablist") {
  23827. moveFocus(1, getFocusElements(focusedElement.parentNode));
  23828. } else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
  23829. enter();
  23830. } else {
  23831. moveFocus(1);
  23832. }
  23833. }
  23834. /**
  23835. * Moves the focus to the up this is called by the up key.
  23836. *
  23837. * @private
  23838. */
  23839. function up() {
  23840. moveFocus(-1);
  23841. }
  23842. /**
  23843. * Moves the focus to the up this is called by the down key.
  23844. *
  23845. * @private
  23846. */
  23847. function down() {
  23848. var role = getRole(), parentRole = getParentRole();
  23849. if (role == "menuitem" && parentRole == "menubar") {
  23850. enter();
  23851. } else if (role == "button" && getAriaProp('haspopup')) {
  23852. enter({key: 'down'});
  23853. } else {
  23854. moveFocus(1);
  23855. }
  23856. }
  23857. /**
  23858. * Moves the focus to the next item or previous item depending on shift key.
  23859. *
  23860. * @private
  23861. * @param {DOMEvent} e DOM event object.
  23862. */
  23863. function tab(e) {
  23864. var parentRole = getParentRole();
  23865. if (parentRole == "tablist") {
  23866. var elm = getFocusElements(focusedControl.getEl('body'))[0];
  23867. if (elm) {
  23868. elm.focus();
  23869. }
  23870. } else {
  23871. moveFocus(e.shiftKey ? -1 : 1);
  23872. }
  23873. }
  23874. /**
  23875. * Calls the cancel event on the currently focused control. This is normally done using the Esc key.
  23876. *
  23877. * @private
  23878. */
  23879. function cancel() {
  23880. focusedControl.fire('cancel');
  23881. }
  23882. /**
  23883. * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
  23884. *
  23885. * @private
  23886. * @param {Object} aria Optional aria data to pass along with the enter event.
  23887. */
  23888. function enter(aria) {
  23889. aria = aria || {};
  23890. focusedControl.fire('click', {target: focusedElement, aria: aria});
  23891. }
  23892. root.on('keydown', function(e) {
  23893. function handleNonTabOrEscEvent(e, handler) {
  23894. // Ignore non tab keys for text elements
  23895. if (isTextInputElement(focusedElement)) {
  23896. return;
  23897. }
  23898. if (getRole(focusedElement) === 'slider') {
  23899. return;
  23900. }
  23901. if (handler(e) !== false) {
  23902. e.preventDefault();
  23903. }
  23904. }
  23905. if (e.isDefaultPrevented()) {
  23906. return;
  23907. }
  23908. switch (e.keyCode) {
  23909. case 37: // DOM_VK_LEFT
  23910. handleNonTabOrEscEvent(e, left);
  23911. break;
  23912. case 39: // DOM_VK_RIGHT
  23913. handleNonTabOrEscEvent(e, right);
  23914. break;
  23915. case 38: // DOM_VK_UP
  23916. handleNonTabOrEscEvent(e, up);
  23917. break;
  23918. case 40: // DOM_VK_DOWN
  23919. handleNonTabOrEscEvent(e, down);
  23920. break;
  23921. case 27: // DOM_VK_ESCAPE
  23922. cancel();
  23923. break;
  23924. case 14: // DOM_VK_ENTER
  23925. case 13: // DOM_VK_RETURN
  23926. case 32: // DOM_VK_SPACE
  23927. handleNonTabOrEscEvent(e, enter);
  23928. break;
  23929. case 9: // DOM_VK_TAB
  23930. if (tab(e) !== false) {
  23931. e.preventDefault();
  23932. }
  23933. break;
  23934. }
  23935. });
  23936. root.on('focusin', function(e) {
  23937. focusedElement = e.target;
  23938. focusedControl = e.control;
  23939. });
  23940. return {
  23941. focusFirst: focusFirst
  23942. };
  23943. };
  23944. });
  23945. // Included from: js/tinymce/classes/ui/Container.js
  23946. /**
  23947. * Container.js
  23948. *
  23949. * Released under LGPL License.
  23950. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  23951. *
  23952. * License: http://www.tinymce.com/license
  23953. * Contributing: http://www.tinymce.com/contributing
  23954. */
  23955. /**
  23956. * Container control. This is extended by all controls that can have
  23957. * children such as panels etc. You can also use this class directly as an
  23958. * generic container instance. The container doesn't have any specific role or style.
  23959. *
  23960. * @-x-less Container.less
  23961. * @class tinymce.ui.Container
  23962. * @extends tinymce.ui.Control
  23963. */
  23964. define("tinymce/ui/Container", [
  23965. "tinymce/ui/Control",
  23966. "tinymce/ui/Collection",
  23967. "tinymce/ui/Selector",
  23968. "tinymce/ui/Factory",
  23969. "tinymce/ui/KeyboardNavigation",
  23970. "tinymce/util/Tools",
  23971. "tinymce/dom/DomQuery",
  23972. "tinymce/ui/ClassList",
  23973. "tinymce/ui/ReflowQueue"
  23974. ], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, $, ClassList, ReflowQueue) {
  23975. "use strict";
  23976. var selectorCache = {};
  23977. return Control.extend({
  23978. /**
  23979. * Constructs a new control instance with the specified settings.
  23980. *
  23981. * @constructor
  23982. * @param {Object} settings Name/value object with settings.
  23983. * @setting {Array} items Items to add to container in JSON format or control instances.
  23984. * @setting {String} layout Layout manager by name to use.
  23985. * @setting {Object} defaults Default settings to apply to all items.
  23986. */
  23987. init: function(settings) {
  23988. var self = this;
  23989. self._super(settings);
  23990. settings = self.settings;
  23991. if (settings.fixed) {
  23992. self.state.set('fixed', true);
  23993. }
  23994. self._items = new Collection();
  23995. if (self.isRtl()) {
  23996. self.classes.add('rtl');
  23997. }
  23998. self.bodyClasses = new ClassList(function() {
  23999. if (self.state.get('rendered')) {
  24000. self.getEl('body').className = this.toString();
  24001. }
  24002. });
  24003. self.bodyClasses.prefix = self.classPrefix;
  24004. self.classes.add('container');
  24005. self.bodyClasses.add('container-body');
  24006. if (settings.containerCls) {
  24007. self.classes.add(settings.containerCls);
  24008. }
  24009. self._layout = Factory.create((settings.layout || '') + 'layout');
  24010. if (self.settings.items) {
  24011. self.add(self.settings.items);
  24012. } else {
  24013. self.add(self.render());
  24014. }
  24015. // TODO: Fix this!
  24016. self._hasBody = true;
  24017. },
  24018. /**
  24019. * Returns a collection of child items that the container currently have.
  24020. *
  24021. * @method items
  24022. * @return {tinymce.ui.Collection} Control collection direct child controls.
  24023. */
  24024. items: function() {
  24025. return this._items;
  24026. },
  24027. /**
  24028. * Find child controls by selector.
  24029. *
  24030. * @method find
  24031. * @param {String} selector Selector CSS pattern to find children by.
  24032. * @return {tinymce.ui.Collection} Control collection with child controls.
  24033. */
  24034. find: function(selector) {
  24035. selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector);
  24036. return selector.find(this);
  24037. },
  24038. /**
  24039. * Adds one or many items to the current container. This will create instances of
  24040. * the object representations if needed.
  24041. *
  24042. * @method add
  24043. * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container.
  24044. * @return {tinymce.ui.Collection} Current collection control.
  24045. */
  24046. add: function(items) {
  24047. var self = this;
  24048. self.items().add(self.create(items)).parent(self);
  24049. return self;
  24050. },
  24051. /**
  24052. * Focuses the current container instance. This will look
  24053. * for the first control in the container and focus that.
  24054. *
  24055. * @method focus
  24056. * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
  24057. * @return {tinymce.ui.Collection} Current instance.
  24058. */
  24059. focus: function(keyboard) {
  24060. var self = this, focusCtrl, keyboardNav, items;
  24061. if (keyboard) {
  24062. keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;
  24063. if (keyboardNav) {
  24064. keyboardNav.focusFirst(self);
  24065. return;
  24066. }
  24067. }
  24068. items = self.find('*');
  24069. // TODO: Figure out a better way to auto focus alert dialog buttons
  24070. if (self.statusbar) {
  24071. items.add(self.statusbar.items());
  24072. }
  24073. items.each(function(ctrl) {
  24074. if (ctrl.settings.autofocus) {
  24075. focusCtrl = null;
  24076. return false;
  24077. }
  24078. if (ctrl.canFocus) {
  24079. focusCtrl = focusCtrl || ctrl;
  24080. }
  24081. });
  24082. if (focusCtrl) {
  24083. focusCtrl.focus();
  24084. }
  24085. return self;
  24086. },
  24087. /**
  24088. * Replaces the specified child control with a new control.
  24089. *
  24090. * @method replace
  24091. * @param {tinymce.ui.Control} oldItem Old item to be replaced.
  24092. * @param {tinymce.ui.Control} newItem New item to be inserted.
  24093. */
  24094. replace: function(oldItem, newItem) {
  24095. var ctrlElm, items = this.items(), i = items.length;
  24096. // Replace the item in collection
  24097. while (i--) {
  24098. if (items[i] === oldItem) {
  24099. items[i] = newItem;
  24100. break;
  24101. }
  24102. }
  24103. if (i >= 0) {
  24104. // Remove new item from DOM
  24105. ctrlElm = newItem.getEl();
  24106. if (ctrlElm) {
  24107. ctrlElm.parentNode.removeChild(ctrlElm);
  24108. }
  24109. // Remove old item from DOM
  24110. ctrlElm = oldItem.getEl();
  24111. if (ctrlElm) {
  24112. ctrlElm.parentNode.removeChild(ctrlElm);
  24113. }
  24114. }
  24115. // Adopt the item
  24116. newItem.parent(this);
  24117. },
  24118. /**
  24119. * Creates the specified items. If any of the items is plain JSON style objects
  24120. * it will convert these into real tinymce.ui.Control instances.
  24121. *
  24122. * @method create
  24123. * @param {Array} items Array of items to convert into control instances.
  24124. * @return {Array} Array with control instances.
  24125. */
  24126. create: function(items) {
  24127. var self = this, settings, ctrlItems = [];
  24128. // Non array structure, then force it into an array
  24129. if (!Tools.isArray(items)) {
  24130. items = [items];
  24131. }
  24132. // Add default type to each child control
  24133. Tools.each(items, function(item) {
  24134. if (item) {
  24135. // Construct item if needed
  24136. if (!(item instanceof Control)) {
  24137. // Name only then convert it to an object
  24138. if (typeof item == "string") {
  24139. item = {type: item};
  24140. }
  24141. // Create control instance based on input settings and default settings
  24142. settings = Tools.extend({}, self.settings.defaults, item);
  24143. item.type = settings.type = settings.type || item.type || self.settings.defaultType ||
  24144. (settings.defaults ? settings.defaults.type : null);
  24145. item = Factory.create(settings);
  24146. }
  24147. ctrlItems.push(item);
  24148. }
  24149. });
  24150. return ctrlItems;
  24151. },
  24152. /**
  24153. * Renders new control instances.
  24154. *
  24155. * @private
  24156. */
  24157. renderNew: function() {
  24158. var self = this;
  24159. // Render any new items
  24160. self.items().each(function(ctrl, index) {
  24161. var containerElm;
  24162. ctrl.parent(self);
  24163. if (!ctrl.state.get('rendered')) {
  24164. containerElm = self.getEl('body');
  24165. // Insert or append the item
  24166. if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) {
  24167. $(containerElm.childNodes[index]).before(ctrl.renderHtml());
  24168. } else {
  24169. $(containerElm).append(ctrl.renderHtml());
  24170. }
  24171. ctrl.postRender();
  24172. ReflowQueue.add(ctrl);
  24173. }
  24174. });
  24175. self._layout.applyClasses(self.items().filter(':visible'));
  24176. self._lastRect = null;
  24177. return self;
  24178. },
  24179. /**
  24180. * Appends new instances to the current container.
  24181. *
  24182. * @method append
  24183. * @param {Array/tinymce.ui.Collection} items Array if controls to append.
  24184. * @return {tinymce.ui.Container} Current container instance.
  24185. */
  24186. append: function(items) {
  24187. return this.add(items).renderNew();
  24188. },
  24189. /**
  24190. * Prepends new instances to the current container.
  24191. *
  24192. * @method prepend
  24193. * @param {Array/tinymce.ui.Collection} items Array if controls to prepend.
  24194. * @return {tinymce.ui.Container} Current container instance.
  24195. */
  24196. prepend: function(items) {
  24197. var self = this;
  24198. self.items().set(self.create(items).concat(self.items().toArray()));
  24199. return self.renderNew();
  24200. },
  24201. /**
  24202. * Inserts an control at a specific index.
  24203. *
  24204. * @method insert
  24205. * @param {Array/tinymce.ui.Collection} items Array if controls to insert.
  24206. * @param {Number} index Index to insert controls at.
  24207. * @param {Boolean} [before=false] Inserts controls before the index.
  24208. */
  24209. insert: function(items, index, before) {
  24210. var self = this, curItems, beforeItems, afterItems;
  24211. items = self.create(items);
  24212. curItems = self.items();
  24213. if (!before && index < curItems.length - 1) {
  24214. index += 1;
  24215. }
  24216. if (index >= 0 && index < curItems.length) {
  24217. beforeItems = curItems.slice(0, index).toArray();
  24218. afterItems = curItems.slice(index).toArray();
  24219. curItems.set(beforeItems.concat(items, afterItems));
  24220. }
  24221. return self.renderNew();
  24222. },
  24223. /**
  24224. * Populates the form fields from the specified JSON data object.
  24225. *
  24226. * Control items in the form that matches the data will have it's value set.
  24227. *
  24228. * @method fromJSON
  24229. * @param {Object} data JSON data object to set control values by.
  24230. * @return {tinymce.ui.Container} Current form instance.
  24231. */
  24232. fromJSON: function(data) {
  24233. var self = this;
  24234. for (var name in data) {
  24235. self.find('#' + name).value(data[name]);
  24236. }
  24237. return self;
  24238. },
  24239. /**
  24240. * Serializes the form into a JSON object by getting all items
  24241. * that has a name and a value.
  24242. *
  24243. * @method toJSON
  24244. * @return {Object} JSON object with form data.
  24245. */
  24246. toJSON: function() {
  24247. var self = this, data = {};
  24248. self.find('*').each(function(ctrl) {
  24249. var name = ctrl.name(), value = ctrl.value();
  24250. if (name && typeof value != "undefined") {
  24251. data[name] = value;
  24252. }
  24253. });
  24254. return data;
  24255. },
  24256. /**
  24257. * Renders the control as a HTML string.
  24258. *
  24259. * @method renderHtml
  24260. * @return {String} HTML representing the control.
  24261. */
  24262. renderHtml: function() {
  24263. var self = this, layout = self._layout, role = this.settings.role;
  24264. self.preRender();
  24265. layout.preRender(self);
  24266. return (
  24267. '<div id="' + self._id + '" class="' + self.classes + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' +
  24268. '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' +
  24269. (self.settings.html || '') + layout.renderHtml(self) +
  24270. '</div>' +
  24271. '</div>'
  24272. );
  24273. },
  24274. /**
  24275. * Post render method. Called after the control has been rendered to the target.
  24276. *
  24277. * @method postRender
  24278. * @return {tinymce.ui.Container} Current combobox instance.
  24279. */
  24280. postRender: function() {
  24281. var self = this, box;
  24282. self.items().exec('postRender');
  24283. self._super();
  24284. self._layout.postRender(self);
  24285. self.state.set('rendered', true);
  24286. if (self.settings.style) {
  24287. self.$el.css(self.settings.style);
  24288. }
  24289. if (self.settings.border) {
  24290. box = self.borderBox;
  24291. self.$el.css({
  24292. 'border-top-width': box.top,
  24293. 'border-right-width': box.right,
  24294. 'border-bottom-width': box.bottom,
  24295. 'border-left-width': box.left
  24296. });
  24297. }
  24298. if (!self.parent()) {
  24299. self.keyboardNav = new KeyboardNavigation({
  24300. root: self
  24301. });
  24302. }
  24303. return self;
  24304. },
  24305. /**
  24306. * Initializes the current controls layout rect.
  24307. * This will be executed by the layout managers to determine the
  24308. * default minWidth/minHeight etc.
  24309. *
  24310. * @method initLayoutRect
  24311. * @return {Object} Layout rect instance.
  24312. */
  24313. initLayoutRect: function() {
  24314. var self = this, layoutRect = self._super();
  24315. // Recalc container size by asking layout manager
  24316. self._layout.recalc(self);
  24317. return layoutRect;
  24318. },
  24319. /**
  24320. * Recalculates the positions of the controls in the current container.
  24321. * This is invoked by the reflow method and shouldn't be called directly.
  24322. *
  24323. * @method recalc
  24324. */
  24325. recalc: function() {
  24326. var self = this, rect = self._layoutRect, lastRect = self._lastRect;
  24327. if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) {
  24328. self._layout.recalc(self);
  24329. rect = self.layoutRect();
  24330. self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h};
  24331. return true;
  24332. }
  24333. },
  24334. /**
  24335. * Reflows the current container and it's children and possible parents.
  24336. * This should be used after you for example append children to the current control so
  24337. * that the layout managers know that they need to reposition everything.
  24338. *
  24339. * @example
  24340. * container.append({type: 'button', text: 'My button'}).reflow();
  24341. *
  24342. * @method reflow
  24343. * @return {tinymce.ui.Container} Current container instance.
  24344. */
  24345. reflow: function() {
  24346. var i;
  24347. ReflowQueue.remove(this);
  24348. if (this.visible()) {
  24349. Control.repaintControls = [];
  24350. Control.repaintControls.map = {};
  24351. this.recalc();
  24352. i = Control.repaintControls.length;
  24353. while (i--) {
  24354. Control.repaintControls[i].repaint();
  24355. }
  24356. // TODO: Fix me!
  24357. if (this.settings.layout !== "flow" && this.settings.layout !== "stack") {
  24358. this.repaint();
  24359. }
  24360. Control.repaintControls = [];
  24361. }
  24362. return this;
  24363. }
  24364. });
  24365. });
  24366. // Included from: js/tinymce/classes/ui/DragHelper.js
  24367. /**
  24368. * DragHelper.js
  24369. *
  24370. * Released under LGPL License.
  24371. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  24372. *
  24373. * License: http://www.tinymce.com/license
  24374. * Contributing: http://www.tinymce.com/contributing
  24375. */
  24376. /**
  24377. * Drag/drop helper class.
  24378. *
  24379. * @example
  24380. * var dragHelper = new tinymce.ui.DragHelper('mydiv', {
  24381. * start: function(evt) {
  24382. * },
  24383. *
  24384. * drag: function(evt) {
  24385. * },
  24386. *
  24387. * end: function(evt) {
  24388. * }
  24389. * });
  24390. *
  24391. * @class tinymce.ui.DragHelper
  24392. */
  24393. define("tinymce/ui/DragHelper", [
  24394. "tinymce/dom/DomQuery"
  24395. ], function($) {
  24396. "use strict";
  24397. function getDocumentSize(doc) {
  24398. var documentElement, body, scrollWidth, clientWidth;
  24399. var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max;
  24400. documentElement = doc.documentElement;
  24401. body = doc.body;
  24402. scrollWidth = max(documentElement.scrollWidth, body.scrollWidth);
  24403. clientWidth = max(documentElement.clientWidth, body.clientWidth);
  24404. offsetWidth = max(documentElement.offsetWidth, body.offsetWidth);
  24405. scrollHeight = max(documentElement.scrollHeight, body.scrollHeight);
  24406. clientHeight = max(documentElement.clientHeight, body.clientHeight);
  24407. offsetHeight = max(documentElement.offsetHeight, body.offsetHeight);
  24408. return {
  24409. width: scrollWidth < offsetWidth ? clientWidth : scrollWidth,
  24410. height: scrollHeight < offsetHeight ? clientHeight : scrollHeight
  24411. };
  24412. }
  24413. function updateWithTouchData(e) {
  24414. var keys, i;
  24415. if (e.changedTouches) {
  24416. keys = "screenX screenY pageX pageY clientX clientY".split(' ');
  24417. for (i = 0; i < keys.length; i++) {
  24418. e[keys[i]] = e.changedTouches[0][keys[i]];
  24419. }
  24420. }
  24421. }
  24422. return function(id, settings) {
  24423. var $eventOverlay, doc = settings.document || document, downButton, start, stop, drag, startX, startY;
  24424. settings = settings || {};
  24425. function getHandleElm() {
  24426. return doc.getElementById(settings.handle || id);
  24427. }
  24428. start = function(e) {
  24429. var docSize = getDocumentSize(doc), handleElm, cursor;
  24430. updateWithTouchData(e);
  24431. e.preventDefault();
  24432. downButton = e.button;
  24433. handleElm = getHandleElm();
  24434. startX = e.screenX;
  24435. startY = e.screenY;
  24436. // Grab cursor from handle so we can place it on overlay
  24437. if (window.getComputedStyle) {
  24438. cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor");
  24439. } else {
  24440. cursor = handleElm.runtimeStyle.cursor;
  24441. }
  24442. $eventOverlay = $('<div>').css({
  24443. position: "absolute",
  24444. top: 0, left: 0,
  24445. width: docSize.width,
  24446. height: docSize.height,
  24447. zIndex: 0x7FFFFFFF,
  24448. opacity: 0.0001,
  24449. cursor: cursor
  24450. }).appendTo(doc.body);
  24451. $(doc).on('mousemove touchmove', drag).on('mouseup touchend', stop);
  24452. settings.start(e);
  24453. };
  24454. drag = function(e) {
  24455. updateWithTouchData(e);
  24456. if (e.button !== downButton) {
  24457. return stop(e);
  24458. }
  24459. e.deltaX = e.screenX - startX;
  24460. e.deltaY = e.screenY - startY;
  24461. e.preventDefault();
  24462. settings.drag(e);
  24463. };
  24464. stop = function(e) {
  24465. updateWithTouchData(e);
  24466. $(doc).off('mousemove touchmove', drag).off('mouseup touchend', stop);
  24467. $eventOverlay.remove();
  24468. if (settings.stop) {
  24469. settings.stop(e);
  24470. }
  24471. };
  24472. /**
  24473. * Destroys the drag/drop helper instance.
  24474. *
  24475. * @method destroy
  24476. */
  24477. this.destroy = function() {
  24478. $(getHandleElm()).off();
  24479. };
  24480. $(getHandleElm()).on('mousedown touchstart', start);
  24481. };
  24482. });
  24483. // Included from: js/tinymce/classes/ui/Scrollable.js
  24484. /**
  24485. * Scrollable.js
  24486. *
  24487. * Released under LGPL License.
  24488. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  24489. *
  24490. * License: http://www.tinymce.com/license
  24491. * Contributing: http://www.tinymce.com/contributing
  24492. */
  24493. /**
  24494. * This mixin makes controls scrollable using custom scrollbars.
  24495. *
  24496. * @-x-less Scrollable.less
  24497. * @mixin tinymce.ui.Scrollable
  24498. */
  24499. define("tinymce/ui/Scrollable", [
  24500. "tinymce/dom/DomQuery",
  24501. "tinymce/ui/DragHelper"
  24502. ], function($, DragHelper) {
  24503. "use strict";
  24504. return {
  24505. init: function() {
  24506. var self = this;
  24507. self.on('repaint', self.renderScroll);
  24508. },
  24509. renderScroll: function() {
  24510. var self = this, margin = 2;
  24511. function repaintScroll() {
  24512. var hasScrollH, hasScrollV, bodyElm;
  24513. function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) {
  24514. var containerElm, scrollBarElm, scrollThumbElm;
  24515. var containerSize, scrollSize, ratio, rect;
  24516. var posNameLower, sizeNameLower;
  24517. scrollBarElm = self.getEl('scroll' + axisName);
  24518. if (scrollBarElm) {
  24519. posNameLower = posName.toLowerCase();
  24520. sizeNameLower = sizeName.toLowerCase();
  24521. $(self.getEl('absend')).css(posNameLower, self.layoutRect()[contentSizeName] - 1);
  24522. if (!hasScroll) {
  24523. $(scrollBarElm).css('display', 'none');
  24524. return;
  24525. }
  24526. $(scrollBarElm).css('display', 'block');
  24527. containerElm = self.getEl('body');
  24528. scrollThumbElm = self.getEl('scroll' + axisName + "t");
  24529. containerSize = containerElm["client" + sizeName] - (margin * 2);
  24530. containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0;
  24531. scrollSize = containerElm["scroll" + sizeName];
  24532. ratio = containerSize / scrollSize;
  24533. rect = {};
  24534. rect[posNameLower] = containerElm["offset" + posName] + margin;
  24535. rect[sizeNameLower] = containerSize;
  24536. $(scrollBarElm).css(rect);
  24537. rect = {};
  24538. rect[posNameLower] = containerElm["scroll" + posName] * ratio;
  24539. rect[sizeNameLower] = containerSize * ratio;
  24540. $(scrollThumbElm).css(rect);
  24541. }
  24542. }
  24543. bodyElm = self.getEl('body');
  24544. hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth;
  24545. hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight;
  24546. repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height");
  24547. repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width");
  24548. }
  24549. function addScroll() {
  24550. function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) {
  24551. var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix;
  24552. $(self.getEl()).append(
  24553. '<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' +
  24554. '<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' +
  24555. '</div>'
  24556. );
  24557. self.draghelper = new DragHelper(axisId + 't', {
  24558. start: function() {
  24559. scrollStart = self.getEl('body')["scroll" + posName];
  24560. $('#' + axisId).addClass(prefix + 'active');
  24561. },
  24562. drag: function(e) {
  24563. var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect();
  24564. hasScrollH = layoutRect.contentW > layoutRect.innerW;
  24565. hasScrollV = layoutRect.contentH > layoutRect.innerH;
  24566. containerSize = self.getEl('body')["client" + sizeName] - (margin * 2);
  24567. containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0;
  24568. ratio = containerSize / self.getEl('body')["scroll" + sizeName];
  24569. self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio);
  24570. },
  24571. stop: function() {
  24572. $('#' + axisId).removeClass(prefix + 'active');
  24573. }
  24574. });
  24575. }
  24576. self.classes.add('scroll');
  24577. addScrollAxis("v", "Top", "Height", "Y", "Width");
  24578. addScrollAxis("h", "Left", "Width", "X", "Height");
  24579. }
  24580. if (self.settings.autoScroll) {
  24581. if (!self._hasScroll) {
  24582. self._hasScroll = true;
  24583. addScroll();
  24584. self.on('wheel', function(e) {
  24585. var bodyEl = self.getEl('body');
  24586. bodyEl.scrollLeft += (e.deltaX || 0) * 10;
  24587. bodyEl.scrollTop += e.deltaY * 10;
  24588. repaintScroll();
  24589. });
  24590. $(self.getEl('body')).on("scroll", repaintScroll);
  24591. }
  24592. repaintScroll();
  24593. }
  24594. }
  24595. };
  24596. });
  24597. // Included from: js/tinymce/classes/ui/Panel.js
  24598. /**
  24599. * Panel.js
  24600. *
  24601. * Released under LGPL License.
  24602. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  24603. *
  24604. * License: http://www.tinymce.com/license
  24605. * Contributing: http://www.tinymce.com/contributing
  24606. */
  24607. /**
  24608. * Creates a new panel.
  24609. *
  24610. * @-x-less Panel.less
  24611. * @class tinymce.ui.Panel
  24612. * @extends tinymce.ui.Container
  24613. * @mixes tinymce.ui.Scrollable
  24614. */
  24615. define("tinymce/ui/Panel", [
  24616. "tinymce/ui/Container",
  24617. "tinymce/ui/Scrollable"
  24618. ], function(Container, Scrollable) {
  24619. "use strict";
  24620. return Container.extend({
  24621. Defaults: {
  24622. layout: 'fit',
  24623. containerCls: 'panel'
  24624. },
  24625. Mixins: [Scrollable],
  24626. /**
  24627. * Renders the control as a HTML string.
  24628. *
  24629. * @method renderHtml
  24630. * @return {String} HTML representing the control.
  24631. */
  24632. renderHtml: function() {
  24633. var self = this, layout = self._layout, innerHtml = self.settings.html;
  24634. self.preRender();
  24635. layout.preRender(self);
  24636. if (typeof innerHtml == "undefined") {
  24637. innerHtml = (
  24638. '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' +
  24639. layout.renderHtml(self) +
  24640. '</div>'
  24641. );
  24642. } else {
  24643. if (typeof innerHtml == 'function') {
  24644. innerHtml = innerHtml.call(self);
  24645. }
  24646. self._hasBody = false;
  24647. }
  24648. return (
  24649. '<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1" role="group">' +
  24650. (self._preBodyHtml || '') +
  24651. innerHtml +
  24652. '</div>'
  24653. );
  24654. }
  24655. });
  24656. });
  24657. // Included from: js/tinymce/classes/ui/Movable.js
  24658. /**
  24659. * Movable.js
  24660. *
  24661. * Released under LGPL License.
  24662. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  24663. *
  24664. * License: http://www.tinymce.com/license
  24665. * Contributing: http://www.tinymce.com/contributing
  24666. */
  24667. /**
  24668. * Movable mixin. Makes controls movable absolute and relative to other elements.
  24669. *
  24670. * @mixin tinymce.ui.Movable
  24671. */
  24672. define("tinymce/ui/Movable", [
  24673. "tinymce/ui/DomUtils"
  24674. ], function(DomUtils) {
  24675. "use strict";
  24676. function calculateRelativePosition(ctrl, targetElm, rel) {
  24677. var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size;
  24678. viewport = DomUtils.getViewPort();
  24679. // Get pos of target
  24680. pos = DomUtils.getPos(targetElm);
  24681. x = pos.x;
  24682. y = pos.y;
  24683. if (ctrl.state.get('fixed') && DomUtils.getRuntimeStyle(document.body, 'position') == 'static') {
  24684. x -= viewport.x;
  24685. y -= viewport.y;
  24686. }
  24687. // Get size of self
  24688. ctrlElm = ctrl.getEl();
  24689. size = DomUtils.getSize(ctrlElm);
  24690. selfW = size.width;
  24691. selfH = size.height;
  24692. // Get size of target
  24693. size = DomUtils.getSize(targetElm);
  24694. targetW = size.width;
  24695. targetH = size.height;
  24696. // Parse align string
  24697. rel = (rel || '').split('');
  24698. // Target corners
  24699. if (rel[0] === 'b') {
  24700. y += targetH;
  24701. }
  24702. if (rel[1] === 'r') {
  24703. x += targetW;
  24704. }
  24705. if (rel[0] === 'c') {
  24706. y += Math.round(targetH / 2);
  24707. }
  24708. if (rel[1] === 'c') {
  24709. x += Math.round(targetW / 2);
  24710. }
  24711. // Self corners
  24712. if (rel[3] === 'b') {
  24713. y -= selfH;
  24714. }
  24715. if (rel[4] === 'r') {
  24716. x -= selfW;
  24717. }
  24718. if (rel[3] === 'c') {
  24719. y -= Math.round(selfH / 2);
  24720. }
  24721. if (rel[4] === 'c') {
  24722. x -= Math.round(selfW / 2);
  24723. }
  24724. return {
  24725. x: x,
  24726. y: y,
  24727. w: selfW,
  24728. h: selfH
  24729. };
  24730. }
  24731. return {
  24732. /**
  24733. * Tests various positions to get the most suitable one.
  24734. *
  24735. * @method testMoveRel
  24736. * @param {DOMElement} elm Element to position against.
  24737. * @param {Array} rels Array with relative positions.
  24738. * @return {String} Best suitable relative position.
  24739. */
  24740. testMoveRel: function(elm, rels) {
  24741. var viewPortRect = DomUtils.getViewPort();
  24742. for (var i = 0; i < rels.length; i++) {
  24743. var pos = calculateRelativePosition(this, elm, rels[i]);
  24744. if (this.state.get('fixed')) {
  24745. if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) {
  24746. return rels[i];
  24747. }
  24748. } else {
  24749. if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x &&
  24750. pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) {
  24751. return rels[i];
  24752. }
  24753. }
  24754. }
  24755. return rels[0];
  24756. },
  24757. /**
  24758. * Move relative to the specified element.
  24759. *
  24760. * @method moveRel
  24761. * @param {Element} elm Element to move relative to.
  24762. * @param {String} rel Relative mode. For example: br-tl.
  24763. * @return {tinymce.ui.Control} Current control instance.
  24764. */
  24765. moveRel: function(elm, rel) {
  24766. if (typeof rel != 'string') {
  24767. rel = this.testMoveRel(elm, rel);
  24768. }
  24769. var pos = calculateRelativePosition(this, elm, rel);
  24770. return this.moveTo(pos.x, pos.y);
  24771. },
  24772. /**
  24773. * Move by a relative x, y values.
  24774. *
  24775. * @method moveBy
  24776. * @param {Number} dx Relative x position.
  24777. * @param {Number} dy Relative y position.
  24778. * @return {tinymce.ui.Control} Current control instance.
  24779. */
  24780. moveBy: function(dx, dy) {
  24781. var self = this, rect = self.layoutRect();
  24782. self.moveTo(rect.x + dx, rect.y + dy);
  24783. return self;
  24784. },
  24785. /**
  24786. * Move to absolute position.
  24787. *
  24788. * @method moveTo
  24789. * @param {Number} x Absolute x position.
  24790. * @param {Number} y Absolute y position.
  24791. * @return {tinymce.ui.Control} Current control instance.
  24792. */
  24793. moveTo: function(x, y) {
  24794. var self = this;
  24795. // TODO: Move this to some global class
  24796. function constrain(value, max, size) {
  24797. if (value < 0) {
  24798. return 0;
  24799. }
  24800. if (value + size > max) {
  24801. value = max - size;
  24802. return value < 0 ? 0 : value;
  24803. }
  24804. return value;
  24805. }
  24806. if (self.settings.constrainToViewport) {
  24807. var viewPortRect = DomUtils.getViewPort(window);
  24808. var layoutRect = self.layoutRect();
  24809. x = constrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w);
  24810. y = constrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h);
  24811. }
  24812. if (self.state.get('rendered')) {
  24813. self.layoutRect({x: x, y: y}).repaint();
  24814. } else {
  24815. self.settings.x = x;
  24816. self.settings.y = y;
  24817. }
  24818. self.fire('move', {x: x, y: y});
  24819. return self;
  24820. }
  24821. };
  24822. });
  24823. // Included from: js/tinymce/classes/ui/Resizable.js
  24824. /**
  24825. * Resizable.js
  24826. *
  24827. * Released under LGPL License.
  24828. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  24829. *
  24830. * License: http://www.tinymce.com/license
  24831. * Contributing: http://www.tinymce.com/contributing
  24832. */
  24833. /**
  24834. * Resizable mixin. Enables controls to be resized.
  24835. *
  24836. * @mixin tinymce.ui.Resizable
  24837. */
  24838. define("tinymce/ui/Resizable", [
  24839. "tinymce/ui/DomUtils"
  24840. ], function(DomUtils) {
  24841. "use strict";
  24842. return {
  24843. /**
  24844. * Resizes the control to contents.
  24845. *
  24846. * @method resizeToContent
  24847. */
  24848. resizeToContent: function() {
  24849. this._layoutRect.autoResize = true;
  24850. this._lastRect = null;
  24851. this.reflow();
  24852. },
  24853. /**
  24854. * Resizes the control to a specific width/height.
  24855. *
  24856. * @method resizeTo
  24857. * @param {Number} w Control width.
  24858. * @param {Number} h Control height.
  24859. * @return {tinymce.ui.Control} Current control instance.
  24860. */
  24861. resizeTo: function(w, h) {
  24862. // TODO: Fix hack
  24863. if (w <= 1 || h <= 1) {
  24864. var rect = DomUtils.getWindowSize();
  24865. w = w <= 1 ? w * rect.w : w;
  24866. h = h <= 1 ? h * rect.h : h;
  24867. }
  24868. this._layoutRect.autoResize = false;
  24869. return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow();
  24870. },
  24871. /**
  24872. * Resizes the control to a specific relative width/height.
  24873. *
  24874. * @method resizeBy
  24875. * @param {Number} dw Relative control width.
  24876. * @param {Number} dh Relative control height.
  24877. * @return {tinymce.ui.Control} Current control instance.
  24878. */
  24879. resizeBy: function(dw, dh) {
  24880. var self = this, rect = self.layoutRect();
  24881. return self.resizeTo(rect.w + dw, rect.h + dh);
  24882. }
  24883. };
  24884. });
  24885. // Included from: js/tinymce/classes/ui/FloatPanel.js
  24886. /**
  24887. * FloatPanel.js
  24888. *
  24889. * Released under LGPL License.
  24890. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  24891. *
  24892. * License: http://www.tinymce.com/license
  24893. * Contributing: http://www.tinymce.com/contributing
  24894. */
  24895. /**
  24896. * This class creates a floating panel.
  24897. *
  24898. * @-x-less FloatPanel.less
  24899. * @class tinymce.ui.FloatPanel
  24900. * @extends tinymce.ui.Panel
  24901. * @mixes tinymce.ui.Movable
  24902. * @mixes tinymce.ui.Resizable
  24903. */
  24904. define("tinymce/ui/FloatPanel", [
  24905. "tinymce/ui/Panel",
  24906. "tinymce/ui/Movable",
  24907. "tinymce/ui/Resizable",
  24908. "tinymce/ui/DomUtils",
  24909. "tinymce/dom/DomQuery",
  24910. "tinymce/util/Delay"
  24911. ], function(Panel, Movable, Resizable, DomUtils, $, Delay) {
  24912. "use strict";
  24913. var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = [];
  24914. var zOrder = [], hasModal;
  24915. function isChildOf(ctrl, parent) {
  24916. while (ctrl) {
  24917. if (ctrl == parent) {
  24918. return true;
  24919. }
  24920. ctrl = ctrl.parent();
  24921. }
  24922. }
  24923. function skipOrHidePanels(e) {
  24924. // Hide any float panel when a click/focus out is out side that float panel and the
  24925. // float panels direct parent for example a click on a menu button
  24926. var i = visiblePanels.length;
  24927. while (i--) {
  24928. var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
  24929. if (panel.settings.autohide) {
  24930. if (clickCtrl) {
  24931. if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
  24932. continue;
  24933. }
  24934. }
  24935. e = panel.fire('autohide', {target: e.target});
  24936. if (!e.isDefaultPrevented()) {
  24937. panel.hide();
  24938. }
  24939. }
  24940. }
  24941. }
  24942. function bindDocumentClickHandler() {
  24943. if (!documentClickHandler) {
  24944. documentClickHandler = function(e) {
  24945. // Gecko fires click event and in the wrong order on Mac so lets normalize
  24946. if (e.button == 2) {
  24947. return;
  24948. }
  24949. skipOrHidePanels(e);
  24950. };
  24951. $(document).on('click touchstart', documentClickHandler);
  24952. }
  24953. }
  24954. function bindDocumentScrollHandler() {
  24955. if (!documentScrollHandler) {
  24956. documentScrollHandler = function() {
  24957. var i;
  24958. i = visiblePanels.length;
  24959. while (i--) {
  24960. repositionPanel(visiblePanels[i]);
  24961. }
  24962. };
  24963. $(window).on('scroll', documentScrollHandler);
  24964. }
  24965. }
  24966. function bindWindowResizeHandler() {
  24967. if (!windowResizeHandler) {
  24968. var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight;
  24969. windowResizeHandler = function() {
  24970. // Workaround for #7065 IE 7 fires resize events event though the window wasn't resized
  24971. if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) {
  24972. clientWidth = docElm.clientWidth;
  24973. clientHeight = docElm.clientHeight;
  24974. FloatPanel.hideAll();
  24975. }
  24976. };
  24977. $(window).on('resize', windowResizeHandler);
  24978. }
  24979. }
  24980. /**
  24981. * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
  24982. * also reposition all child panels of the current panel.
  24983. */
  24984. function repositionPanel(panel) {
  24985. var scrollY = DomUtils.getViewPort().y;
  24986. function toggleFixedChildPanels(fixed, deltaY) {
  24987. var parent;
  24988. for (var i = 0; i < visiblePanels.length; i++) {
  24989. if (visiblePanels[i] != panel) {
  24990. parent = visiblePanels[i].parent();
  24991. while (parent && (parent = parent.parent())) {
  24992. if (parent == panel) {
  24993. visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
  24994. }
  24995. }
  24996. }
  24997. }
  24998. }
  24999. if (panel.settings.autofix) {
  25000. if (!panel.state.get('fixed')) {
  25001. panel._autoFixY = panel.layoutRect().y;
  25002. if (panel._autoFixY < scrollY) {
  25003. panel.fixed(true).layoutRect({y: 0}).repaint();
  25004. toggleFixedChildPanels(true, scrollY - panel._autoFixY);
  25005. }
  25006. } else {
  25007. if (panel._autoFixY > scrollY) {
  25008. panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
  25009. toggleFixedChildPanels(false, panel._autoFixY - scrollY);
  25010. }
  25011. }
  25012. }
  25013. }
  25014. function addRemove(add, ctrl) {
  25015. var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;
  25016. if (add) {
  25017. zOrder.push(ctrl);
  25018. } else {
  25019. i = zOrder.length;
  25020. while (i--) {
  25021. if (zOrder[i] === ctrl) {
  25022. zOrder.splice(i, 1);
  25023. }
  25024. }
  25025. }
  25026. if (zOrder.length) {
  25027. for (i = 0; i < zOrder.length; i++) {
  25028. if (zOrder[i].modal) {
  25029. zIndex++;
  25030. topModal = zOrder[i];
  25031. }
  25032. zOrder[i].getEl().style.zIndex = zIndex;
  25033. zOrder[i].zIndex = zIndex;
  25034. zIndex++;
  25035. }
  25036. }
  25037. var modalBlockEl = $('#' + ctrl.classPrefix + 'modal-block', ctrl.getContainerElm())[0];
  25038. if (topModal) {
  25039. $(modalBlockEl).css('z-index', topModal.zIndex - 1);
  25040. } else if (modalBlockEl) {
  25041. modalBlockEl.parentNode.removeChild(modalBlockEl);
  25042. hasModal = false;
  25043. }
  25044. FloatPanel.currentZIndex = zIndex;
  25045. }
  25046. var FloatPanel = Panel.extend({
  25047. Mixins: [Movable, Resizable],
  25048. /**
  25049. * Constructs a new control instance with the specified settings.
  25050. *
  25051. * @constructor
  25052. * @param {Object} settings Name/value object with settings.
  25053. * @setting {Boolean} autohide Automatically hide the panel.
  25054. */
  25055. init: function(settings) {
  25056. var self = this;
  25057. self._super(settings);
  25058. self._eventsRoot = self;
  25059. self.classes.add('floatpanel');
  25060. // Hide floatpanes on click out side the root button
  25061. if (settings.autohide) {
  25062. bindDocumentClickHandler();
  25063. bindWindowResizeHandler();
  25064. visiblePanels.push(self);
  25065. }
  25066. if (settings.autofix) {
  25067. bindDocumentScrollHandler();
  25068. self.on('move', function() {
  25069. repositionPanel(this);
  25070. });
  25071. }
  25072. self.on('postrender show', function(e) {
  25073. if (e.control == self) {
  25074. var $modalBlockEl, prefix = self.classPrefix;
  25075. if (self.modal && !hasModal) {
  25076. $modalBlockEl = $('#' + prefix + 'modal-block', self.getContainerElm());
  25077. if (!$modalBlockEl[0]) {
  25078. $modalBlockEl = $(
  25079. '<div id="' + prefix + 'modal-block" class="' + prefix + 'reset ' + prefix + 'fade"></div>'
  25080. ).appendTo(self.getContainerElm());
  25081. }
  25082. Delay.setTimeout(function() {
  25083. $modalBlockEl.addClass(prefix + 'in');
  25084. $(self.getEl()).addClass(prefix + 'in');
  25085. });
  25086. hasModal = true;
  25087. }
  25088. addRemove(true, self);
  25089. }
  25090. });
  25091. self.on('show', function() {
  25092. self.parents().each(function(ctrl) {
  25093. if (ctrl.state.get('fixed')) {
  25094. self.fixed(true);
  25095. return false;
  25096. }
  25097. });
  25098. });
  25099. if (settings.popover) {
  25100. self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>';
  25101. self.classes.add('popover').add('bottom').add(self.isRtl() ? 'end' : 'start');
  25102. }
  25103. self.aria('label', settings.ariaLabel);
  25104. self.aria('labelledby', self._id);
  25105. self.aria('describedby', self.describedBy || self._id + '-none');
  25106. },
  25107. fixed: function(state) {
  25108. var self = this;
  25109. if (self.state.get('fixed') != state) {
  25110. if (self.state.get('rendered')) {
  25111. var viewport = DomUtils.getViewPort();
  25112. if (state) {
  25113. self.layoutRect().y -= viewport.y;
  25114. } else {
  25115. self.layoutRect().y += viewport.y;
  25116. }
  25117. }
  25118. self.classes.toggle('fixed', state);
  25119. self.state.set('fixed', state);
  25120. }
  25121. return self;
  25122. },
  25123. /**
  25124. * Shows the current float panel.
  25125. *
  25126. * @method show
  25127. * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
  25128. */
  25129. show: function() {
  25130. var self = this, i, state = self._super();
  25131. i = visiblePanels.length;
  25132. while (i--) {
  25133. if (visiblePanels[i] === self) {
  25134. break;
  25135. }
  25136. }
  25137. if (i === -1) {
  25138. visiblePanels.push(self);
  25139. }
  25140. return state;
  25141. },
  25142. /**
  25143. * Hides the current float panel.
  25144. *
  25145. * @method hide
  25146. * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
  25147. */
  25148. hide: function() {
  25149. removeVisiblePanel(this);
  25150. addRemove(false, this);
  25151. return this._super();
  25152. },
  25153. /**
  25154. * Hide all visible float panels with he autohide setting enabled. This is for
  25155. * manually hiding floating menus or panels.
  25156. *
  25157. * @method hideAll
  25158. */
  25159. hideAll: function() {
  25160. FloatPanel.hideAll();
  25161. },
  25162. /**
  25163. * Closes the float panel. This will remove the float panel from page and fire the close event.
  25164. *
  25165. * @method close
  25166. */
  25167. close: function() {
  25168. var self = this;
  25169. if (!self.fire('close').isDefaultPrevented()) {
  25170. self.remove();
  25171. addRemove(false, self);
  25172. }
  25173. return self;
  25174. },
  25175. /**
  25176. * Removes the float panel from page.
  25177. *
  25178. * @method remove
  25179. */
  25180. remove: function() {
  25181. removeVisiblePanel(this);
  25182. this._super();
  25183. },
  25184. postRender: function() {
  25185. var self = this;
  25186. if (self.settings.bodyRole) {
  25187. this.getEl('body').setAttribute('role', self.settings.bodyRole);
  25188. }
  25189. return self._super();
  25190. }
  25191. });
  25192. /**
  25193. * Hide all visible float panels with he autohide setting enabled. This is for
  25194. * manually hiding floating menus or panels.
  25195. *
  25196. * @static
  25197. * @method hideAll
  25198. */
  25199. FloatPanel.hideAll = function() {
  25200. var i = visiblePanels.length;
  25201. while (i--) {
  25202. var panel = visiblePanels[i];
  25203. if (panel && panel.settings.autohide) {
  25204. panel.hide();
  25205. visiblePanels.splice(i, 1);
  25206. }
  25207. }
  25208. };
  25209. function removeVisiblePanel(panel) {
  25210. var i;
  25211. i = visiblePanels.length;
  25212. while (i--) {
  25213. if (visiblePanels[i] === panel) {
  25214. visiblePanels.splice(i, 1);
  25215. }
  25216. }
  25217. i = zOrder.length;
  25218. while (i--) {
  25219. if (zOrder[i] === panel) {
  25220. zOrder.splice(i, 1);
  25221. }
  25222. }
  25223. }
  25224. return FloatPanel;
  25225. });
  25226. // Included from: js/tinymce/classes/ui/Window.js
  25227. /**
  25228. * Window.js
  25229. *
  25230. * Released under LGPL License.
  25231. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  25232. *
  25233. * License: http://www.tinymce.com/license
  25234. * Contributing: http://www.tinymce.com/contributing
  25235. */
  25236. /**
  25237. * Creates a new window.
  25238. *
  25239. * @-x-less Window.less
  25240. * @class tinymce.ui.Window
  25241. * @extends tinymce.ui.FloatPanel
  25242. */
  25243. define("tinymce/ui/Window", [
  25244. "tinymce/ui/FloatPanel",
  25245. "tinymce/ui/Panel",
  25246. "tinymce/ui/DomUtils",
  25247. "tinymce/dom/DomQuery",
  25248. "tinymce/ui/DragHelper",
  25249. "tinymce/ui/BoxUtils",
  25250. "tinymce/Env",
  25251. "tinymce/util/Delay"
  25252. ], function(FloatPanel, Panel, DomUtils, $, DragHelper, BoxUtils, Env, Delay) {
  25253. "use strict";
  25254. var windows = [], oldMetaValue = '';
  25255. function toggleFullScreenState(state) {
  25256. var noScaleMetaValue = 'width=device-width,initial-scale=1.0,user-scalable=0,minimum-scale=1.0,maximum-scale=1.0',
  25257. viewport = $("meta[name=viewport]")[0],
  25258. contentValue;
  25259. if (Env.overrideViewPort === false) {
  25260. return;
  25261. }
  25262. if (!viewport) {
  25263. viewport = document.createElement('meta');
  25264. viewport.setAttribute('name', 'viewport');
  25265. document.getElementsByTagName('head')[0].appendChild(viewport);
  25266. }
  25267. contentValue = viewport.getAttribute('content');
  25268. if (contentValue && typeof oldMetaValue != 'undefined') {
  25269. oldMetaValue = contentValue;
  25270. }
  25271. viewport.setAttribute('content', state ? noScaleMetaValue : oldMetaValue);
  25272. }
  25273. function toggleBodyFullScreenClasses(classPrefix) {
  25274. for (var i = 0; i < windows.length; i++) {
  25275. if (windows[i]._fullscreen) {
  25276. return;
  25277. }
  25278. }
  25279. $([document.documentElement, document.body]).removeClass(classPrefix + 'fullscreen');
  25280. }
  25281. function handleWindowResize() {
  25282. if (!Env.desktop) {
  25283. var lastSize = {
  25284. w: window.innerWidth,
  25285. h: window.innerHeight
  25286. };
  25287. Delay.setInterval(function() {
  25288. var w = window.innerWidth,
  25289. h = window.innerHeight;
  25290. if (lastSize.w != w || lastSize.h != h) {
  25291. lastSize = {
  25292. w: w,
  25293. h: h
  25294. };
  25295. $(window).trigger('resize');
  25296. }
  25297. }, 100);
  25298. }
  25299. function reposition() {
  25300. var i, rect = DomUtils.getWindowSize(), layoutRect;
  25301. for (i = 0; i < windows.length; i++) {
  25302. layoutRect = windows[i].layoutRect();
  25303. windows[i].moveTo(
  25304. windows[i].settings.x || Math.max(0, rect.w / 2 - layoutRect.w / 2),
  25305. windows[i].settings.y || Math.max(0, rect.h / 2 - layoutRect.h / 2)
  25306. );
  25307. }
  25308. }
  25309. $(window).on('resize', reposition);
  25310. }
  25311. var Window = FloatPanel.extend({
  25312. modal: true,
  25313. Defaults: {
  25314. border: 1,
  25315. layout: 'flex',
  25316. containerCls: 'panel',
  25317. role: 'dialog',
  25318. callbacks: {
  25319. submit: function() {
  25320. this.fire('submit', {data: this.toJSON()});
  25321. },
  25322. close: function() {
  25323. this.close();
  25324. }
  25325. }
  25326. },
  25327. /**
  25328. * Constructs a instance with the specified settings.
  25329. *
  25330. * @constructor
  25331. * @param {Object} settings Name/value object with settings.
  25332. */
  25333. init: function(settings) {
  25334. var self = this;
  25335. self._super(settings);
  25336. if (self.isRtl()) {
  25337. self.classes.add('rtl');
  25338. }
  25339. self.classes.add('window');
  25340. self.bodyClasses.add('window-body');
  25341. self.state.set('fixed', true);
  25342. // Create statusbar
  25343. if (settings.buttons) {
  25344. self.statusbar = new Panel({
  25345. layout: 'flex',
  25346. border: '1 0 0 0',
  25347. spacing: 3,
  25348. padding: 10,
  25349. align: 'center',
  25350. pack: self.isRtl() ? 'start' : 'end',
  25351. defaults: {
  25352. type: 'button'
  25353. },
  25354. items: settings.buttons
  25355. });
  25356. self.statusbar.classes.add('foot');
  25357. self.statusbar.parent(self);
  25358. }
  25359. self.on('click', function(e) {
  25360. var closeClass = self.classPrefix + 'close';
  25361. if (DomUtils.hasClass(e.target, closeClass) || DomUtils.hasClass(e.target.parentNode, closeClass)) {
  25362. self.close();
  25363. }
  25364. });
  25365. self.on('cancel', function() {
  25366. self.close();
  25367. });
  25368. self.aria('describedby', self.describedBy || self._id + '-none');
  25369. self.aria('label', settings.title);
  25370. self._fullscreen = false;
  25371. },
  25372. /**
  25373. * Recalculates the positions of the controls in the current container.
  25374. * This is invoked by the reflow method and shouldn't be called directly.
  25375. *
  25376. * @method recalc
  25377. */
  25378. recalc: function() {
  25379. var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc;
  25380. if (self._fullscreen) {
  25381. self.layoutRect(DomUtils.getWindowSize());
  25382. self.layoutRect().contentH = self.layoutRect().innerH;
  25383. }
  25384. self._super();
  25385. layoutRect = self.layoutRect();
  25386. // Resize window based on title width
  25387. if (self.settings.title && !self._fullscreen) {
  25388. width = layoutRect.headerW;
  25389. if (width > layoutRect.w) {
  25390. x = layoutRect.x - Math.max(0, width / 2);
  25391. self.layoutRect({w: width, x: x});
  25392. needsRecalc = true;
  25393. }
  25394. }
  25395. // Resize window based on statusbar width
  25396. if (statusbar) {
  25397. statusbar.layoutRect({w: self.layoutRect().innerW}).recalc();
  25398. width = statusbar.layoutRect().minW + layoutRect.deltaW;
  25399. if (width > layoutRect.w) {
  25400. x = layoutRect.x - Math.max(0, width - layoutRect.w);
  25401. self.layoutRect({w: width, x: x});
  25402. needsRecalc = true;
  25403. }
  25404. }
  25405. // Recalc body and disable auto resize
  25406. if (needsRecalc) {
  25407. self.recalc();
  25408. }
  25409. },
  25410. /**
  25411. * Initializes the current controls layout rect.
  25412. * This will be executed by the layout managers to determine the
  25413. * default minWidth/minHeight etc.
  25414. *
  25415. * @method initLayoutRect
  25416. * @return {Object} Layout rect instance.
  25417. */
  25418. initLayoutRect: function() {
  25419. var self = this, layoutRect = self._super(), deltaH = 0, headEl;
  25420. // Reserve vertical space for title
  25421. if (self.settings.title && !self._fullscreen) {
  25422. headEl = self.getEl('head');
  25423. var size = DomUtils.getSize(headEl);
  25424. layoutRect.headerW = size.width;
  25425. layoutRect.headerH = size.height;
  25426. deltaH += layoutRect.headerH;
  25427. }
  25428. // Reserve vertical space for statusbar
  25429. if (self.statusbar) {
  25430. deltaH += self.statusbar.layoutRect().h;
  25431. }
  25432. layoutRect.deltaH += deltaH;
  25433. layoutRect.minH += deltaH;
  25434. //layoutRect.innerH -= deltaH;
  25435. layoutRect.h += deltaH;
  25436. var rect = DomUtils.getWindowSize();
  25437. layoutRect.x = self.settings.x || Math.max(0, rect.w / 2 - layoutRect.w / 2);
  25438. layoutRect.y = self.settings.y || Math.max(0, rect.h / 2 - layoutRect.h / 2);
  25439. return layoutRect;
  25440. },
  25441. /**
  25442. * Renders the control as a HTML string.
  25443. *
  25444. * @method renderHtml
  25445. * @return {String} HTML representing the control.
  25446. */
  25447. renderHtml: function() {
  25448. var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix;
  25449. var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html;
  25450. self.preRender();
  25451. layout.preRender(self);
  25452. if (settings.title) {
  25453. headerHtml = (
  25454. '<div id="' + id + '-head" class="' + prefix + 'window-head">' +
  25455. '<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
  25456. '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
  25457. '<button type="button" class="' + prefix + 'close" aria-hidden="true">' +
  25458. '<i class="mce-ico mce-i-remove"></i>' +
  25459. '</button>' +
  25460. '</div>'
  25461. );
  25462. }
  25463. if (settings.url) {
  25464. html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>';
  25465. }
  25466. if (typeof html == "undefined") {
  25467. html = layout.renderHtml(self);
  25468. }
  25469. if (self.statusbar) {
  25470. footerHtml = self.statusbar.renderHtml();
  25471. }
  25472. return (
  25473. '<div id="' + id + '" class="' + self.classes + '" hidefocus="1">' +
  25474. '<div class="' + self.classPrefix + 'reset" role="application">' +
  25475. headerHtml +
  25476. '<div id="' + id + '-body" class="' + self.bodyClasses + '">' +
  25477. html +
  25478. '</div>' +
  25479. footerHtml +
  25480. '</div>' +
  25481. '</div>'
  25482. );
  25483. },
  25484. /**
  25485. * Switches the window fullscreen mode.
  25486. *
  25487. * @method fullscreen
  25488. * @param {Boolean} state True/false state.
  25489. * @return {tinymce.ui.Window} Current window instance.
  25490. */
  25491. fullscreen: function(state) {
  25492. var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect;
  25493. if (state != self._fullscreen) {
  25494. $(window).on('resize', function() {
  25495. var time;
  25496. if (self._fullscreen) {
  25497. // Time the layout time if it's to slow use a timeout to not hog the CPU
  25498. if (!slowRendering) {
  25499. time = new Date().getTime();
  25500. var rect = DomUtils.getWindowSize();
  25501. self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  25502. if ((new Date().getTime()) - time > 50) {
  25503. slowRendering = true;
  25504. }
  25505. } else {
  25506. if (!self._timer) {
  25507. self._timer = Delay.setTimeout(function() {
  25508. var rect = DomUtils.getWindowSize();
  25509. self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  25510. self._timer = 0;
  25511. }, 50);
  25512. }
  25513. }
  25514. }
  25515. });
  25516. layoutRect = self.layoutRect();
  25517. self._fullscreen = state;
  25518. if (!state) {
  25519. self.borderBox = BoxUtils.parseBox(self.settings.border);
  25520. self.getEl('head').style.display = '';
  25521. layoutRect.deltaH += layoutRect.headerH;
  25522. $([documentElement, document.body]).removeClass(prefix + 'fullscreen');
  25523. self.classes.remove('fullscreen');
  25524. self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h);
  25525. } else {
  25526. self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h};
  25527. self.borderBox = BoxUtils.parseBox('0');
  25528. self.getEl('head').style.display = 'none';
  25529. layoutRect.deltaH -= layoutRect.headerH + 2;
  25530. $([documentElement, document.body]).addClass(prefix + 'fullscreen');
  25531. self.classes.add('fullscreen');
  25532. var rect = DomUtils.getWindowSize();
  25533. self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  25534. }
  25535. }
  25536. return self.reflow();
  25537. },
  25538. /**
  25539. * Called after the control has been rendered.
  25540. *
  25541. * @method postRender
  25542. */
  25543. postRender: function() {
  25544. var self = this, startPos;
  25545. setTimeout(function() {
  25546. self.classes.add('in');
  25547. self.fire('open');
  25548. }, 0);
  25549. self._super();
  25550. if (self.statusbar) {
  25551. self.statusbar.postRender();
  25552. }
  25553. self.focus();
  25554. this.dragHelper = new DragHelper(self._id + '-dragh', {
  25555. start: function() {
  25556. startPos = {
  25557. x: self.layoutRect().x,
  25558. y: self.layoutRect().y
  25559. };
  25560. },
  25561. drag: function(e) {
  25562. self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY);
  25563. }
  25564. });
  25565. self.on('submit', function(e) {
  25566. if (!e.isDefaultPrevented()) {
  25567. self.close();
  25568. }
  25569. });
  25570. windows.push(self);
  25571. toggleFullScreenState(true);
  25572. },
  25573. /**
  25574. * Fires a submit event with the serialized form.
  25575. *
  25576. * @method submit
  25577. * @return {Object} Event arguments object.
  25578. */
  25579. submit: function() {
  25580. return this.fire('submit', {data: this.toJSON()});
  25581. },
  25582. /**
  25583. * Removes the current control from DOM and from UI collections.
  25584. *
  25585. * @method remove
  25586. * @return {tinymce.ui.Control} Current control instance.
  25587. */
  25588. remove: function() {
  25589. var self = this, i;
  25590. self.dragHelper.destroy();
  25591. self._super();
  25592. if (self.statusbar) {
  25593. this.statusbar.remove();
  25594. }
  25595. i = windows.length;
  25596. while (i--) {
  25597. if (windows[i] === self) {
  25598. windows.splice(i, 1);
  25599. }
  25600. }
  25601. toggleFullScreenState(windows.length > 0);
  25602. toggleBodyFullScreenClasses(self.classPrefix);
  25603. },
  25604. /**
  25605. * Returns the contentWindow object of the iframe if it exists.
  25606. *
  25607. * @method getContentWindow
  25608. * @return {Window} window object or null.
  25609. */
  25610. getContentWindow: function() {
  25611. var ifr = this.getEl().getElementsByTagName('iframe')[0];
  25612. return ifr ? ifr.contentWindow : null;
  25613. }
  25614. });
  25615. handleWindowResize();
  25616. return Window;
  25617. });
  25618. // Included from: js/tinymce/classes/ui/MessageBox.js
  25619. /**
  25620. * MessageBox.js
  25621. *
  25622. * Released under LGPL License.
  25623. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  25624. *
  25625. * License: http://www.tinymce.com/license
  25626. * Contributing: http://www.tinymce.com/contributing
  25627. */
  25628. /**
  25629. * This class is used to create MessageBoxes like alerts/confirms etc.
  25630. *
  25631. * @class tinymce.ui.MessageBox
  25632. * @extends tinymce.ui.FloatPanel
  25633. */
  25634. define("tinymce/ui/MessageBox", [
  25635. "tinymce/ui/Window"
  25636. ], function(Window) {
  25637. "use strict";
  25638. var MessageBox = Window.extend({
  25639. /**
  25640. * Constructs a instance with the specified settings.
  25641. *
  25642. * @constructor
  25643. * @param {Object} settings Name/value object with settings.
  25644. */
  25645. init: function(settings) {
  25646. settings = {
  25647. border: 1,
  25648. padding: 20,
  25649. layout: 'flex',
  25650. pack: "center",
  25651. align: "center",
  25652. containerCls: 'panel',
  25653. autoScroll: true,
  25654. buttons: {type: "button", text: "Ok", action: "ok"},
  25655. items: {
  25656. type: "label",
  25657. multiline: true,
  25658. maxWidth: 500,
  25659. maxHeight: 200
  25660. }
  25661. };
  25662. this._super(settings);
  25663. },
  25664. Statics: {
  25665. /**
  25666. * Ok buttons constant.
  25667. *
  25668. * @static
  25669. * @final
  25670. * @field {Number} OK
  25671. */
  25672. OK: 1,
  25673. /**
  25674. * Ok/cancel buttons constant.
  25675. *
  25676. * @static
  25677. * @final
  25678. * @field {Number} OK_CANCEL
  25679. */
  25680. OK_CANCEL: 2,
  25681. /**
  25682. * yes/no buttons constant.
  25683. *
  25684. * @static
  25685. * @final
  25686. * @field {Number} YES_NO
  25687. */
  25688. YES_NO: 3,
  25689. /**
  25690. * yes/no/cancel buttons constant.
  25691. *
  25692. * @static
  25693. * @final
  25694. * @field {Number} YES_NO_CANCEL
  25695. */
  25696. YES_NO_CANCEL: 4,
  25697. /**
  25698. * Constructs a new message box and renders it to the body element.
  25699. *
  25700. * @static
  25701. * @method msgBox
  25702. * @param {Object} settings Name/value object with settings.
  25703. */
  25704. msgBox: function(settings) {
  25705. var buttons, callback = settings.callback || function() {};
  25706. function createButton(text, status, primary) {
  25707. return {
  25708. type: "button",
  25709. text: text,
  25710. subtype: primary ? 'primary' : '',
  25711. onClick: function(e) {
  25712. e.control.parents()[1].close();
  25713. callback(status);
  25714. }
  25715. };
  25716. }
  25717. switch (settings.buttons) {
  25718. case MessageBox.OK_CANCEL:
  25719. buttons = [
  25720. createButton('Ok', true, true),
  25721. createButton('Cancel', false)
  25722. ];
  25723. break;
  25724. case MessageBox.YES_NO:
  25725. case MessageBox.YES_NO_CANCEL:
  25726. buttons = [
  25727. createButton('Yes', 1, true),
  25728. createButton('No', 0)
  25729. ];
  25730. if (settings.buttons == MessageBox.YES_NO_CANCEL) {
  25731. buttons.push(createButton('Cancel', -1));
  25732. }
  25733. break;
  25734. default:
  25735. buttons = [
  25736. createButton('Ok', true, true)
  25737. ];
  25738. break;
  25739. }
  25740. return new Window({
  25741. padding: 20,
  25742. x: settings.x,
  25743. y: settings.y,
  25744. minWidth: 300,
  25745. minHeight: 100,
  25746. layout: "flex",
  25747. pack: "center",
  25748. align: "center",
  25749. buttons: buttons,
  25750. title: settings.title,
  25751. role: 'alertdialog',
  25752. items: {
  25753. type: "label",
  25754. multiline: true,
  25755. maxWidth: 500,
  25756. maxHeight: 200,
  25757. text: settings.text
  25758. },
  25759. onPostRender: function() {
  25760. this.aria('describedby', this.items()[0]._id);
  25761. },
  25762. onClose: settings.onClose,
  25763. onCancel: function() {
  25764. callback(false);
  25765. }
  25766. }).renderTo(document.body).reflow();
  25767. },
  25768. /**
  25769. * Creates a new alert dialog.
  25770. *
  25771. * @method alert
  25772. * @param {Object} settings Settings for the alert dialog.
  25773. * @param {function} [callback] Callback to execute when the user makes a choice.
  25774. */
  25775. alert: function(settings, callback) {
  25776. if (typeof settings == "string") {
  25777. settings = {text: settings};
  25778. }
  25779. settings.callback = callback;
  25780. return MessageBox.msgBox(settings);
  25781. },
  25782. /**
  25783. * Creates a new confirm dialog.
  25784. *
  25785. * @method confirm
  25786. * @param {Object} settings Settings for the confirm dialog.
  25787. * @param {function} [callback] Callback to execute when the user makes a choice.
  25788. */
  25789. confirm: function(settings, callback) {
  25790. if (typeof settings == "string") {
  25791. settings = {text: settings};
  25792. }
  25793. settings.callback = callback;
  25794. settings.buttons = MessageBox.OK_CANCEL;
  25795. return MessageBox.msgBox(settings);
  25796. }
  25797. }
  25798. });
  25799. return MessageBox;
  25800. });
  25801. // Included from: js/tinymce/classes/WindowManager.js
  25802. /**
  25803. * WindowManager.js
  25804. *
  25805. * Released under LGPL License.
  25806. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  25807. *
  25808. * License: http://www.tinymce.com/license
  25809. * Contributing: http://www.tinymce.com/contributing
  25810. */
  25811. /**
  25812. * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
  25813. *
  25814. * @class tinymce.WindowManager
  25815. * @example
  25816. * // Opens a new dialog with the file.htm file and the size 320x240
  25817. * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
  25818. * tinymce.activeEditor.windowManager.open({
  25819. * url: 'file.htm',
  25820. * width: 320,
  25821. * height: 240
  25822. * }, {
  25823. * custom_param: 1
  25824. * });
  25825. *
  25826. * // Displays an alert box using the active editors window manager instance
  25827. * tinymce.activeEditor.windowManager.alert('Hello world!');
  25828. *
  25829. * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
  25830. * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
  25831. * if (s)
  25832. * tinymce.activeEditor.windowManager.alert("Ok");
  25833. * else
  25834. * tinymce.activeEditor.windowManager.alert("Cancel");
  25835. * });
  25836. */
  25837. define("tinymce/WindowManager", [
  25838. "tinymce/ui/Window",
  25839. "tinymce/ui/MessageBox"
  25840. ], function(Window, MessageBox) {
  25841. return function(editor) {
  25842. var self = this, windows = [];
  25843. function getTopMostWindow() {
  25844. if (windows.length) {
  25845. return windows[windows.length - 1];
  25846. }
  25847. }
  25848. function fireOpenEvent(win) {
  25849. editor.fire('OpenWindow', {
  25850. win: win
  25851. });
  25852. }
  25853. function fireCloseEvent(win) {
  25854. editor.fire('CloseWindow', {
  25855. win: win
  25856. });
  25857. }
  25858. self.windows = windows;
  25859. editor.on('remove', function() {
  25860. var i = windows.length;
  25861. while (i--) {
  25862. windows[i].close();
  25863. }
  25864. });
  25865. /**
  25866. * Opens a new window.
  25867. *
  25868. * @method open
  25869. * @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
  25870. * @param {Object} params Options like title, file, width, height etc.
  25871. * @option {String} title Window title.
  25872. * @option {String} file URL of the file to open in the window.
  25873. * @option {Number} width Width in pixels.
  25874. * @option {Number} height Height in pixels.
  25875. * @option {Boolean} autoScroll Specifies whether the popup window can have scrollbars if required (i.e. content
  25876. * larger than the popup size specified).
  25877. */
  25878. self.open = function(args, params) {
  25879. var win;
  25880. editor.editorManager.setActive(editor);
  25881. args.title = args.title || ' ';
  25882. // Handle URL
  25883. args.url = args.url || args.file; // Legacy
  25884. if (args.url) {
  25885. args.width = parseInt(args.width || 320, 10);
  25886. args.height = parseInt(args.height || 240, 10);
  25887. }
  25888. // Handle body
  25889. if (args.body) {
  25890. args.items = {
  25891. defaults: args.defaults,
  25892. type: args.bodyType || 'form',
  25893. items: args.body,
  25894. data: args.data,
  25895. callbacks: args.commands
  25896. };
  25897. }
  25898. if (!args.url && !args.buttons) {
  25899. args.buttons = [
  25900. {text: 'Ok', subtype: 'primary', onclick: function() {
  25901. win.find('form')[0].submit();
  25902. }},
  25903. {text: 'Cancel', onclick: function() {
  25904. win.close();
  25905. }}
  25906. ];
  25907. }
  25908. win = new Window(args);
  25909. windows.push(win);
  25910. win.on('close', function() {
  25911. var i = windows.length;
  25912. while (i--) {
  25913. if (windows[i] === win) {
  25914. windows.splice(i, 1);
  25915. }
  25916. }
  25917. if (!windows.length) {
  25918. editor.focus();
  25919. }
  25920. fireCloseEvent(win);
  25921. });
  25922. // Handle data
  25923. if (args.data) {
  25924. win.on('postRender', function() {
  25925. this.find('*').each(function(ctrl) {
  25926. var name = ctrl.name();
  25927. if (name in args.data) {
  25928. ctrl.value(args.data[name]);
  25929. }
  25930. });
  25931. });
  25932. }
  25933. // store args and parameters
  25934. win.features = args || {};
  25935. win.params = params || {};
  25936. // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
  25937. if (windows.length === 1) {
  25938. editor.nodeChanged();
  25939. }
  25940. win = win.renderTo().reflow();
  25941. fireOpenEvent(win);
  25942. return win;
  25943. };
  25944. /**
  25945. * Creates a alert dialog. Please don't use the blocking behavior of this
  25946. * native version use the callback method instead then it can be extended.
  25947. *
  25948. * @method alert
  25949. * @param {String} message Text to display in the new alert dialog.
  25950. * @param {function} callback Callback function to be executed after the user has selected ok.
  25951. * @param {Object} scope Optional scope to execute the callback in.
  25952. * @example
  25953. * // Displays an alert box using the active editors window manager instance
  25954. * tinymce.activeEditor.windowManager.alert('Hello world!');
  25955. */
  25956. self.alert = function(message, callback, scope) {
  25957. var win;
  25958. win = MessageBox.alert(message, function() {
  25959. if (callback) {
  25960. callback.call(scope || this);
  25961. } else {
  25962. editor.focus();
  25963. }
  25964. });
  25965. win.on('close', function() {
  25966. fireCloseEvent(win);
  25967. });
  25968. fireOpenEvent(win);
  25969. };
  25970. /**
  25971. * Creates a confirm dialog. Please don't use the blocking behavior of this
  25972. * native version use the callback method instead then it can be extended.
  25973. *
  25974. * @method confirm
  25975. * @param {String} message Text to display in the new confirm dialog.
  25976. * @param {function} callback Callback function to be executed after the user has selected ok or cancel.
  25977. * @param {Object} scope Optional scope to execute the callback in.
  25978. * @example
  25979. * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
  25980. * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
  25981. * if (s)
  25982. * tinymce.activeEditor.windowManager.alert("Ok");
  25983. * else
  25984. * tinymce.activeEditor.windowManager.alert("Cancel");
  25985. * });
  25986. */
  25987. self.confirm = function(message, callback, scope) {
  25988. var win;
  25989. win = MessageBox.confirm(message, function(state) {
  25990. callback.call(scope || this, state);
  25991. });
  25992. win.on('close', function() {
  25993. fireCloseEvent(win);
  25994. });
  25995. fireOpenEvent(win);
  25996. };
  25997. /**
  25998. * Closes the top most window.
  25999. *
  26000. * @method close
  26001. */
  26002. self.close = function() {
  26003. if (getTopMostWindow()) {
  26004. getTopMostWindow().close();
  26005. }
  26006. };
  26007. /**
  26008. * Returns the params of the last window open call. This can be used in iframe based
  26009. * dialog to get params passed from the tinymce plugin.
  26010. *
  26011. * @example
  26012. * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams();
  26013. *
  26014. * @method getParams
  26015. * @return {Object} Name/value object with parameters passed from windowManager.open call.
  26016. */
  26017. self.getParams = function() {
  26018. return getTopMostWindow() ? getTopMostWindow().params : null;
  26019. };
  26020. /**
  26021. * Sets the params of the last opened window.
  26022. *
  26023. * @method setParams
  26024. * @param {Object} params Params object to set for the last opened window.
  26025. */
  26026. self.setParams = function(params) {
  26027. if (getTopMostWindow()) {
  26028. getTopMostWindow().params = params;
  26029. }
  26030. };
  26031. /**
  26032. * Returns the currently opened window objects.
  26033. *
  26034. * @method getWindows
  26035. * @return {Array} Array of the currently opened windows.
  26036. */
  26037. self.getWindows = function() {
  26038. return windows;
  26039. };
  26040. };
  26041. });
  26042. // Included from: js/tinymce/classes/ui/Tooltip.js
  26043. /**
  26044. * Tooltip.js
  26045. *
  26046. * Released under LGPL License.
  26047. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  26048. *
  26049. * License: http://www.tinymce.com/license
  26050. * Contributing: http://www.tinymce.com/contributing
  26051. */
  26052. /**
  26053. * Creates a tooltip instance.
  26054. *
  26055. * @-x-less ToolTip.less
  26056. * @class tinymce.ui.ToolTip
  26057. * @extends tinymce.ui.Control
  26058. * @mixes tinymce.ui.Movable
  26059. */
  26060. define("tinymce/ui/Tooltip", [
  26061. "tinymce/ui/Control",
  26062. "tinymce/ui/Movable"
  26063. ], function(Control, Movable) {
  26064. return Control.extend({
  26065. Mixins: [Movable],
  26066. Defaults: {
  26067. classes: 'widget tooltip tooltip-n'
  26068. },
  26069. /**
  26070. * Renders the control as a HTML string.
  26071. *
  26072. * @method renderHtml
  26073. * @return {String} HTML representing the control.
  26074. */
  26075. renderHtml: function() {
  26076. var self = this, prefix = self.classPrefix;
  26077. return (
  26078. '<div id="' + self._id + '" class="' + self.classes + '" role="presentation">' +
  26079. '<div class="' + prefix + 'tooltip-arrow"></div>' +
  26080. '<div class="' + prefix + 'tooltip-inner">' + self.encode(self.state.get('text')) + '</div>' +
  26081. '</div>'
  26082. );
  26083. },
  26084. bindStates: function() {
  26085. var self = this;
  26086. self.state.on('change:text', function(e) {
  26087. self.getEl().lastChild.innerHTML = self.encode(e.value);
  26088. });
  26089. return self._super();
  26090. },
  26091. /**
  26092. * Repaints the control after a layout operation.
  26093. *
  26094. * @method repaint
  26095. */
  26096. repaint: function() {
  26097. var self = this, style, rect;
  26098. style = self.getEl().style;
  26099. rect = self._layoutRect;
  26100. style.left = rect.x + 'px';
  26101. style.top = rect.y + 'px';
  26102. style.zIndex = 0xFFFF + 0xFFFF;
  26103. }
  26104. });
  26105. });
  26106. // Included from: js/tinymce/classes/ui/Widget.js
  26107. /**
  26108. * Widget.js
  26109. *
  26110. * Released under LGPL License.
  26111. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  26112. *
  26113. * License: http://www.tinymce.com/license
  26114. * Contributing: http://www.tinymce.com/contributing
  26115. */
  26116. /**
  26117. * Widget base class a widget is a control that has a tooltip and some basic states.
  26118. *
  26119. * @class tinymce.ui.Widget
  26120. * @extends tinymce.ui.Control
  26121. */
  26122. define("tinymce/ui/Widget", [
  26123. "tinymce/ui/Control",
  26124. "tinymce/ui/Tooltip"
  26125. ], function(Control, Tooltip) {
  26126. "use strict";
  26127. var tooltip;
  26128. var Widget = Control.extend({
  26129. /**
  26130. * Constructs a instance with the specified settings.
  26131. *
  26132. * @constructor
  26133. * @param {Object} settings Name/value object with settings.
  26134. * @setting {String} tooltip Tooltip text to display when hovering.
  26135. * @setting {Boolean} autofocus True if the control should be focused when rendered.
  26136. * @setting {String} text Text to display inside widget.
  26137. */
  26138. init: function(settings) {
  26139. var self = this;
  26140. self._super(settings);
  26141. settings = self.settings;
  26142. self.canFocus = true;
  26143. if (settings.tooltip && Widget.tooltips !== false) {
  26144. self.on('mouseenter', function(e) {
  26145. var tooltip = self.tooltip().moveTo(-0xFFFF);
  26146. if (e.control == self) {
  26147. var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']);
  26148. tooltip.classes.toggle('tooltip-n', rel == 'bc-tc');
  26149. tooltip.classes.toggle('tooltip-nw', rel == 'bc-tl');
  26150. tooltip.classes.toggle('tooltip-ne', rel == 'bc-tr');
  26151. tooltip.moveRel(self.getEl(), rel);
  26152. } else {
  26153. tooltip.hide();
  26154. }
  26155. });
  26156. self.on('mouseleave mousedown click', function() {
  26157. self.tooltip().hide();
  26158. });
  26159. }
  26160. self.aria('label', settings.ariaLabel || settings.tooltip);
  26161. },
  26162. /**
  26163. * Returns the current tooltip instance.
  26164. *
  26165. * @method tooltip
  26166. * @return {tinymce.ui.Tooltip} Tooltip instance.
  26167. */
  26168. tooltip: function() {
  26169. if (!tooltip) {
  26170. tooltip = new Tooltip({type: 'tooltip'});
  26171. tooltip.renderTo();
  26172. }
  26173. return tooltip;
  26174. },
  26175. /**
  26176. * Called after the control has been rendered.
  26177. *
  26178. * @method postRender
  26179. */
  26180. postRender: function() {
  26181. var self = this, settings = self.settings;
  26182. self._super();
  26183. if (!self.parent() && (settings.width || settings.height)) {
  26184. self.initLayoutRect();
  26185. self.repaint();
  26186. }
  26187. if (settings.autofocus) {
  26188. self.focus();
  26189. }
  26190. },
  26191. bindStates: function() {
  26192. var self = this;
  26193. function disable(state) {
  26194. self.aria('disabled', state);
  26195. self.classes.toggle('disabled', state);
  26196. }
  26197. function active(state) {
  26198. self.aria('pressed', state);
  26199. self.classes.toggle('active', state);
  26200. }
  26201. self.state.on('change:disabled', function(e) {
  26202. disable(e.value);
  26203. });
  26204. self.state.on('change:active', function(e) {
  26205. active(e.value);
  26206. });
  26207. if (self.state.get('disabled')) {
  26208. disable(true);
  26209. }
  26210. if (self.state.get('active')) {
  26211. active(true);
  26212. }
  26213. return self._super();
  26214. },
  26215. /**
  26216. * Removes the current control from DOM and from UI collections.
  26217. *
  26218. * @method remove
  26219. * @return {tinymce.ui.Control} Current control instance.
  26220. */
  26221. remove: function() {
  26222. this._super();
  26223. if (tooltip) {
  26224. tooltip.remove();
  26225. tooltip = null;
  26226. }
  26227. }
  26228. });
  26229. return Widget;
  26230. });
  26231. // Included from: js/tinymce/classes/ui/Progress.js
  26232. /**
  26233. * Progress.js
  26234. *
  26235. * Released under LGPL License.
  26236. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  26237. *
  26238. * License: http://www.tinymce.com/license
  26239. * Contributing: http://www.tinymce.com/contributing
  26240. */
  26241. /**
  26242. * Progress control.
  26243. *
  26244. * @-x-less Progress.less
  26245. * @class tinymce.ui.Progress
  26246. * @extends tinymce.ui.Control
  26247. */
  26248. define("tinymce/ui/Progress", [
  26249. "tinymce/ui/Widget"
  26250. ], function(Widget) {
  26251. "use strict";
  26252. return Widget.extend({
  26253. Defaults: {
  26254. value: 0
  26255. },
  26256. init: function(settings) {
  26257. var self = this;
  26258. self._super(settings);
  26259. self.classes.add('progress');
  26260. if (!self.settings.filter) {
  26261. self.settings.filter = function(value) {
  26262. return Math.round(value);
  26263. };
  26264. }
  26265. },
  26266. renderHtml: function() {
  26267. var self = this, id = self._id, prefix = this.classPrefix;
  26268. return (
  26269. '<div id="' + id + '" class="' + self.classes + '">' +
  26270. '<div class="' + prefix + 'bar-container">' +
  26271. '<div class="' + prefix + 'bar"></div>' +
  26272. '</div>' +
  26273. '<div class="' + prefix + 'text">0%</div>' +
  26274. '</div>'
  26275. );
  26276. },
  26277. postRender: function() {
  26278. var self = this;
  26279. self._super();
  26280. self.value(self.settings.value);
  26281. return self;
  26282. },
  26283. bindStates: function() {
  26284. var self = this;
  26285. function setValue(value) {
  26286. value = self.settings.filter(value);
  26287. self.getEl().lastChild.innerHTML = value + '%';
  26288. self.getEl().firstChild.firstChild.style.width = value + '%';
  26289. }
  26290. self.state.on('change:value', function(e) {
  26291. setValue(e.value);
  26292. });
  26293. setValue(self.state.get('value'));
  26294. return self._super();
  26295. }
  26296. });
  26297. });
  26298. // Included from: js/tinymce/classes/ui/Notification.js
  26299. /**
  26300. * Notification.js
  26301. *
  26302. * Released under LGPL License.
  26303. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  26304. *
  26305. * License: http://www.tinymce.com/license
  26306. * Contributing: http://www.tinymce.com/contributing
  26307. */
  26308. /**
  26309. * Creates a notification instance.
  26310. *
  26311. * @-x-less Notification.less
  26312. * @class tinymce.ui.Notification
  26313. * @extends tinymce.ui.Container
  26314. * @mixes tinymce.ui.Movable
  26315. */
  26316. define("tinymce/ui/Notification", [
  26317. "tinymce/ui/Control",
  26318. "tinymce/ui/Movable",
  26319. "tinymce/ui/Progress",
  26320. "tinymce/util/Delay"
  26321. ], function(Control, Movable, Progress, Delay) {
  26322. return Control.extend({
  26323. Mixins: [Movable],
  26324. Defaults: {
  26325. classes: 'widget notification'
  26326. },
  26327. init: function(settings) {
  26328. var self = this;
  26329. self._super(settings);
  26330. if (settings.text) {
  26331. self.text(settings.text);
  26332. }
  26333. if (settings.icon) {
  26334. self.icon = settings.icon;
  26335. }
  26336. if (settings.color) {
  26337. self.color = settings.color;
  26338. }
  26339. if (settings.type) {
  26340. self.classes.add('notification-' + settings.type);
  26341. }
  26342. if (settings.timeout && (settings.timeout < 0 || settings.timeout > 0) && !settings.closeButton) {
  26343. self.closeButton = false;
  26344. } else {
  26345. self.classes.add('has-close');
  26346. self.closeButton = true;
  26347. }
  26348. if (settings.progressBar) {
  26349. self.progressBar = new Progress();
  26350. }
  26351. self.on('click', function(e) {
  26352. if (e.target.className.indexOf(self.classPrefix + 'close') != -1) {
  26353. self.close();
  26354. }
  26355. });
  26356. },
  26357. /**
  26358. * Renders the control as a HTML string.
  26359. *
  26360. * @method renderHtml
  26361. * @return {String} HTML representing the control.
  26362. */
  26363. renderHtml: function() {
  26364. var self = this, prefix = self.classPrefix, icon = '', closeButton = '', progressBar = '', notificationStyle = '';
  26365. if (self.icon) {
  26366. icon = '<i class="' + prefix + 'ico' + ' ' + prefix + 'i-' + self.icon + '"></i>';
  26367. }
  26368. if (self.color) {
  26369. notificationStyle = ' style="background-color: ' + self.color + '"';
  26370. }
  26371. if (self.closeButton) {
  26372. closeButton = '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>';
  26373. }
  26374. if (self.progressBar) {
  26375. progressBar = self.progressBar.renderHtml();
  26376. }
  26377. return (
  26378. '<div id="' + self._id + '" class="' + self.classes + '"' + notificationStyle + ' role="presentation">' +
  26379. icon +
  26380. '<div class="' + prefix + 'notification-inner">' + self.state.get('text') + '</div>' +
  26381. progressBar +
  26382. closeButton +
  26383. '</div>'
  26384. );
  26385. },
  26386. postRender: function() {
  26387. var self = this;
  26388. Delay.setTimeout(function() {
  26389. self.$el.addClass(self.classPrefix + 'in');
  26390. });
  26391. return self._super();
  26392. },
  26393. bindStates: function() {
  26394. var self = this;
  26395. self.state.on('change:text', function(e) {
  26396. self.getEl().childNodes[1].innerHTML = e.value;
  26397. });
  26398. if (self.progressBar) {
  26399. self.progressBar.bindStates();
  26400. }
  26401. return self._super();
  26402. },
  26403. close: function() {
  26404. var self = this;
  26405. if (!self.fire('close').isDefaultPrevented()) {
  26406. self.remove();
  26407. }
  26408. return self;
  26409. },
  26410. /**
  26411. * Repaints the control after a layout operation.
  26412. *
  26413. * @method repaint
  26414. */
  26415. repaint: function() {
  26416. var self = this, style, rect;
  26417. style = self.getEl().style;
  26418. rect = self._layoutRect;
  26419. style.left = rect.x + 'px';
  26420. style.top = rect.y + 'px';
  26421. style.zIndex = 0xFFFF + 0xFFFF;
  26422. }
  26423. });
  26424. });
  26425. // Included from: js/tinymce/classes/NotificationManager.js
  26426. /**
  26427. * NotificationManager.js
  26428. *
  26429. * Released under LGPL License.
  26430. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  26431. *
  26432. * License: http://www.tinymce.com/license
  26433. * Contributing: http://www.tinymce.com/contributing
  26434. */
  26435. /**
  26436. * This class handles the creation of TinyMCE's notifications.
  26437. *
  26438. * @class tinymce.notificationManager
  26439. * @example
  26440. * // Opens a new notification of type "error" with text "An error occurred."
  26441. * tinymce.activeEditor.notificationManager.open({
  26442. * text: 'An error occurred.',
  26443. * type: 'error'
  26444. * });
  26445. */
  26446. define("tinymce/NotificationManager", [
  26447. "tinymce/ui/Notification",
  26448. "tinymce/util/Delay"
  26449. ], function(Notification, Delay) {
  26450. return function(editor) {
  26451. var self = this, notifications = [];
  26452. function getLastNotification() {
  26453. if (notifications.length) {
  26454. return notifications[notifications.length - 1];
  26455. }
  26456. }
  26457. self.notifications = notifications;
  26458. function resizeWindowEvent() {
  26459. Delay.requestAnimationFrame(function() {
  26460. prePositionNotifications();
  26461. positionNotifications();
  26462. });
  26463. }
  26464. // Since the viewport will change based on the present notifications, we need to move them all to the
  26465. // top left of the viewport to give an accurate size measurement so we can position them later.
  26466. function prePositionNotifications() {
  26467. for (var i = 0; i < notifications.length; i++) {
  26468. notifications[i].moveTo(0, 0);
  26469. }
  26470. }
  26471. function positionNotifications() {
  26472. if (notifications.length > 0) {
  26473. var firstItem = notifications.slice(0, 1)[0];
  26474. var container = editor.inline ? editor.getElement() : editor.getContentAreaContainer();
  26475. firstItem.moveRel(container, 'tc-tc');
  26476. if (notifications.length > 1) {
  26477. for (var i = 1; i < notifications.length; i++) {
  26478. notifications[i].moveRel(notifications[i - 1].getEl(), 'bc-tc');
  26479. }
  26480. }
  26481. }
  26482. }
  26483. editor.on('remove', function() {
  26484. var i = notifications.length;
  26485. while (i--) {
  26486. notifications[i].close();
  26487. }
  26488. });
  26489. editor.on('ResizeEditor', positionNotifications);
  26490. editor.on('ResizeWindow', resizeWindowEvent);
  26491. /**
  26492. * Opens a new notification.
  26493. *
  26494. * @method open
  26495. * @param {Object} args Optional name/value settings collection contains things like timeout/color/message etc.
  26496. */
  26497. self.open = function(args) {
  26498. var notif;
  26499. editor.editorManager.setActive(editor);
  26500. notif = new Notification(args);
  26501. notifications.push(notif);
  26502. //If we have a timeout value
  26503. if (args.timeout > 0) {
  26504. notif.timer = setTimeout(function() {
  26505. notif.close();
  26506. }, args.timeout);
  26507. }
  26508. notif.on('close', function() {
  26509. var i = notifications.length;
  26510. if (notif.timer) {
  26511. editor.getWin().clearTimeout(notif.timer);
  26512. }
  26513. while (i--) {
  26514. if (notifications[i] === notif) {
  26515. notifications.splice(i, 1);
  26516. }
  26517. }
  26518. positionNotifications();
  26519. });
  26520. notif.renderTo();
  26521. positionNotifications();
  26522. return notif;
  26523. };
  26524. /**
  26525. * Closes the top most notification.
  26526. *
  26527. * @method close
  26528. */
  26529. self.close = function() {
  26530. if (getLastNotification()) {
  26531. getLastNotification().close();
  26532. }
  26533. };
  26534. /**
  26535. * Returns the currently opened notification objects.
  26536. *
  26537. * @method getNotifications
  26538. * @return {Array} Array of the currently opened notifications.
  26539. */
  26540. self.getNotifications = function() {
  26541. return notifications;
  26542. };
  26543. editor.on('SkinLoaded', function() {
  26544. var serviceMessage = editor.settings.service_message;
  26545. if (serviceMessage) {
  26546. editor.notificationManager.open({
  26547. text: serviceMessage,
  26548. type: 'warning',
  26549. timeout: 0,
  26550. icon: ''
  26551. });
  26552. }
  26553. });
  26554. //self.positionNotifications = positionNotifications;
  26555. };
  26556. });
  26557. // Included from: js/tinymce/classes/dom/NodePath.js
  26558. /**
  26559. * NodePath.js
  26560. *
  26561. * Released under LGPL License.
  26562. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  26563. *
  26564. * License: http://www.tinymce.com/license
  26565. * Contributing: http://www.tinymce.com/contributing
  26566. */
  26567. /**
  26568. * Handles paths of nodes within an element.
  26569. *
  26570. * @private
  26571. * @class tinymce.dom.NodePath
  26572. */
  26573. define("tinymce/dom/NodePath", [
  26574. "tinymce/dom/DOMUtils"
  26575. ], function(DOMUtils) {
  26576. function create(rootNode, targetNode, normalized) {
  26577. var path = [];
  26578. for (; targetNode && targetNode != rootNode; targetNode = targetNode.parentNode) {
  26579. path.push(DOMUtils.nodeIndex(targetNode, normalized));
  26580. }
  26581. return path;
  26582. }
  26583. function resolve(rootNode, path) {
  26584. var i, node, children;
  26585. for (node = rootNode, i = path.length - 1; i >= 0; i--) {
  26586. children = node.childNodes;
  26587. if (path[i] > children.length - 1) {
  26588. return null;
  26589. }
  26590. node = children[path[i]];
  26591. }
  26592. return node;
  26593. }
  26594. return {
  26595. create: create,
  26596. resolve: resolve
  26597. };
  26598. });
  26599. // Included from: js/tinymce/classes/util/Quirks.js
  26600. /**
  26601. * Quirks.js
  26602. *
  26603. * Released under LGPL License.
  26604. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  26605. *
  26606. * License: http://www.tinymce.com/license
  26607. * Contributing: http://www.tinymce.com/contributing
  26608. *
  26609. * @ignore-file
  26610. */
  26611. /**
  26612. * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes.
  26613. *
  26614. * @private
  26615. * @class tinymce.util.Quirks
  26616. */
  26617. define("tinymce/util/Quirks", [
  26618. "tinymce/util/VK",
  26619. "tinymce/dom/RangeUtils",
  26620. "tinymce/dom/TreeWalker",
  26621. "tinymce/dom/NodePath",
  26622. "tinymce/html/Node",
  26623. "tinymce/html/Entities",
  26624. "tinymce/Env",
  26625. "tinymce/util/Tools",
  26626. "tinymce/util/Delay",
  26627. "tinymce/caret/CaretContainer",
  26628. "tinymce/caret/CaretPosition",
  26629. "tinymce/caret/CaretWalker"
  26630. ], function(VK, RangeUtils, TreeWalker, NodePath, Node, Entities, Env, Tools, Delay, CaretContainer, CaretPosition, CaretWalker) {
  26631. return function(editor) {
  26632. var each = Tools.each, $ = editor.$;
  26633. var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
  26634. settings = editor.settings, parser = editor.parser, serializer = editor.serializer;
  26635. var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit;
  26636. var mceInternalUrlPrefix = 'data:text/mce-internal,';
  26637. var mceInternalDataType = isIE ? 'Text' : 'URL';
  26638. /**
  26639. * Executes a command with a specific state this can be to enable/disable browser editing features.
  26640. */
  26641. function setEditorCommandState(cmd, state) {
  26642. try {
  26643. editor.getDoc().execCommand(cmd, false, state);
  26644. } catch (ex) {
  26645. // Ignore
  26646. }
  26647. }
  26648. /**
  26649. * Returns current IE document mode.
  26650. */
  26651. function getDocumentMode() {
  26652. var documentMode = editor.getDoc().documentMode;
  26653. return documentMode ? documentMode : 6;
  26654. }
  26655. /**
  26656. * Returns true/false if the event is prevented or not.
  26657. *
  26658. * @private
  26659. * @param {Event} e Event object.
  26660. * @return {Boolean} true/false if the event is prevented or not.
  26661. */
  26662. function isDefaultPrevented(e) {
  26663. return e.isDefaultPrevented();
  26664. }
  26665. /**
  26666. * Sets Text/URL data on the event's dataTransfer object to a special data:text/mce-internal url.
  26667. * This is to workaround the inability to set custom contentType on IE and Safari.
  26668. * The editor's selected content is encoded into this url so drag and drop between editors will work.
  26669. *
  26670. * @private
  26671. * @param {DragEvent} e Event object
  26672. */
  26673. function setMceInternalContent(e) {
  26674. var selectionHtml, internalContent;
  26675. if (e.dataTransfer) {
  26676. if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') {
  26677. selection.select(e.target);
  26678. }
  26679. selectionHtml = editor.selection.getContent();
  26680. // Safari/IE doesn't support custom dataTransfer items so we can only use URL and Text
  26681. if (selectionHtml.length > 0) {
  26682. internalContent = mceInternalUrlPrefix + escape(editor.id) + ',' + escape(selectionHtml);
  26683. e.dataTransfer.setData(mceInternalDataType, internalContent);
  26684. }
  26685. }
  26686. }
  26687. /**
  26688. * Gets content of special data:text/mce-internal url on the event's dataTransfer object.
  26689. * This is to workaround the inability to set custom contentType on IE and Safari.
  26690. * The editor's selected content is encoded into this url so drag and drop between editors will work.
  26691. *
  26692. * @private
  26693. * @param {DragEvent} e Event object
  26694. * @returns {String} mce-internal content
  26695. */
  26696. function getMceInternalContent(e) {
  26697. var internalContent;
  26698. if (e.dataTransfer) {
  26699. internalContent = e.dataTransfer.getData(mceInternalDataType);
  26700. if (internalContent && internalContent.indexOf(mceInternalUrlPrefix) >= 0) {
  26701. internalContent = internalContent.substr(mceInternalUrlPrefix.length).split(',');
  26702. return {
  26703. id: unescape(internalContent[0]),
  26704. html: unescape(internalContent[1])
  26705. };
  26706. }
  26707. }
  26708. return null;
  26709. }
  26710. /**
  26711. * Inserts contents using the paste clipboard command if it's available if it isn't it will fallback
  26712. * to the core command.
  26713. *
  26714. * @private
  26715. * @param {String} content Content to insert at selection.
  26716. */
  26717. function insertClipboardContents(content) {
  26718. if (editor.queryCommandSupported('mceInsertClipboardContent')) {
  26719. editor.execCommand('mceInsertClipboardContent', false, {content: content});
  26720. } else {
  26721. editor.execCommand('mceInsertContent', false, content);
  26722. }
  26723. }
  26724. /**
  26725. * Fixes a WebKit bug when deleting contents using backspace or delete key.
  26726. * WebKit will produce a span element if you delete across two block elements.
  26727. *
  26728. * Example:
  26729. * <h1>a</h1><p>|b</p>
  26730. *
  26731. * Will produce this on backspace:
  26732. * <h1>a<span style="<all runtime styles>">b</span></p>
  26733. *
  26734. * This fixes the backspace to produce:
  26735. * <h1>a|b</p>
  26736. *
  26737. * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
  26738. *
  26739. * This fixes the following delete scenarios:
  26740. * 1. Delete by pressing backspace key.
  26741. * 2. Delete by pressing delete key.
  26742. * 3. Delete by pressing backspace key with ctrl/cmd (Word delete).
  26743. * 4. Delete by pressing delete key with ctrl/cmd (Word delete).
  26744. * 5. Delete by drag/dropping contents inside the editor.
  26745. * 6. Delete by using Cut Ctrl+X/Cmd+X.
  26746. * 7. Delete by selecting contents and writing a character.
  26747. *
  26748. * This code is a ugly hack since writing full custom delete logic for just this bug
  26749. * fix seemed like a huge task. I hope we can remove this before the year 2030.
  26750. */
  26751. function cleanupStylesWhenDeleting() {
  26752. var doc = editor.getDoc(), dom = editor.dom, selection = editor.selection;
  26753. var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng;
  26754. // Add mini polyfill for older WebKits
  26755. // TODO: Remove this when old Safari versions gets updated
  26756. if (!MutationObserver) {
  26757. olderWebKit = true;
  26758. MutationObserver = function() {
  26759. var records = [], target;
  26760. function nodeInsert(e) {
  26761. var target = e.relatedNode || e.target;
  26762. records.push({target: target, addedNodes: [target]});
  26763. }
  26764. function attrModified(e) {
  26765. var target = e.relatedNode || e.target;
  26766. records.push({target: target, attributeName: e.attrName});
  26767. }
  26768. this.observe = function(node) {
  26769. target = node;
  26770. target.addEventListener('DOMSubtreeModified', nodeInsert, false);
  26771. target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
  26772. target.addEventListener('DOMNodeInserted', nodeInsert, false);
  26773. target.addEventListener('DOMAttrModified', attrModified, false);
  26774. };
  26775. this.disconnect = function() {
  26776. target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
  26777. target.removeEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
  26778. target.removeEventListener('DOMNodeInserted', nodeInsert, false);
  26779. target.removeEventListener('DOMAttrModified', attrModified, false);
  26780. };
  26781. this.takeRecords = function() {
  26782. return records;
  26783. };
  26784. };
  26785. }
  26786. function isTrailingBr(node) {
  26787. var blockElements = dom.schema.getBlockElements(), rootNode = editor.getBody();
  26788. if (node.nodeName != 'BR') {
  26789. return false;
  26790. }
  26791. for (; node != rootNode && !blockElements[node.nodeName]; node = node.parentNode) {
  26792. if (node.nextSibling) {
  26793. return false;
  26794. }
  26795. }
  26796. return true;
  26797. }
  26798. function isSiblingsIgnoreWhiteSpace(node1, node2) {
  26799. var node;
  26800. for (node = node1.nextSibling; node && node != node2; node = node.nextSibling) {
  26801. if (node.nodeType == 3 && $.trim(node.data).length === 0) {
  26802. continue;
  26803. }
  26804. if (node !== node2) {
  26805. return false;
  26806. }
  26807. }
  26808. return node === node2;
  26809. }
  26810. function findCaretNode(node, forward, startNode) {
  26811. var walker, current, nonEmptyElements;
  26812. nonEmptyElements = dom.schema.getNonEmptyElements();
  26813. walker = new TreeWalker(startNode || node, node);
  26814. while ((current = walker[forward ? 'next' : 'prev']())) {
  26815. if (nonEmptyElements[current.nodeName] && !isTrailingBr(current)) {
  26816. return current;
  26817. }
  26818. if (current.nodeType == 3 && current.data.length > 0) {
  26819. return current;
  26820. }
  26821. }
  26822. }
  26823. function deleteRangeBetweenTextBlocks(rng) {
  26824. var startBlock, endBlock, caretNodeBefore, caretNodeAfter, textBlockElements;
  26825. if (rng.collapsed) {
  26826. return;
  26827. }
  26828. startBlock = dom.getParent(RangeUtils.getNode(rng.startContainer, rng.startOffset), dom.isBlock);
  26829. endBlock = dom.getParent(RangeUtils.getNode(rng.endContainer, rng.endOffset), dom.isBlock);
  26830. textBlockElements = editor.schema.getTextBlockElements();
  26831. if (startBlock == endBlock) {
  26832. return;
  26833. }
  26834. if (!textBlockElements[startBlock.nodeName] || !textBlockElements[endBlock.nodeName]) {
  26835. return;
  26836. }
  26837. if (dom.getContentEditable(startBlock) === "false" || dom.getContentEditable(endBlock) === "false") {
  26838. return;
  26839. }
  26840. rng.deleteContents();
  26841. caretNodeBefore = findCaretNode(startBlock, false);
  26842. caretNodeAfter = findCaretNode(endBlock, true);
  26843. if (!dom.isEmpty(endBlock)) {
  26844. $(startBlock).append(endBlock.childNodes);
  26845. }
  26846. $(endBlock).remove();
  26847. if (caretNodeBefore) {
  26848. if (caretNodeBefore.nodeType == 1) {
  26849. if (caretNodeBefore.nodeName == "BR") {
  26850. rng.setStartBefore(caretNodeBefore);
  26851. rng.setEndBefore(caretNodeBefore);
  26852. } else {
  26853. rng.setStartAfter(caretNodeBefore);
  26854. rng.setEndAfter(caretNodeBefore);
  26855. }
  26856. } else {
  26857. rng.setStart(caretNodeBefore, caretNodeBefore.data.length);
  26858. rng.setEnd(caretNodeBefore, caretNodeBefore.data.length);
  26859. }
  26860. } else if (caretNodeAfter) {
  26861. if (caretNodeAfter.nodeType == 1) {
  26862. rng.setStartBefore(caretNodeAfter);
  26863. rng.setEndBefore(caretNodeAfter);
  26864. } else {
  26865. rng.setStart(caretNodeAfter, 0);
  26866. rng.setEnd(caretNodeAfter, 0);
  26867. }
  26868. }
  26869. selection.setRng(rng);
  26870. return true;
  26871. }
  26872. function expandBetweenBlocks(rng, isForward) {
  26873. var caretNode, targetCaretNode, textBlock, targetTextBlock, container, offset;
  26874. if (!rng.collapsed) {
  26875. return rng;
  26876. }
  26877. container = rng.startContainer;
  26878. offset = rng.startOffset;
  26879. if (container.nodeType == 3) {
  26880. if (isForward) {
  26881. if (offset < container.data.length) {
  26882. return rng;
  26883. }
  26884. } else {
  26885. if (offset > 0) {
  26886. return rng;
  26887. }
  26888. }
  26889. }
  26890. caretNode = RangeUtils.getNode(rng.startContainer, rng.startOffset);
  26891. textBlock = dom.getParent(caretNode, dom.isBlock);
  26892. targetCaretNode = findCaretNode(editor.getBody(), isForward, caretNode);
  26893. targetTextBlock = dom.getParent(targetCaretNode, dom.isBlock);
  26894. if (!caretNode || !targetCaretNode) {
  26895. return rng;
  26896. }
  26897. if (targetTextBlock && textBlock != targetTextBlock) {
  26898. if (!isForward) {
  26899. if (!isSiblingsIgnoreWhiteSpace(targetTextBlock, textBlock)) {
  26900. return rng;
  26901. }
  26902. if (targetCaretNode.nodeType == 1) {
  26903. if (targetCaretNode.nodeName == "BR") {
  26904. rng.setStartBefore(targetCaretNode);
  26905. } else {
  26906. rng.setStartAfter(targetCaretNode);
  26907. }
  26908. } else {
  26909. rng.setStart(targetCaretNode, targetCaretNode.data.length);
  26910. }
  26911. if (caretNode.nodeType == 1) {
  26912. rng.setEnd(caretNode, 0);
  26913. } else {
  26914. rng.setEndBefore(caretNode);
  26915. }
  26916. } else {
  26917. if (!isSiblingsIgnoreWhiteSpace(textBlock, targetTextBlock)) {
  26918. return rng;
  26919. }
  26920. if (caretNode.nodeType == 1) {
  26921. if (caretNode.nodeName == "BR") {
  26922. rng.setStartBefore(caretNode);
  26923. } else {
  26924. rng.setStartAfter(caretNode);
  26925. }
  26926. } else {
  26927. rng.setStart(caretNode, caretNode.data.length);
  26928. }
  26929. if (targetCaretNode.nodeType == 1) {
  26930. rng.setEnd(targetCaretNode, 0);
  26931. } else {
  26932. rng.setEndBefore(targetCaretNode);
  26933. }
  26934. }
  26935. }
  26936. return rng;
  26937. }
  26938. function handleTextBlockMergeDelete(isForward) {
  26939. var rng = selection.getRng();
  26940. rng = expandBetweenBlocks(rng, isForward);
  26941. if (deleteRangeBetweenTextBlocks(rng)) {
  26942. return true;
  26943. }
  26944. }
  26945. /**
  26946. * This retains the formatting if the last character is to be deleted.
  26947. *
  26948. * Backspace on this: <p><b><i>a|</i></b></p> would become <p>|</p> in WebKit.
  26949. * With this patch: <p><b><i>|<br></i></b></p>
  26950. */
  26951. function handleLastBlockCharacterDelete(isForward, rng) {
  26952. var path, blockElm, newBlockElm, clonedBlockElm, sibling,
  26953. container, offset, br, currentFormatNodes;
  26954. function cloneTextBlockWithFormats(blockElm, node) {
  26955. currentFormatNodes = $(node).parents().filter(function(idx, node) {
  26956. return !!editor.schema.getTextInlineElements()[node.nodeName];
  26957. });
  26958. newBlockElm = blockElm.cloneNode(false);
  26959. currentFormatNodes = Tools.map(currentFormatNodes, function(formatNode) {
  26960. formatNode = formatNode.cloneNode(false);
  26961. if (newBlockElm.hasChildNodes()) {
  26962. formatNode.appendChild(newBlockElm.firstChild);
  26963. newBlockElm.appendChild(formatNode);
  26964. } else {
  26965. newBlockElm.appendChild(formatNode);
  26966. }
  26967. newBlockElm.appendChild(formatNode);
  26968. return formatNode;
  26969. });
  26970. if (currentFormatNodes.length) {
  26971. br = dom.create('br');
  26972. currentFormatNodes[0].appendChild(br);
  26973. dom.replace(newBlockElm, blockElm);
  26974. rng.setStartBefore(br);
  26975. rng.setEndBefore(br);
  26976. editor.selection.setRng(rng);
  26977. return br;
  26978. }
  26979. return null;
  26980. }
  26981. function isTextBlock(node) {
  26982. return node && editor.schema.getTextBlockElements()[node.tagName];
  26983. }
  26984. if (!rng.collapsed) {
  26985. return;
  26986. }
  26987. container = rng.startContainer;
  26988. offset = rng.startOffset;
  26989. blockElm = dom.getParent(container, dom.isBlock);
  26990. if (!isTextBlock(blockElm)) {
  26991. return;
  26992. }
  26993. if (container.nodeType == 1) {
  26994. container = container.childNodes[offset];
  26995. if (container && container.tagName != 'BR') {
  26996. return;
  26997. }
  26998. if (isForward) {
  26999. sibling = blockElm.nextSibling;
  27000. } else {
  27001. sibling = blockElm.previousSibling;
  27002. }
  27003. if (dom.isEmpty(blockElm) && isTextBlock(sibling) && dom.isEmpty(sibling)) {
  27004. if (cloneTextBlockWithFormats(blockElm, container)) {
  27005. dom.remove(sibling);
  27006. return true;
  27007. }
  27008. }
  27009. } else if (container.nodeType == 3) {
  27010. path = NodePath.create(blockElm, container);
  27011. clonedBlockElm = blockElm.cloneNode(true);
  27012. container = NodePath.resolve(clonedBlockElm, path);
  27013. if (isForward) {
  27014. if (offset >= container.data.length) {
  27015. return;
  27016. }
  27017. container.deleteData(offset, 1);
  27018. } else {
  27019. if (offset <= 0) {
  27020. return;
  27021. }
  27022. container.deleteData(offset - 1, 1);
  27023. }
  27024. if (dom.isEmpty(clonedBlockElm)) {
  27025. return cloneTextBlockWithFormats(blockElm, container);
  27026. }
  27027. }
  27028. }
  27029. function customDelete(isForward) {
  27030. var mutationObserver, rng, caretElement;
  27031. if (handleTextBlockMergeDelete(isForward)) {
  27032. return;
  27033. }
  27034. Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) {
  27035. // Mark existing spans
  27036. if (elm.tagName == 'SPAN') {
  27037. elm.setAttribute('mce-data-marked', 1);
  27038. }
  27039. // Make sure all elements has a data-mce-style attribute
  27040. if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) {
  27041. editor.dom.setAttrib(elm, 'style', editor.dom.getAttrib(elm, 'style'));
  27042. }
  27043. });
  27044. // Observe added nodes and style attribute changes
  27045. mutationObserver = new MutationObserver(function() {});
  27046. mutationObserver.observe(editor.getDoc(), {
  27047. childList: true,
  27048. attributes: true,
  27049. subtree: true,
  27050. attributeFilter: ['style']
  27051. });
  27052. editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null);
  27053. rng = editor.selection.getRng();
  27054. caretElement = rng.startContainer.parentNode;
  27055. Tools.each(mutationObserver.takeRecords(), function(record) {
  27056. if (!dom.isChildOf(record.target, editor.getBody())) {
  27057. return;
  27058. }
  27059. // Restore style attribute to previous value
  27060. if (record.attributeName == "style") {
  27061. var oldValue = record.target.getAttribute('data-mce-style');
  27062. if (oldValue) {
  27063. record.target.setAttribute("style", oldValue);
  27064. } else {
  27065. record.target.removeAttribute("style");
  27066. }
  27067. }
  27068. // Remove all spans that aren't marked and retain selection
  27069. Tools.each(record.addedNodes, function(node) {
  27070. if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) {
  27071. var offset, container;
  27072. if (node == caretElement) {
  27073. offset = rng.startOffset;
  27074. container = node.firstChild;
  27075. }
  27076. dom.remove(node, true);
  27077. if (container) {
  27078. rng.setStart(container, offset);
  27079. rng.setEnd(container, offset);
  27080. editor.selection.setRng(rng);
  27081. }
  27082. }
  27083. });
  27084. });
  27085. mutationObserver.disconnect();
  27086. // Remove any left over marks
  27087. Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) {
  27088. span.removeAttribute('mce-data-marked');
  27089. });
  27090. }
  27091. editor.on('keydown', function(e) {
  27092. var isForward = e.keyCode == DELETE, isMetaOrCtrl = e.ctrlKey || e.metaKey;
  27093. if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) {
  27094. var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset;
  27095. // Shift+Delete is cut
  27096. if (isForward && e.shiftKey) {
  27097. return;
  27098. }
  27099. if (handleLastBlockCharacterDelete(isForward, rng)) {
  27100. e.preventDefault();
  27101. return;
  27102. }
  27103. // Ignore non meta delete in the where there is text before/after the caret
  27104. if (!isMetaOrCtrl && rng.collapsed && container.nodeType == 3) {
  27105. if (isForward ? offset < container.data.length : offset > 0) {
  27106. return;
  27107. }
  27108. }
  27109. e.preventDefault();
  27110. if (isMetaOrCtrl) {
  27111. editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", e.metaKey ? "lineboundary" : "word");
  27112. }
  27113. customDelete(isForward);
  27114. }
  27115. });
  27116. // Handle case where text is deleted by typing over
  27117. editor.on('keypress', function(e) {
  27118. if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode > 31 && !VK.metaKeyPressed(e)) {
  27119. var rng, currentFormatNodes, fragmentNode, blockParent, caretNode, charText;
  27120. rng = editor.selection.getRng();
  27121. charText = String.fromCharCode(e.charCode);
  27122. e.preventDefault();
  27123. // Keep track of current format nodes
  27124. currentFormatNodes = $(rng.startContainer).parents().filter(function(idx, node) {
  27125. return !!editor.schema.getTextInlineElements()[node.nodeName];
  27126. });
  27127. customDelete(true);
  27128. // Check if the browser removed them
  27129. currentFormatNodes = currentFormatNodes.filter(function(idx, node) {
  27130. return !$.contains(editor.getBody(), node);
  27131. });
  27132. // Then re-add them
  27133. if (currentFormatNodes.length) {
  27134. fragmentNode = dom.createFragment();
  27135. currentFormatNodes.each(function(idx, formatNode) {
  27136. formatNode = formatNode.cloneNode(false);
  27137. if (fragmentNode.hasChildNodes()) {
  27138. formatNode.appendChild(fragmentNode.firstChild);
  27139. fragmentNode.appendChild(formatNode);
  27140. } else {
  27141. caretNode = formatNode;
  27142. fragmentNode.appendChild(formatNode);
  27143. }
  27144. fragmentNode.appendChild(formatNode);
  27145. });
  27146. caretNode.appendChild(editor.getDoc().createTextNode(charText));
  27147. // Prevent edge case where older WebKit would add an extra BR element
  27148. blockParent = dom.getParent(rng.startContainer, dom.isBlock);
  27149. if (dom.isEmpty(blockParent)) {
  27150. $(blockParent).empty().append(fragmentNode);
  27151. } else {
  27152. rng.insertNode(fragmentNode);
  27153. }
  27154. rng.setStart(caretNode.firstChild, 1);
  27155. rng.setEnd(caretNode.firstChild, 1);
  27156. editor.selection.setRng(rng);
  27157. } else {
  27158. editor.selection.setContent(charText);
  27159. }
  27160. }
  27161. });
  27162. editor.addCommand('Delete', function() {
  27163. customDelete();
  27164. });
  27165. editor.addCommand('ForwardDelete', function() {
  27166. customDelete(true);
  27167. });
  27168. // Older WebKits doesn't properly handle the clipboard so we can't add the rest
  27169. if (olderWebKit) {
  27170. return;
  27171. }
  27172. editor.on('dragstart', function(e) {
  27173. dragStartRng = selection.getRng();
  27174. setMceInternalContent(e);
  27175. });
  27176. editor.on('drop', function(e) {
  27177. if (!isDefaultPrevented(e)) {
  27178. var internalContent = getMceInternalContent(e);
  27179. if (internalContent) {
  27180. e.preventDefault();
  27181. // Safari has a weird issue where drag/dropping images sometimes
  27182. // produces a green plus icon. When this happens the caretRangeFromPoint
  27183. // will return "null" even though the x, y coordinate is correct.
  27184. // But if we detach the insert from the drop event we will get a proper range
  27185. Delay.setEditorTimeout(editor, function() {
  27186. var pointRng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, doc);
  27187. if (dragStartRng) {
  27188. selection.setRng(dragStartRng);
  27189. dragStartRng = null;
  27190. }
  27191. customDelete();
  27192. selection.setRng(pointRng);
  27193. insertClipboardContents(internalContent.html);
  27194. });
  27195. }
  27196. }
  27197. });
  27198. editor.on('cut', function(e) {
  27199. if (!isDefaultPrevented(e) && e.clipboardData && !editor.selection.isCollapsed()) {
  27200. e.preventDefault();
  27201. e.clipboardData.clearData();
  27202. e.clipboardData.setData('text/html', editor.selection.getContent());
  27203. e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'}));
  27204. // Needed delay for https://code.google.com/p/chromium/issues/detail?id=363288#c3
  27205. // Nested delete/forwardDelete not allowed on execCommand("cut")
  27206. // This is ugly but not sure how to work around it otherwise
  27207. Delay.setEditorTimeout(editor, function() {
  27208. customDelete(true);
  27209. });
  27210. }
  27211. });
  27212. }
  27213. /**
  27214. * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
  27215. *
  27216. * For example:
  27217. * <p><b>|</b></p>
  27218. *
  27219. * Or:
  27220. * <h1>|</h1>
  27221. *
  27222. * Or:
  27223. * [<h1></h1>]
  27224. */
  27225. function emptyEditorWhenDeleting() {
  27226. function serializeRng(rng) {
  27227. var body = dom.create("body");
  27228. var contents = rng.cloneContents();
  27229. body.appendChild(contents);
  27230. return selection.serializer.serialize(body, {format: 'html'});
  27231. }
  27232. function allContentsSelected(rng) {
  27233. if (!rng.setStart) {
  27234. if (rng.item) {
  27235. return false;
  27236. }
  27237. var bodyRng = rng.duplicate();
  27238. bodyRng.moveToElementText(editor.getBody());
  27239. return RangeUtils.compareRanges(rng, bodyRng);
  27240. }
  27241. var selection = serializeRng(rng);
  27242. var allRng = dom.createRng();
  27243. allRng.selectNode(editor.getBody());
  27244. var allSelection = serializeRng(allRng);
  27245. return selection === allSelection;
  27246. }
  27247. editor.on('keydown', function(e) {
  27248. var keyCode = e.keyCode, isCollapsed, body;
  27249. // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
  27250. if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
  27251. isCollapsed = editor.selection.isCollapsed();
  27252. body = editor.getBody();
  27253. // Selection is collapsed but the editor isn't empty
  27254. if (isCollapsed && !dom.isEmpty(body)) {
  27255. return;
  27256. }
  27257. // Selection isn't collapsed but not all the contents is selected
  27258. if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
  27259. return;
  27260. }
  27261. // Manually empty the editor
  27262. e.preventDefault();
  27263. editor.setContent('');
  27264. if (body.firstChild && dom.isBlock(body.firstChild)) {
  27265. editor.selection.setCursorLocation(body.firstChild, 0);
  27266. } else {
  27267. editor.selection.setCursorLocation(body, 0);
  27268. }
  27269. editor.nodeChanged();
  27270. }
  27271. });
  27272. }
  27273. /**
  27274. * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
  27275. * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438
  27276. * This selects the whole body so that backspace/delete logic will delete everything
  27277. */
  27278. function selectAll() {
  27279. editor.shortcuts.add('meta+a', null, 'SelectAll');
  27280. }
  27281. /**
  27282. * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
  27283. * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
  27284. *
  27285. * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
  27286. * you enter a character into the editor.
  27287. *
  27288. * It also happens when the first focus in made to the body.
  27289. *
  27290. * See: https://bugs.webkit.org/show_bug.cgi?id=83566
  27291. */
  27292. function inputMethodFocus() {
  27293. if (!editor.settings.content_editable) {
  27294. // Case 1 IME doesn't initialize if you focus the document
  27295. // Disabled since it was interferring with the cE=false logic
  27296. // Also coultn't reproduce the issue on Safari 9
  27297. /*dom.bind(editor.getDoc(), 'focusin', function() {
  27298. selection.setRng(selection.getRng());
  27299. });*/
  27300. // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
  27301. // Needs to be both down/up due to weird rendering bug on Chrome Windows
  27302. dom.bind(editor.getDoc(), 'mousedown mouseup', function(e) {
  27303. var rng;
  27304. if (e.target == editor.getDoc().documentElement) {
  27305. rng = selection.getRng();
  27306. editor.getBody().focus();
  27307. if (e.type == 'mousedown') {
  27308. if (CaretContainer.isCaretContainer(rng.startContainer)) {
  27309. return;
  27310. }
  27311. // Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret
  27312. selection.placeCaretAt(e.clientX, e.clientY);
  27313. } else {
  27314. selection.setRng(rng);
  27315. }
  27316. }
  27317. });
  27318. }
  27319. }
  27320. /**
  27321. * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
  27322. * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
  27323. * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
  27324. * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
  27325. * browsers.
  27326. *
  27327. * It also fixes a bug on Firefox where it's impossible to delete HR elements.
  27328. */
  27329. function removeHrOnBackspace() {
  27330. editor.on('keydown', function(e) {
  27331. if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
  27332. // Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow
  27333. if (!editor.getBody().getElementsByTagName('hr').length) {
  27334. return;
  27335. }
  27336. if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
  27337. var node = selection.getNode();
  27338. var previousSibling = node.previousSibling;
  27339. if (node.nodeName == 'HR') {
  27340. dom.remove(node);
  27341. e.preventDefault();
  27342. return;
  27343. }
  27344. if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
  27345. dom.remove(previousSibling);
  27346. e.preventDefault();
  27347. }
  27348. }
  27349. }
  27350. });
  27351. }
  27352. /**
  27353. * Firefox 3.x has an issue where the body element won't get proper focus if you click out
  27354. * side it's rectangle.
  27355. */
  27356. function focusBody() {
  27357. // Fix for a focus bug in FF 3.x where the body element
  27358. // wouldn't get proper focus if the user clicked on the HTML element
  27359. if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
  27360. editor.on('mousedown', function(e) {
  27361. if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
  27362. var body = editor.getBody();
  27363. // Blur the body it's focused but not correctly focused
  27364. body.blur();
  27365. // Refocus the body after a little while
  27366. Delay.setEditorTimeout(editor, function() {
  27367. body.focus();
  27368. });
  27369. }
  27370. });
  27371. }
  27372. }
  27373. /**
  27374. * WebKit has a bug where it isn't possible to select image, hr or anchor elements
  27375. * by clicking on them so we need to fake that.
  27376. */
  27377. function selectControlElements() {
  27378. editor.on('click', function(e) {
  27379. var target = e.target;
  27380. // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
  27381. // WebKit can't even do simple things like selecting an image
  27382. // Needs to be the setBaseAndExtend or it will fail to select floated images
  27383. if (/^(IMG|HR)$/.test(target.nodeName) && dom.getContentEditableParent(target) !== "false") {
  27384. e.preventDefault();
  27385. selection.getSel().setBaseAndExtent(target, 0, target, 1);
  27386. editor.nodeChanged();
  27387. }
  27388. if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) {
  27389. e.preventDefault();
  27390. selection.select(target);
  27391. }
  27392. });
  27393. }
  27394. /**
  27395. * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
  27396. *
  27397. * Fixes do backspace/delete on this:
  27398. * <p>bla[ck</p><p style="color:red">r]ed</p>
  27399. *
  27400. * Would become:
  27401. * <p>bla|ed</p>
  27402. *
  27403. * Instead of:
  27404. * <p style="color:red">bla|ed</p>
  27405. */
  27406. function removeStylesWhenDeletingAcrossBlockElements() {
  27407. function getAttributeApplyFunction() {
  27408. var template = dom.getAttribs(selection.getStart().cloneNode(false));
  27409. return function() {
  27410. var target = selection.getStart();
  27411. if (target !== editor.getBody()) {
  27412. dom.setAttrib(target, "style", null);
  27413. each(template, function(attr) {
  27414. target.setAttributeNode(attr.cloneNode(true));
  27415. });
  27416. }
  27417. };
  27418. }
  27419. function isSelectionAcrossElements() {
  27420. return !selection.isCollapsed() &&
  27421. dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
  27422. }
  27423. editor.on('keypress', function(e) {
  27424. var applyAttributes;
  27425. if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
  27426. applyAttributes = getAttributeApplyFunction();
  27427. editor.getDoc().execCommand('delete', false, null);
  27428. applyAttributes();
  27429. e.preventDefault();
  27430. return false;
  27431. }
  27432. });
  27433. dom.bind(editor.getDoc(), 'cut', function(e) {
  27434. var applyAttributes;
  27435. if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
  27436. applyAttributes = getAttributeApplyFunction();
  27437. Delay.setEditorTimeout(editor, function() {
  27438. applyAttributes();
  27439. });
  27440. }
  27441. });
  27442. }
  27443. /**
  27444. * Screen readers on IE needs to have the role application set on the body.
  27445. */
  27446. function ensureBodyHasRoleApplication() {
  27447. document.body.setAttribute("role", "application");
  27448. }
  27449. /**
  27450. * Backspacing into a table behaves differently depending upon browser type.
  27451. * Therefore, disable Backspace when cursor immediately follows a table.
  27452. */
  27453. function disableBackspaceIntoATable() {
  27454. editor.on('keydown', function(e) {
  27455. if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
  27456. if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
  27457. var previousSibling = selection.getNode().previousSibling;
  27458. if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
  27459. e.preventDefault();
  27460. return false;
  27461. }
  27462. }
  27463. }
  27464. });
  27465. }
  27466. /**
  27467. * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this
  27468. * logic adds a \n before the BR so that it will get rendered.
  27469. */
  27470. function addNewLinesBeforeBrInPre() {
  27471. // IE8+ rendering mode does the right thing with BR in PRE
  27472. if (getDocumentMode() > 7) {
  27473. return;
  27474. }
  27475. // Enable display: none in area and add a specific class that hides all BR elements in PRE to
  27476. // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
  27477. setEditorCommandState('RespectVisibilityInDesign', true);
  27478. editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
  27479. dom.addClass(editor.getBody(), 'mceHideBrInPre');
  27480. // Adds a \n before all BR elements in PRE to get them visual
  27481. parser.addNodeFilter('pre', function(nodes) {
  27482. var i = nodes.length, brNodes, j, brElm, sibling;
  27483. while (i--) {
  27484. brNodes = nodes[i].getAll('br');
  27485. j = brNodes.length;
  27486. while (j--) {
  27487. brElm = brNodes[j];
  27488. // Add \n before BR in PRE elements on older IE:s so the new lines get rendered
  27489. sibling = brElm.prev;
  27490. if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
  27491. sibling.value += '\n';
  27492. } else {
  27493. brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n';
  27494. }
  27495. }
  27496. }
  27497. });
  27498. // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
  27499. serializer.addNodeFilter('pre', function(nodes) {
  27500. var i = nodes.length, brNodes, j, brElm, sibling;
  27501. while (i--) {
  27502. brNodes = nodes[i].getAll('br');
  27503. j = brNodes.length;
  27504. while (j--) {
  27505. brElm = brNodes[j];
  27506. sibling = brElm.prev;
  27507. if (sibling && sibling.type == 3) {
  27508. sibling.value = sibling.value.replace(/\r?\n$/, '');
  27509. }
  27510. }
  27511. }
  27512. });
  27513. }
  27514. /**
  27515. * Moves style width/height to attribute width/height when the user resizes an image on IE.
  27516. */
  27517. function removePreSerializedStylesWhenSelectingControls() {
  27518. dom.bind(editor.getBody(), 'mouseup', function() {
  27519. var value, node = selection.getNode();
  27520. // Moved styles to attributes on IMG eements
  27521. if (node.nodeName == 'IMG') {
  27522. // Convert style width to width attribute
  27523. if ((value = dom.getStyle(node, 'width'))) {
  27524. dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
  27525. dom.setStyle(node, 'width', '');
  27526. }
  27527. // Convert style height to height attribute
  27528. if ((value = dom.getStyle(node, 'height'))) {
  27529. dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
  27530. dom.setStyle(node, 'height', '');
  27531. }
  27532. }
  27533. });
  27534. }
  27535. /**
  27536. * Removes a blockquote when backspace is pressed at the beginning of it.
  27537. *
  27538. * For example:
  27539. * <blockquote><p>|x</p></blockquote>
  27540. *
  27541. * Becomes:
  27542. * <p>|x</p>
  27543. */
  27544. function removeBlockQuoteOnBackSpace() {
  27545. // Add block quote deletion handler
  27546. editor.on('keydown', function(e) {
  27547. var rng, container, offset, root, parent;
  27548. if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
  27549. return;
  27550. }
  27551. rng = selection.getRng();
  27552. container = rng.startContainer;
  27553. offset = rng.startOffset;
  27554. root = dom.getRoot();
  27555. parent = container;
  27556. if (!rng.collapsed || offset !== 0) {
  27557. return;
  27558. }
  27559. while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
  27560. parent = parent.parentNode;
  27561. }
  27562. // Is the cursor at the beginning of a blockquote?
  27563. if (parent.tagName === 'BLOCKQUOTE') {
  27564. // Remove the blockquote
  27565. editor.formatter.toggle('blockquote', null, parent);
  27566. // Move the caret to the beginning of container
  27567. rng = dom.createRng();
  27568. rng.setStart(container, 0);
  27569. rng.setEnd(container, 0);
  27570. selection.setRng(rng);
  27571. }
  27572. });
  27573. }
  27574. /**
  27575. * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
  27576. */
  27577. function setGeckoEditingOptions() {
  27578. function setOpts() {
  27579. refreshContentEditable();
  27580. setEditorCommandState("StyleWithCSS", false);
  27581. setEditorCommandState("enableInlineTableEditing", false);
  27582. if (!settings.object_resizing) {
  27583. setEditorCommandState("enableObjectResizing", false);
  27584. }
  27585. }
  27586. if (!settings.readonly) {
  27587. editor.on('BeforeExecCommand MouseDown', setOpts);
  27588. }
  27589. }
  27590. /**
  27591. * Fixes a gecko link bug, when a link is placed at the end of block elements there is
  27592. * no way to move the caret behind the link. This fix adds a bogus br element after the link.
  27593. *
  27594. * For example this:
  27595. * <p><b><a href="#">x</a></b></p>
  27596. *
  27597. * Becomes this:
  27598. * <p><b><a href="#">x</a></b><br></p>
  27599. */
  27600. function addBrAfterLastLinks() {
  27601. function fixLinks() {
  27602. each(dom.select('a'), function(node) {
  27603. var parentNode = node.parentNode, root = dom.getRoot();
  27604. if (parentNode.lastChild === node) {
  27605. while (parentNode && !dom.isBlock(parentNode)) {
  27606. if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
  27607. return;
  27608. }
  27609. parentNode = parentNode.parentNode;
  27610. }
  27611. dom.add(parentNode, 'br', {'data-mce-bogus': 1});
  27612. }
  27613. });
  27614. }
  27615. editor.on('SetContent ExecCommand', function(e) {
  27616. if (e.type == "setcontent" || e.command === 'mceInsertLink') {
  27617. fixLinks();
  27618. }
  27619. });
  27620. }
  27621. /**
  27622. * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by
  27623. * default we want to change that behavior.
  27624. */
  27625. function setDefaultBlockType() {
  27626. if (settings.forced_root_block) {
  27627. editor.on('init', function() {
  27628. setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
  27629. });
  27630. }
  27631. }
  27632. /**
  27633. * Deletes the selected image on IE instead of navigating to previous page.
  27634. */
  27635. function deleteControlItemOnBackSpace() {
  27636. editor.on('keydown', function(e) {
  27637. var rng;
  27638. if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
  27639. rng = editor.getDoc().selection.createRange();
  27640. if (rng && rng.item) {
  27641. e.preventDefault();
  27642. editor.undoManager.beforeChange();
  27643. dom.remove(rng.item(0));
  27644. editor.undoManager.add();
  27645. }
  27646. }
  27647. });
  27648. }
  27649. /**
  27650. * IE10 doesn't properly render block elements with the right height until you add contents to them.
  27651. * This fixes that by adding a padding-right to all empty text block elements.
  27652. * See: https://connect.microsoft.com/IE/feedback/details/743881
  27653. */
  27654. function renderEmptyBlocksFix() {
  27655. var emptyBlocksCSS;
  27656. // IE10+
  27657. if (getDocumentMode() >= 10) {
  27658. emptyBlocksCSS = '';
  27659. each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
  27660. emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
  27661. });
  27662. editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
  27663. }
  27664. }
  27665. /**
  27666. * Old IE versions can't retain contents within noscript elements so this logic will store the contents
  27667. * as a attribute and the insert that value as it's raw text when the DOM is serialized.
  27668. */
  27669. function keepNoScriptContents() {
  27670. if (getDocumentMode() < 9) {
  27671. parser.addNodeFilter('noscript', function(nodes) {
  27672. var i = nodes.length, node, textNode;
  27673. while (i--) {
  27674. node = nodes[i];
  27675. textNode = node.firstChild;
  27676. if (textNode) {
  27677. node.attr('data-mce-innertext', textNode.value);
  27678. }
  27679. }
  27680. });
  27681. serializer.addNodeFilter('noscript', function(nodes) {
  27682. var i = nodes.length, node, textNode, value;
  27683. while (i--) {
  27684. node = nodes[i];
  27685. textNode = nodes[i].firstChild;
  27686. if (textNode) {
  27687. textNode.value = Entities.decode(textNode.value);
  27688. } else {
  27689. // Old IE can't retain noscript value so an attribute is used to store it
  27690. value = node.attributes.map['data-mce-innertext'];
  27691. if (value) {
  27692. node.attr('data-mce-innertext', null);
  27693. textNode = new Node('#text', 3);
  27694. textNode.value = value;
  27695. textNode.raw = true;
  27696. node.append(textNode);
  27697. }
  27698. }
  27699. }
  27700. });
  27701. }
  27702. }
  27703. /**
  27704. * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode.
  27705. */
  27706. function fixCaretSelectionOfDocumentElementOnIe() {
  27707. var doc = dom.doc, body = doc.body, started, startRng, htmlElm;
  27708. // Return range from point or null if it failed
  27709. function rngFromPoint(x, y) {
  27710. var rng = body.createTextRange();
  27711. try {
  27712. rng.moveToPoint(x, y);
  27713. } catch (ex) {
  27714. // IE sometimes throws and exception, so lets just ignore it
  27715. rng = null;
  27716. }
  27717. return rng;
  27718. }
  27719. // Fires while the selection is changing
  27720. function selectionChange(e) {
  27721. var pointRng;
  27722. // Check if the button is down or not
  27723. if (e.button) {
  27724. // Create range from mouse position
  27725. pointRng = rngFromPoint(e.x, e.y);
  27726. if (pointRng) {
  27727. // Check if pointRange is before/after selection then change the endPoint
  27728. if (pointRng.compareEndPoints('StartToStart', startRng) > 0) {
  27729. pointRng.setEndPoint('StartToStart', startRng);
  27730. } else {
  27731. pointRng.setEndPoint('EndToEnd', startRng);
  27732. }
  27733. pointRng.select();
  27734. }
  27735. } else {
  27736. endSelection();
  27737. }
  27738. }
  27739. // Removes listeners
  27740. function endSelection() {
  27741. var rng = doc.selection.createRange();
  27742. // If the range is collapsed then use the last start range
  27743. if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) {
  27744. startRng.select();
  27745. }
  27746. dom.unbind(doc, 'mouseup', endSelection);
  27747. dom.unbind(doc, 'mousemove', selectionChange);
  27748. startRng = started = 0;
  27749. }
  27750. // Make HTML element unselectable since we are going to handle selection by hand
  27751. doc.documentElement.unselectable = true;
  27752. // Detect when user selects outside BODY
  27753. dom.bind(doc, 'mousedown contextmenu', function(e) {
  27754. if (e.target.nodeName === 'HTML') {
  27755. if (started) {
  27756. endSelection();
  27757. }
  27758. // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
  27759. htmlElm = doc.documentElement;
  27760. if (htmlElm.scrollHeight > htmlElm.clientHeight) {
  27761. return;
  27762. }
  27763. started = 1;
  27764. // Setup start position
  27765. startRng = rngFromPoint(e.x, e.y);
  27766. if (startRng) {
  27767. // Listen for selection change events
  27768. dom.bind(doc, 'mouseup', endSelection);
  27769. dom.bind(doc, 'mousemove', selectionChange);
  27770. dom.getRoot().focus();
  27771. startRng.select();
  27772. }
  27773. }
  27774. });
  27775. }
  27776. /**
  27777. * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
  27778. * this fix will lean the caret right into the closest inline element.
  27779. */
  27780. function normalizeSelection() {
  27781. // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
  27782. editor.on('keyup focusin mouseup', function(e) {
  27783. if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
  27784. selection.normalize();
  27785. }
  27786. }, true);
  27787. }
  27788. /**
  27789. * Forces Gecko to render a broken image icon if it fails to load an image.
  27790. */
  27791. function showBrokenImageIcon() {
  27792. editor.contentStyles.push(
  27793. 'img:-moz-broken {' +
  27794. '-moz-force-broken-image-icon:1;' +
  27795. 'min-width:24px;' +
  27796. 'min-height:24px' +
  27797. '}'
  27798. );
  27799. }
  27800. /**
  27801. * iOS has a bug where it's impossible to type if the document has a touchstart event
  27802. * bound and the user touches the document while having the on screen keyboard visible.
  27803. *
  27804. * The touch event moves the focus to the parent document while having the caret inside the iframe
  27805. * this fix moves the focus back into the iframe document.
  27806. */
  27807. function restoreFocusOnKeyDown() {
  27808. if (!editor.inline) {
  27809. editor.on('keydown', function() {
  27810. if (document.activeElement == document.body) {
  27811. editor.getWin().focus();
  27812. }
  27813. });
  27814. }
  27815. }
  27816. /**
  27817. * IE 11 has an annoying issue where you can't move focus into the editor
  27818. * by clicking on the white area HTML element. We used to be able to to fix this with
  27819. * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection
  27820. * object it's not possible anymore. So we need to hack in a ungly CSS to force the
  27821. * body to be at least 150px. If the user clicks the HTML element out side this 150px region
  27822. * we simply move the focus into the first paragraph. Not ideal since you loose the
  27823. * positioning of the caret but goot enough for most cases.
  27824. */
  27825. function bodyHeight() {
  27826. if (!editor.inline) {
  27827. editor.contentStyles.push('body {min-height: 150px}');
  27828. editor.on('click', function(e) {
  27829. var rng;
  27830. if (e.target.nodeName == 'HTML') {
  27831. // Edge seems to only need focus if we set the range
  27832. // the caret will become invisible and moved out of the iframe!!
  27833. if (Env.ie > 11) {
  27834. editor.getBody().focus();
  27835. return;
  27836. }
  27837. // Need to store away non collapsed ranges since the focus call will mess that up see #7382
  27838. rng = editor.selection.getRng();
  27839. editor.getBody().focus();
  27840. editor.selection.setRng(rng);
  27841. editor.selection.normalize();
  27842. editor.nodeChanged();
  27843. }
  27844. });
  27845. }
  27846. }
  27847. /**
  27848. * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow.
  27849. * You might then loose all your work so we need to block that behavior and replace it with our own.
  27850. */
  27851. function blockCmdArrowNavigation() {
  27852. if (Env.mac) {
  27853. editor.on('keydown', function(e) {
  27854. if (VK.metaKeyPressed(e) && !e.shiftKey && (e.keyCode == 37 || e.keyCode == 39)) {
  27855. e.preventDefault();
  27856. editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'lineboundary');
  27857. }
  27858. });
  27859. }
  27860. }
  27861. /**
  27862. * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin.
  27863. */
  27864. function disableAutoUrlDetect() {
  27865. setEditorCommandState("AutoUrlDetect", false);
  27866. }
  27867. /**
  27868. * iOS 7.1 introduced two new bugs:
  27869. * 1) It's possible to open links within a contentEditable area by clicking on them.
  27870. * 2) If you hold down the finger it will display the link/image touch callout menu.
  27871. */
  27872. function tapLinksAndImages() {
  27873. editor.on('click', function(e) {
  27874. var elm = e.target;
  27875. do {
  27876. if (elm.tagName === 'A') {
  27877. e.preventDefault();
  27878. return;
  27879. }
  27880. } while ((elm = elm.parentNode));
  27881. });
  27882. editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
  27883. }
  27884. /**
  27885. * iOS Safari and possible other browsers have a bug where it won't fire
  27886. * a click event when a contentEditable is focused. This function fakes click events
  27887. * by using touchstart/touchend and measuring the time and distance travelled.
  27888. */
  27889. /*
  27890. function touchClickEvent() {
  27891. editor.on('touchstart', function(e) {
  27892. var elm, time, startTouch, changedTouches;
  27893. elm = e.target;
  27894. time = new Date().getTime();
  27895. changedTouches = e.changedTouches;
  27896. if (!changedTouches || changedTouches.length > 1) {
  27897. return;
  27898. }
  27899. startTouch = changedTouches[0];
  27900. editor.once('touchend', function(e) {
  27901. var endTouch = e.changedTouches[0], args;
  27902. if (new Date().getTime() - time > 500) {
  27903. return;
  27904. }
  27905. if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) {
  27906. return;
  27907. }
  27908. if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) {
  27909. return;
  27910. }
  27911. args = {
  27912. target: elm
  27913. };
  27914. each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) {
  27915. args[key] = endTouch[key];
  27916. });
  27917. args = editor.fire('click', args);
  27918. if (!args.isDefaultPrevented()) {
  27919. // iOS WebKit can't place the caret properly once
  27920. // you bind touch events so we need to do this manually
  27921. // TODO: Expand to the closest word? Touble tap still works.
  27922. editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY);
  27923. editor.nodeChanged();
  27924. }
  27925. });
  27926. });
  27927. }
  27928. */
  27929. /**
  27930. * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element.
  27931. * For example this: <form><button></form>
  27932. */
  27933. function blockFormSubmitInsideEditor() {
  27934. editor.on('init', function() {
  27935. editor.dom.bind(editor.getBody(), 'submit', function(e) {
  27936. e.preventDefault();
  27937. });
  27938. });
  27939. }
  27940. /**
  27941. * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class.
  27942. *
  27943. * Scenario:
  27944. * 1) Create a table 2x2.
  27945. * 2) Select and copy cells A2-B2.
  27946. * 3) Paste and it will add BR element to table cell.
  27947. */
  27948. function removeAppleInterchangeBrs() {
  27949. parser.addNodeFilter('br', function(nodes) {
  27950. var i = nodes.length;
  27951. while (i--) {
  27952. if (nodes[i].attr('class') == 'Apple-interchange-newline') {
  27953. nodes[i].remove();
  27954. }
  27955. }
  27956. });
  27957. }
  27958. /**
  27959. * IE cannot set custom contentType's on drag events, and also does not properly drag/drop between
  27960. * editors. This uses a special data:text/mce-internal URL to pass data when drag/drop between editors.
  27961. */
  27962. function ieInternalDragAndDrop() {
  27963. editor.on('dragstart', function(e) {
  27964. setMceInternalContent(e);
  27965. });
  27966. editor.on('drop', function(e) {
  27967. if (!isDefaultPrevented(e)) {
  27968. var internalContent = getMceInternalContent(e);
  27969. if (internalContent && internalContent.id != editor.id) {
  27970. e.preventDefault();
  27971. var rng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, editor.getDoc());
  27972. selection.setRng(rng);
  27973. insertClipboardContents(internalContent.html);
  27974. }
  27975. }
  27976. });
  27977. }
  27978. function refreshContentEditable() {
  27979. var body, parent;
  27980. // Check if the editor was hidden and the re-initialize contentEditable mode by removing and adding the body again
  27981. if (isHidden()) {
  27982. body = editor.getBody();
  27983. parent = body.parentNode;
  27984. parent.removeChild(body);
  27985. parent.appendChild(body);
  27986. body.focus();
  27987. }
  27988. }
  27989. function isHidden() {
  27990. var sel;
  27991. if (!isGecko) {
  27992. return 0;
  27993. }
  27994. // Weird, wheres that cursor selection?
  27995. sel = editor.selection.getSel();
  27996. return (!sel || !sel.rangeCount || sel.rangeCount === 0);
  27997. }
  27998. /**
  27999. * Properly empties the editor if all contents is selected and deleted this to
  28000. * prevent empty paragraphs from being produced at beginning/end of contents.
  28001. */
  28002. function emptyEditorOnDeleteEverything() {
  28003. function isEverythingSelected(editor) {
  28004. var caretWalker = new CaretWalker(editor.getBody());
  28005. var rng = editor.selection.getRng();
  28006. var startCaretPos = CaretPosition.fromRangeStart(rng);
  28007. var endCaretPos = CaretPosition.fromRangeEnd(rng);
  28008. return !editor.selection.isCollapsed() && !caretWalker.prev(startCaretPos) && !caretWalker.next(endCaretPos);
  28009. }
  28010. // Type over case delete and insert this won't cover typeover with a IME but at least it covers the common case
  28011. editor.on('keypress', function (e) {
  28012. if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode > 31 && !VK.metaKeyPressed(e)) {
  28013. if (isEverythingSelected(editor)) {
  28014. e.preventDefault();
  28015. editor.setContent(String.fromCharCode(e.charCode));
  28016. editor.selection.select(editor.getBody(), true);
  28017. editor.selection.collapse(false);
  28018. editor.nodeChanged();
  28019. }
  28020. }
  28021. });
  28022. editor.on('keydown', function (e) {
  28023. var keyCode = e.keyCode;
  28024. if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
  28025. if (isEverythingSelected(editor)) {
  28026. e.preventDefault();
  28027. editor.setContent('');
  28028. editor.nodeChanged();
  28029. }
  28030. }
  28031. });
  28032. }
  28033. // All browsers
  28034. removeBlockQuoteOnBackSpace();
  28035. emptyEditorWhenDeleting();
  28036. // Windows phone will return a range like [body, 0] on mousedown so
  28037. // it will always normalize to the wrong location
  28038. if (!Env.windowsPhone) {
  28039. normalizeSelection();
  28040. }
  28041. // WebKit
  28042. if (isWebKit) {
  28043. emptyEditorOnDeleteEverything();
  28044. cleanupStylesWhenDeleting();
  28045. inputMethodFocus();
  28046. selectControlElements();
  28047. setDefaultBlockType();
  28048. blockFormSubmitInsideEditor();
  28049. disableBackspaceIntoATable();
  28050. removeAppleInterchangeBrs();
  28051. //touchClickEvent();
  28052. // iOS
  28053. if (Env.iOS) {
  28054. restoreFocusOnKeyDown();
  28055. bodyHeight();
  28056. tapLinksAndImages();
  28057. } else {
  28058. selectAll();
  28059. }
  28060. }
  28061. // IE
  28062. if (isIE && Env.ie < 11) {
  28063. removeHrOnBackspace();
  28064. ensureBodyHasRoleApplication();
  28065. addNewLinesBeforeBrInPre();
  28066. removePreSerializedStylesWhenSelectingControls();
  28067. deleteControlItemOnBackSpace();
  28068. renderEmptyBlocksFix();
  28069. keepNoScriptContents();
  28070. fixCaretSelectionOfDocumentElementOnIe();
  28071. }
  28072. if (Env.ie >= 11) {
  28073. bodyHeight();
  28074. disableBackspaceIntoATable();
  28075. }
  28076. if (Env.ie) {
  28077. selectAll();
  28078. disableAutoUrlDetect();
  28079. ieInternalDragAndDrop();
  28080. }
  28081. // Gecko
  28082. if (isGecko) {
  28083. emptyEditorOnDeleteEverything();
  28084. removeHrOnBackspace();
  28085. focusBody();
  28086. removeStylesWhenDeletingAcrossBlockElements();
  28087. setGeckoEditingOptions();
  28088. addBrAfterLastLinks();
  28089. showBrokenImageIcon();
  28090. blockCmdArrowNavigation();
  28091. disableBackspaceIntoATable();
  28092. }
  28093. return {
  28094. refreshContentEditable: refreshContentEditable,
  28095. isHidden: isHidden
  28096. };
  28097. };
  28098. });
  28099. // Included from: js/tinymce/classes/EditorObservable.js
  28100. /**
  28101. * EditorObservable.js
  28102. *
  28103. * Released under LGPL License.
  28104. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  28105. *
  28106. * License: http://www.tinymce.com/license
  28107. * Contributing: http://www.tinymce.com/contributing
  28108. */
  28109. /**
  28110. * This mixin contains the event logic for the tinymce.Editor class.
  28111. *
  28112. * @mixin tinymce.EditorObservable
  28113. * @extends tinymce.util.Observable
  28114. */
  28115. define("tinymce/EditorObservable", [
  28116. "tinymce/util/Observable",
  28117. "tinymce/dom/DOMUtils",
  28118. "tinymce/util/Tools"
  28119. ], function(Observable, DOMUtils, Tools) {
  28120. var DOM = DOMUtils.DOM, customEventRootDelegates;
  28121. /**
  28122. * Returns the event target so for the specified event. Some events fire
  28123. * only on document, some fire on documentElement etc. This also handles the
  28124. * custom event root setting where it returns that element instead of the body.
  28125. *
  28126. * @private
  28127. * @param {tinymce.Editor} editor Editor instance to get event target from.
  28128. * @param {String} eventName Name of the event for example "click".
  28129. * @return {Element/Document} HTML Element or document target to bind on.
  28130. */
  28131. function getEventTarget(editor, eventName) {
  28132. if (eventName == 'selectionchange') {
  28133. return editor.getDoc();
  28134. }
  28135. // Need to bind mousedown/mouseup etc to document not body in iframe mode
  28136. // Since the user might click on the HTML element not the BODY
  28137. if (!editor.inline && /^mouse|touch|click|contextmenu|drop|dragover|dragend/.test(eventName)) {
  28138. return editor.getDoc().documentElement;
  28139. }
  28140. // Bind to event root instead of body if it's defined
  28141. if (editor.settings.event_root) {
  28142. if (!editor.eventRoot) {
  28143. editor.eventRoot = DOM.select(editor.settings.event_root)[0];
  28144. }
  28145. return editor.eventRoot;
  28146. }
  28147. return editor.getBody();
  28148. }
  28149. /**
  28150. * Binds a event delegate for the specified name this delegate will fire
  28151. * the event to the editor dispatcher.
  28152. *
  28153. * @private
  28154. * @param {tinymce.Editor} editor Editor instance to get event target from.
  28155. * @param {String} eventName Name of the event for example "click".
  28156. */
  28157. function bindEventDelegate(editor, eventName) {
  28158. var eventRootElm = getEventTarget(editor, eventName), delegate;
  28159. function isListening(editor) {
  28160. return !editor.hidden && !editor.readonly;
  28161. }
  28162. if (!editor.delegates) {
  28163. editor.delegates = {};
  28164. }
  28165. if (editor.delegates[eventName]) {
  28166. return;
  28167. }
  28168. if (editor.settings.event_root) {
  28169. if (!customEventRootDelegates) {
  28170. customEventRootDelegates = {};
  28171. editor.editorManager.on('removeEditor', function() {
  28172. var name;
  28173. if (!editor.editorManager.activeEditor) {
  28174. if (customEventRootDelegates) {
  28175. for (name in customEventRootDelegates) {
  28176. editor.dom.unbind(getEventTarget(editor, name));
  28177. }
  28178. customEventRootDelegates = null;
  28179. }
  28180. }
  28181. });
  28182. }
  28183. if (customEventRootDelegates[eventName]) {
  28184. return;
  28185. }
  28186. delegate = function(e) {
  28187. var target = e.target, editors = editor.editorManager.editors, i = editors.length;
  28188. while (i--) {
  28189. var body = editors[i].getBody();
  28190. if (body === target || DOM.isChildOf(target, body)) {
  28191. if (isListening(editors[i])) {
  28192. editors[i].fire(eventName, e);
  28193. }
  28194. }
  28195. }
  28196. };
  28197. customEventRootDelegates[eventName] = delegate;
  28198. DOM.bind(eventRootElm, eventName, delegate);
  28199. } else {
  28200. delegate = function(e) {
  28201. if (isListening(editor)) {
  28202. editor.fire(eventName, e);
  28203. }
  28204. };
  28205. DOM.bind(eventRootElm, eventName, delegate);
  28206. editor.delegates[eventName] = delegate;
  28207. }
  28208. }
  28209. var EditorObservable = {
  28210. /**
  28211. * Bind any pending event delegates. This gets executed after the target body/document is created.
  28212. *
  28213. * @private
  28214. */
  28215. bindPendingEventDelegates: function() {
  28216. var self = this;
  28217. Tools.each(self._pendingNativeEvents, function(name) {
  28218. bindEventDelegate(self, name);
  28219. });
  28220. },
  28221. /**
  28222. * Toggles a native event on/off this is called by the EventDispatcher when
  28223. * the first native event handler is added and when the last native event handler is removed.
  28224. *
  28225. * @private
  28226. */
  28227. toggleNativeEvent: function(name, state) {
  28228. var self = this;
  28229. // Never bind focus/blur since the FocusManager fakes those
  28230. if (name == "focus" || name == "blur") {
  28231. return;
  28232. }
  28233. if (state) {
  28234. if (self.initialized) {
  28235. bindEventDelegate(self, name);
  28236. } else {
  28237. if (!self._pendingNativeEvents) {
  28238. self._pendingNativeEvents = [name];
  28239. } else {
  28240. self._pendingNativeEvents.push(name);
  28241. }
  28242. }
  28243. } else if (self.initialized) {
  28244. self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
  28245. delete self.delegates[name];
  28246. }
  28247. },
  28248. /**
  28249. * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
  28250. *
  28251. * @private
  28252. */
  28253. unbindAllNativeEvents: function() {
  28254. var self = this, name;
  28255. if (self.delegates) {
  28256. for (name in self.delegates) {
  28257. self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
  28258. }
  28259. delete self.delegates;
  28260. }
  28261. if (!self.inline) {
  28262. self.getBody().onload = null;
  28263. self.dom.unbind(self.getWin());
  28264. self.dom.unbind(self.getDoc());
  28265. }
  28266. self.dom.unbind(self.getBody());
  28267. self.dom.unbind(self.getContainer());
  28268. }
  28269. };
  28270. EditorObservable = Tools.extend({}, Observable, EditorObservable);
  28271. return EditorObservable;
  28272. });
  28273. // Included from: js/tinymce/classes/Mode.js
  28274. /**
  28275. * Mode.js
  28276. *
  28277. * Released under LGPL License.
  28278. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  28279. *
  28280. * License: http://www.tinymce.com/license
  28281. * Contributing: http://www.tinymce.com/contributing
  28282. */
  28283. /**
  28284. * Mode switcher logic.
  28285. *
  28286. * @private
  28287. * @class tinymce.Mode
  28288. */
  28289. define("tinymce/Mode", [], function() {
  28290. function setEditorCommandState(editor, cmd, state) {
  28291. try {
  28292. editor.getDoc().execCommand(cmd, false, state);
  28293. } catch (ex) {
  28294. // Ignore
  28295. }
  28296. }
  28297. function clickBlocker(editor) {
  28298. var target, handler;
  28299. target = editor.getBody();
  28300. handler = function(e) {
  28301. if (editor.dom.getParents(e.target, 'a').length > 0) {
  28302. e.preventDefault();
  28303. }
  28304. };
  28305. editor.dom.bind(target, 'click', handler);
  28306. return {
  28307. unbind: function() {
  28308. editor.dom.unbind(target, 'click', handler);
  28309. }
  28310. };
  28311. }
  28312. function toggleReadOnly(editor, state) {
  28313. if (editor._clickBlocker) {
  28314. editor._clickBlocker.unbind();
  28315. editor._clickBlocker = null;
  28316. }
  28317. if (state) {
  28318. editor._clickBlocker = clickBlocker(editor);
  28319. editor.selection.controlSelection.hideResizeRect();
  28320. editor.readonly = true;
  28321. editor.getBody().contentEditable = false;
  28322. } else {
  28323. editor.readonly = false;
  28324. editor.getBody().contentEditable = true;
  28325. setEditorCommandState(editor, "StyleWithCSS", false);
  28326. setEditorCommandState(editor, "enableInlineTableEditing", false);
  28327. setEditorCommandState(editor, "enableObjectResizing", false);
  28328. editor.focus();
  28329. editor.nodeChanged();
  28330. }
  28331. }
  28332. function setMode(editor, mode) {
  28333. var currentMode = editor.readonly ? 'readonly' : 'design';
  28334. if (mode == currentMode) {
  28335. return;
  28336. }
  28337. if (editor.initialized) {
  28338. toggleReadOnly(editor, mode == 'readonly');
  28339. } else {
  28340. editor.on('init', function() {
  28341. toggleReadOnly(editor, mode == 'readonly');
  28342. });
  28343. }
  28344. // Event is NOT preventable
  28345. editor.fire('SwitchMode', {mode: mode});
  28346. }
  28347. return {
  28348. setMode: setMode
  28349. };
  28350. });
  28351. // Included from: js/tinymce/classes/Shortcuts.js
  28352. /**
  28353. * Shortcuts.js
  28354. *
  28355. * Released under LGPL License.
  28356. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  28357. *
  28358. * License: http://www.tinymce.com/license
  28359. * Contributing: http://www.tinymce.com/contributing
  28360. */
  28361. /**
  28362. * Contains all logic for handling of keyboard shortcuts.
  28363. *
  28364. * @class tinymce.Shortcuts
  28365. * @example
  28366. * editor.shortcuts.add('ctrl+a', function() {});
  28367. * editor.shortcuts.add('meta+a', function() {}); // "meta" maps to Command on Mac and Ctrl on PC
  28368. * editor.shortcuts.add('ctrl+alt+a', function() {});
  28369. * editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC
  28370. */
  28371. define("tinymce/Shortcuts", [
  28372. "tinymce/util/Tools",
  28373. "tinymce/Env"
  28374. ], function(Tools, Env) {
  28375. var each = Tools.each, explode = Tools.explode;
  28376. var keyCodeLookup = {
  28377. "f9": 120,
  28378. "f10": 121,
  28379. "f11": 122
  28380. };
  28381. var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access');
  28382. return function(editor) {
  28383. var self = this, shortcuts = {}, pendingPatterns = [];
  28384. function parseShortcut(pattern) {
  28385. var id, key, shortcut = {};
  28386. // Parse modifiers and keys ctrl+alt+b for example
  28387. each(explode(pattern, '+'), function(value) {
  28388. if (value in modifierNames) {
  28389. shortcut[value] = true;
  28390. } else {
  28391. // Allow numeric keycodes like ctrl+219 for ctrl+[
  28392. if (/^[0-9]{2,}$/.test(value)) {
  28393. shortcut.keyCode = parseInt(value, 10);
  28394. } else {
  28395. shortcut.charCode = value.charCodeAt(0);
  28396. shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
  28397. }
  28398. }
  28399. });
  28400. // Generate unique id for modifier combination and set default state for unused modifiers
  28401. id = [shortcut.keyCode];
  28402. for (key in modifierNames) {
  28403. if (shortcut[key]) {
  28404. id.push(key);
  28405. } else {
  28406. shortcut[key] = false;
  28407. }
  28408. }
  28409. shortcut.id = id.join(',');
  28410. // Handle special access modifier differently depending on Mac/Win
  28411. if (shortcut.access) {
  28412. shortcut.alt = true;
  28413. if (Env.mac) {
  28414. shortcut.ctrl = true;
  28415. } else {
  28416. shortcut.shift = true;
  28417. }
  28418. }
  28419. // Handle special meta modifier differently depending on Mac/Win
  28420. if (shortcut.meta) {
  28421. if (Env.mac) {
  28422. shortcut.meta = true;
  28423. } else {
  28424. shortcut.ctrl = true;
  28425. shortcut.meta = false;
  28426. }
  28427. }
  28428. return shortcut;
  28429. }
  28430. function createShortcut(pattern, desc, cmdFunc, scope) {
  28431. var shortcuts;
  28432. shortcuts = Tools.map(explode(pattern, '>'), parseShortcut);
  28433. shortcuts[shortcuts.length - 1] = Tools.extend(shortcuts[shortcuts.length - 1], {
  28434. func: cmdFunc,
  28435. scope: scope || editor
  28436. });
  28437. return Tools.extend(shortcuts[0], {
  28438. desc: editor.translate(desc),
  28439. subpatterns: shortcuts.slice(1)
  28440. });
  28441. }
  28442. function hasModifier(e) {
  28443. return e.altKey || e.ctrlKey || e.metaKey;
  28444. }
  28445. function isFunctionKey(e) {
  28446. return e.keyCode >= 112 && e.keyCode <= 123;
  28447. }
  28448. function matchShortcut(e, shortcut) {
  28449. if (!shortcut) {
  28450. return false;
  28451. }
  28452. if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) {
  28453. return false;
  28454. }
  28455. if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
  28456. return false;
  28457. }
  28458. if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
  28459. e.preventDefault();
  28460. return true;
  28461. }
  28462. return false;
  28463. }
  28464. function executeShortcutAction(shortcut) {
  28465. return shortcut.func ? shortcut.func.call(shortcut.scope) : null;
  28466. }
  28467. editor.on('keyup keypress keydown', function(e) {
  28468. if ((hasModifier(e) || isFunctionKey(e)) && !e.isDefaultPrevented()) {
  28469. each(shortcuts, function(shortcut) {
  28470. if (matchShortcut(e, shortcut)) {
  28471. pendingPatterns = shortcut.subpatterns.slice(0);
  28472. if (e.type == "keydown") {
  28473. executeShortcutAction(shortcut);
  28474. }
  28475. return true;
  28476. }
  28477. });
  28478. if (matchShortcut(e, pendingPatterns[0])) {
  28479. if (pendingPatterns.length === 1) {
  28480. if (e.type == "keydown") {
  28481. executeShortcutAction(pendingPatterns[0]);
  28482. }
  28483. }
  28484. pendingPatterns.shift();
  28485. }
  28486. }
  28487. });
  28488. /**
  28489. * Adds a keyboard shortcut for some command or function.
  28490. *
  28491. * @method addShortcut
  28492. * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  28493. * @param {String} desc Text description for the command.
  28494. * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
  28495. * @param {Object} scope Optional scope to execute the function in.
  28496. * @return {Boolean} true/false state if the shortcut was added or not.
  28497. */
  28498. self.add = function(pattern, desc, cmdFunc, scope) {
  28499. var cmd;
  28500. cmd = cmdFunc;
  28501. if (typeof cmdFunc === 'string') {
  28502. cmdFunc = function() {
  28503. editor.execCommand(cmd, false, null);
  28504. };
  28505. } else if (Tools.isArray(cmd)) {
  28506. cmdFunc = function() {
  28507. editor.execCommand(cmd[0], cmd[1], cmd[2]);
  28508. };
  28509. }
  28510. each(explode(Tools.trim(pattern.toLowerCase())), function(pattern) {
  28511. var shortcut = createShortcut(pattern, desc, cmdFunc, scope);
  28512. shortcuts[shortcut.id] = shortcut;
  28513. });
  28514. return true;
  28515. };
  28516. /**
  28517. * Remove a keyboard shortcut by pattern.
  28518. *
  28519. * @method remove
  28520. * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  28521. * @return {Boolean} true/false state if the shortcut was removed or not.
  28522. */
  28523. self.remove = function(pattern) {
  28524. var shortcut = createShortcut(pattern);
  28525. if (shortcuts[shortcut.id]) {
  28526. delete shortcuts[shortcut.id];
  28527. return true;
  28528. }
  28529. return false;
  28530. };
  28531. };
  28532. });
  28533. // Included from: js/tinymce/classes/file/Uploader.js
  28534. /**
  28535. * Uploader.js
  28536. *
  28537. * Released under LGPL License.
  28538. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  28539. *
  28540. * License: http://www.tinymce.com/license
  28541. * Contributing: http://www.tinymce.com/contributing
  28542. */
  28543. /**
  28544. * Upload blobs or blob infos to the specified URL or handler.
  28545. *
  28546. * @private
  28547. * @class tinymce.file.Uploader
  28548. * @example
  28549. * var uploader = new Uploader({
  28550. * url: '/upload.php',
  28551. * basePath: '/base/path',
  28552. * credentials: true,
  28553. * handler: function(data, success, failure) {
  28554. * ...
  28555. * }
  28556. * });
  28557. *
  28558. * uploader.upload(blobInfos).then(function(result) {
  28559. * ...
  28560. * });
  28561. */
  28562. define("tinymce/file/Uploader", [
  28563. "tinymce/util/Promise",
  28564. "tinymce/util/Tools",
  28565. "tinymce/util/Fun"
  28566. ], function(Promise, Tools, Fun) {
  28567. return function(uploadStatus, settings) {
  28568. var pendingPromises = {};
  28569. function fileName(blobInfo) {
  28570. var ext, extensions;
  28571. extensions = {
  28572. 'image/jpeg': 'jpg',
  28573. 'image/jpg': 'jpg',
  28574. 'image/gif': 'gif',
  28575. 'image/png': 'png'
  28576. };
  28577. ext = extensions[blobInfo.blob().type.toLowerCase()] || 'dat';
  28578. return blobInfo.id() + '.' + ext;
  28579. }
  28580. function pathJoin(path1, path2) {
  28581. if (path1) {
  28582. return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, '');
  28583. }
  28584. return path2;
  28585. }
  28586. function blobInfoToData(blobInfo) {
  28587. return {
  28588. id: blobInfo.id,
  28589. blob: blobInfo.blob,
  28590. base64: blobInfo.base64,
  28591. filename: Fun.constant(fileName(blobInfo))
  28592. };
  28593. }
  28594. function defaultHandler(blobInfo, success, failure, progress) {
  28595. var xhr, formData;
  28596. xhr = new XMLHttpRequest();
  28597. xhr.open('POST', settings.url);
  28598. xhr.withCredentials = settings.credentials;
  28599. xhr.upload.onprogress = function(e) {
  28600. progress(e.loaded / e.total * 100);
  28601. };
  28602. xhr.onerror = function() {
  28603. failure("Image upload failed due to a XHR Transport error. Code: " + xhr.status);
  28604. };
  28605. xhr.onload = function() {
  28606. var json;
  28607. if (xhr.status != 200) {
  28608. failure("HTTP Error: " + xhr.status);
  28609. return;
  28610. }
  28611. json = JSON.parse(xhr.responseText);
  28612. if (!json || typeof json.location != "string") {
  28613. failure("Invalid JSON: " + xhr.responseText);
  28614. return;
  28615. }
  28616. success(pathJoin(settings.basePath, json.location));
  28617. };
  28618. formData = new FormData();
  28619. formData.append('file', blobInfo.blob(), fileName(blobInfo));
  28620. xhr.send(formData);
  28621. }
  28622. function noUpload() {
  28623. return new Promise(function(resolve) {
  28624. resolve([]);
  28625. });
  28626. }
  28627. function handlerSuccess(blobInfo, url) {
  28628. return {
  28629. url: url,
  28630. blobInfo: blobInfo,
  28631. status: true
  28632. };
  28633. }
  28634. function handlerFailure(blobInfo, error) {
  28635. return {
  28636. url: '',
  28637. blobInfo: blobInfo,
  28638. status: false,
  28639. error: error
  28640. };
  28641. }
  28642. function resolvePending(blobUri, result) {
  28643. Tools.each(pendingPromises[blobUri], function(resolve) {
  28644. resolve(result);
  28645. });
  28646. delete pendingPromises[blobUri];
  28647. }
  28648. function uploadBlobInfo(blobInfo, handler, openNotification) {
  28649. uploadStatus.markPending(blobInfo.blobUri());
  28650. return new Promise(function(resolve) {
  28651. var notification, progress;
  28652. var noop = function() {
  28653. };
  28654. try {
  28655. var closeNotification = function() {
  28656. if (notification) {
  28657. notification.close();
  28658. progress = noop; // Once it's closed it's closed
  28659. }
  28660. };
  28661. var success = function(url) {
  28662. closeNotification();
  28663. uploadStatus.markUploaded(blobInfo.blobUri(), url);
  28664. resolvePending(blobInfo.blobUri(), handlerSuccess(blobInfo, url));
  28665. resolve(handlerSuccess(blobInfo, url));
  28666. };
  28667. var failure = function() {
  28668. closeNotification();
  28669. uploadStatus.removeFailed(blobInfo.blobUri());
  28670. resolvePending(blobInfo.blobUri(), handlerFailure(blobInfo, failure));
  28671. resolve(handlerFailure(blobInfo, failure));
  28672. };
  28673. progress = function(percent) {
  28674. if (percent < 0 || percent > 100) {
  28675. return;
  28676. }
  28677. if (!notification) {
  28678. notification = openNotification();
  28679. }
  28680. notification.progressBar.value(percent);
  28681. };
  28682. handler(blobInfoToData(blobInfo), success, failure, progress);
  28683. } catch (ex) {
  28684. resolve(handlerFailure(blobInfo, ex.message));
  28685. }
  28686. });
  28687. }
  28688. function isDefaultHandler(handler) {
  28689. return handler === defaultHandler;
  28690. }
  28691. function pendingUploadBlobInfo(blobInfo) {
  28692. var blobUri = blobInfo.blobUri();
  28693. return new Promise(function(resolve) {
  28694. pendingPromises[blobUri] = pendingPromises[blobUri] || [];
  28695. pendingPromises[blobUri].push(resolve);
  28696. });
  28697. }
  28698. function uploadBlobs(blobInfos, openNotification) {
  28699. blobInfos = Tools.grep(blobInfos, function(blobInfo) {
  28700. return !uploadStatus.isUploaded(blobInfo.blobUri());
  28701. });
  28702. return Promise.all(Tools.map(blobInfos, function(blobInfo) {
  28703. return uploadStatus.isPending(blobInfo.blobUri()) ?
  28704. pendingUploadBlobInfo(blobInfo) : uploadBlobInfo(blobInfo, settings.handler, openNotification);
  28705. }));
  28706. }
  28707. function upload(blobInfos, openNotification) {
  28708. return (!settings.url && isDefaultHandler(settings.handler)) ? noUpload() : uploadBlobs(blobInfos, openNotification);
  28709. }
  28710. settings = Tools.extend({
  28711. credentials: false,
  28712. // We are adding a notify argument to this (at the moment, until it doesn't work)
  28713. handler: defaultHandler
  28714. }, settings);
  28715. return {
  28716. upload: upload
  28717. };
  28718. };
  28719. });
  28720. // Included from: js/tinymce/classes/file/Conversions.js
  28721. /**
  28722. * Conversions.js
  28723. *
  28724. * Released under LGPL License.
  28725. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  28726. *
  28727. * License: http://www.tinymce.com/license
  28728. * Contributing: http://www.tinymce.com/contributing
  28729. */
  28730. /**
  28731. * Converts blob/uris back and forth.
  28732. *
  28733. * @private
  28734. * @class tinymce.file.Conversions
  28735. */
  28736. define("tinymce/file/Conversions", [
  28737. "tinymce/util/Promise"
  28738. ], function(Promise) {
  28739. function blobUriToBlob(url) {
  28740. return new Promise(function(resolve) {
  28741. var xhr = new XMLHttpRequest();
  28742. xhr.open('GET', url, true);
  28743. xhr.responseType = 'blob';
  28744. xhr.onload = function() {
  28745. if (this.status == 200) {
  28746. resolve(this.response);
  28747. }
  28748. };
  28749. xhr.send();
  28750. });
  28751. }
  28752. function parseDataUri(uri) {
  28753. var type, matches;
  28754. uri = decodeURIComponent(uri).split(',');
  28755. matches = /data:([^;]+)/.exec(uri[0]);
  28756. if (matches) {
  28757. type = matches[1];
  28758. }
  28759. return {
  28760. type: type,
  28761. data: uri[1]
  28762. };
  28763. }
  28764. function dataUriToBlob(uri) {
  28765. return new Promise(function(resolve) {
  28766. var str, arr, i;
  28767. uri = parseDataUri(uri);
  28768. // Might throw error if data isn't proper base64
  28769. try {
  28770. str = atob(uri.data);
  28771. } catch (e) {
  28772. resolve(new Blob([]));
  28773. return;
  28774. }
  28775. arr = new Uint8Array(str.length);
  28776. for (i = 0; i < arr.length; i++) {
  28777. arr[i] = str.charCodeAt(i);
  28778. }
  28779. resolve(new Blob([arr], {type: uri.type}));
  28780. });
  28781. }
  28782. function uriToBlob(url) {
  28783. if (url.indexOf('blob:') === 0) {
  28784. return blobUriToBlob(url);
  28785. }
  28786. if (url.indexOf('data:') === 0) {
  28787. return dataUriToBlob(url);
  28788. }
  28789. return null;
  28790. }
  28791. function blobToDataUri(blob) {
  28792. return new Promise(function(resolve) {
  28793. var reader = new FileReader();
  28794. reader.onloadend = function() {
  28795. resolve(reader.result);
  28796. };
  28797. reader.readAsDataURL(blob);
  28798. });
  28799. }
  28800. return {
  28801. uriToBlob: uriToBlob,
  28802. blobToDataUri: blobToDataUri,
  28803. parseDataUri: parseDataUri
  28804. };
  28805. });
  28806. // Included from: js/tinymce/classes/file/ImageScanner.js
  28807. /**
  28808. * ImageScanner.js
  28809. *
  28810. * Released under LGPL License.
  28811. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  28812. *
  28813. * License: http://www.tinymce.com/license
  28814. * Contributing: http://www.tinymce.com/contributing
  28815. */
  28816. /**
  28817. * Finds images with data uris or blob uris. If data uris are found it will convert them into blob uris.
  28818. *
  28819. * @private
  28820. * @class tinymce.file.ImageScanner
  28821. */
  28822. define("tinymce/file/ImageScanner", [
  28823. "tinymce/util/Promise",
  28824. "tinymce/util/Arr",
  28825. "tinymce/util/Fun",
  28826. "tinymce/file/Conversions",
  28827. "tinymce/Env"
  28828. ], function(Promise, Arr, Fun, Conversions, Env) {
  28829. var count = 0;
  28830. return function(uploadStatus, blobCache) {
  28831. var cachedPromises = {};
  28832. function findAll(elm, predicate) {
  28833. var images, promises;
  28834. function imageToBlobInfo(img, resolve) {
  28835. var base64, blobInfo;
  28836. if (img.src.indexOf('blob:') === 0) {
  28837. blobInfo = blobCache.getByUri(img.src);
  28838. if (blobInfo) {
  28839. resolve({
  28840. image: img,
  28841. blobInfo: blobInfo
  28842. });
  28843. }
  28844. return;
  28845. }
  28846. base64 = Conversions.parseDataUri(img.src).data;
  28847. blobInfo = blobCache.findFirst(function(cachedBlobInfo) {
  28848. return cachedBlobInfo.base64() === base64;
  28849. });
  28850. if (blobInfo) {
  28851. resolve({
  28852. image: img,
  28853. blobInfo: blobInfo
  28854. });
  28855. } else {
  28856. Conversions.uriToBlob(img.src).then(function(blob) {
  28857. var blobInfoId = 'blobid' + (count++),
  28858. blobInfo = blobCache.create(blobInfoId, blob, base64);
  28859. blobCache.add(blobInfo);
  28860. resolve({
  28861. image: img,
  28862. blobInfo: blobInfo
  28863. });
  28864. });
  28865. }
  28866. }
  28867. if (!predicate) {
  28868. predicate = Fun.constant(true);
  28869. }
  28870. images = Arr.filter(elm.getElementsByTagName('img'), function(img) {
  28871. var src = img.src;
  28872. if (!Env.fileApi) {
  28873. return false;
  28874. }
  28875. if (img.hasAttribute('data-mce-bogus')) {
  28876. return false;
  28877. }
  28878. if (img.hasAttribute('data-mce-placeholder')) {
  28879. return false;
  28880. }
  28881. if (!src || src == Env.transparentSrc) {
  28882. return false;
  28883. }
  28884. if (src.indexOf('blob:') === 0) {
  28885. return !uploadStatus.isUploaded(src);
  28886. }
  28887. if (src.indexOf('data:') === 0) {
  28888. return predicate(img);
  28889. }
  28890. return false;
  28891. });
  28892. promises = Arr.map(images, function(img) {
  28893. var newPromise;
  28894. if (cachedPromises[img.src]) {
  28895. // Since the cached promise will return the cached image
  28896. // We need to wrap it and resolve with the actual image
  28897. return new Promise(function(resolve) {
  28898. cachedPromises[img.src].then(function(imageInfo) {
  28899. resolve({
  28900. image: img,
  28901. blobInfo: imageInfo.blobInfo
  28902. });
  28903. });
  28904. });
  28905. }
  28906. newPromise = new Promise(function(resolve) {
  28907. imageToBlobInfo(img, resolve);
  28908. }).then(function(result) {
  28909. delete cachedPromises[result.image.src];
  28910. return result;
  28911. })['catch'](function(error) {
  28912. delete cachedPromises[img.src];
  28913. return error;
  28914. });
  28915. cachedPromises[img.src] = newPromise;
  28916. return newPromise;
  28917. });
  28918. return Promise.all(promises);
  28919. }
  28920. return {
  28921. findAll: findAll
  28922. };
  28923. };
  28924. });
  28925. // Included from: js/tinymce/classes/file/BlobCache.js
  28926. /**
  28927. * BlobCache.js
  28928. *
  28929. * Released under LGPL License.
  28930. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  28931. *
  28932. * License: http://www.tinymce.com/license
  28933. * Contributing: http://www.tinymce.com/contributing
  28934. */
  28935. /**
  28936. * Hold blob info objects where a blob has extra internal information.
  28937. *
  28938. * @private
  28939. * @class tinymce.file.BlobCache
  28940. */
  28941. define("tinymce/file/BlobCache", [
  28942. "tinymce/util/Arr",
  28943. "tinymce/util/Fun"
  28944. ], function(Arr, Fun) {
  28945. return function() {
  28946. var cache = [], constant = Fun.constant;
  28947. function create(id, blob, base64) {
  28948. return {
  28949. id: constant(id),
  28950. blob: constant(blob),
  28951. base64: constant(base64),
  28952. blobUri: constant(URL.createObjectURL(blob))
  28953. };
  28954. }
  28955. function add(blobInfo) {
  28956. if (!get(blobInfo.id())) {
  28957. cache.push(blobInfo);
  28958. }
  28959. }
  28960. function get(id) {
  28961. return findFirst(function(cachedBlobInfo) {
  28962. return cachedBlobInfo.id() === id;
  28963. });
  28964. }
  28965. function findFirst(predicate) {
  28966. return Arr.filter(cache, predicate)[0];
  28967. }
  28968. function getByUri(blobUri) {
  28969. return findFirst(function(blobInfo) {
  28970. return blobInfo.blobUri() == blobUri;
  28971. });
  28972. }
  28973. function removeByUri(blobUri) {
  28974. cache = Arr.filter(cache, function(blobInfo) {
  28975. if (blobInfo.blobUri() === blobUri) {
  28976. URL.revokeObjectURL(blobInfo.blobUri());
  28977. return false;
  28978. }
  28979. return true;
  28980. });
  28981. }
  28982. function destroy() {
  28983. Arr.each(cache, function(cachedBlobInfo) {
  28984. URL.revokeObjectURL(cachedBlobInfo.blobUri());
  28985. });
  28986. cache = [];
  28987. }
  28988. return {
  28989. create: create,
  28990. add: add,
  28991. get: get,
  28992. getByUri: getByUri,
  28993. findFirst: findFirst,
  28994. removeByUri: removeByUri,
  28995. destroy: destroy
  28996. };
  28997. };
  28998. });
  28999. // Included from: js/tinymce/classes/file/UploadStatus.js
  29000. /**
  29001. * UploadStatus.js
  29002. *
  29003. * Released under LGPL License.
  29004. * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
  29005. *
  29006. * License: http://www.tinymce.com/license
  29007. * Contributing: http://www.tinymce.com/contributing
  29008. */
  29009. /**
  29010. * Holds the current status of a blob uri, if it's pending or uploaded and what the result urls was.
  29011. *
  29012. * @private
  29013. * @class tinymce.file.UploadStatus
  29014. */
  29015. define("tinymce/file/UploadStatus", [
  29016. ], function() {
  29017. return function() {
  29018. var PENDING = 1, UPLOADED = 2;
  29019. var blobUriStatuses = {};
  29020. function createStatus(status, resultUri) {
  29021. return {
  29022. status: status,
  29023. resultUri: resultUri
  29024. };
  29025. }
  29026. function hasBlobUri(blobUri) {
  29027. return blobUri in blobUriStatuses;
  29028. }
  29029. function getResultUri(blobUri) {
  29030. var result = blobUriStatuses[blobUri];
  29031. return result ? result.resultUri : null;
  29032. }
  29033. function isPending(blobUri) {
  29034. return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === PENDING : false;
  29035. }
  29036. function isUploaded(blobUri) {
  29037. return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === UPLOADED : false;
  29038. }
  29039. function markPending(blobUri) {
  29040. blobUriStatuses[blobUri] = createStatus(PENDING, null);
  29041. }
  29042. function markUploaded(blobUri, resultUri) {
  29043. blobUriStatuses[blobUri] = createStatus(UPLOADED, resultUri);
  29044. }
  29045. function removeFailed(blobUri) {
  29046. delete blobUriStatuses[blobUri];
  29047. }
  29048. function destroy() {
  29049. blobUriStatuses = {};
  29050. }
  29051. return {
  29052. hasBlobUri: hasBlobUri,
  29053. getResultUri: getResultUri,
  29054. isPending: isPending,
  29055. isUploaded: isUploaded,
  29056. markPending: markPending,
  29057. markUploaded: markUploaded,
  29058. removeFailed: removeFailed,
  29059. destroy: destroy
  29060. };
  29061. };
  29062. });
  29063. // Included from: js/tinymce/classes/EditorUpload.js
  29064. /**
  29065. * EditorUpload.js
  29066. *
  29067. * Released under LGPL License.
  29068. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  29069. *
  29070. * License: http://www.tinymce.com/license
  29071. * Contributing: http://www.tinymce.com/contributing
  29072. */
  29073. /**
  29074. * Handles image uploads, updates undo stack and patches over various internal functions.
  29075. *
  29076. * @private
  29077. * @class tinymce.EditorUpload
  29078. */
  29079. define("tinymce/EditorUpload", [
  29080. "tinymce/util/Arr",
  29081. "tinymce/file/Uploader",
  29082. "tinymce/file/ImageScanner",
  29083. "tinymce/file/BlobCache",
  29084. "tinymce/file/UploadStatus"
  29085. ], function(Arr, Uploader, ImageScanner, BlobCache, UploadStatus) {
  29086. return function(editor) {
  29087. var blobCache = new BlobCache(), uploader, imageScanner, settings = editor.settings;
  29088. var uploadStatus = new UploadStatus();
  29089. function aliveGuard(callback) {
  29090. return function(result) {
  29091. if (editor.selection) {
  29092. return callback(result);
  29093. }
  29094. return [];
  29095. };
  29096. }
  29097. // Replaces strings without regexps to avoid FF regexp to big issue
  29098. function replaceString(content, search, replace) {
  29099. var index = 0;
  29100. do {
  29101. index = content.indexOf(search, index);
  29102. if (index !== -1) {
  29103. content = content.substring(0, index) + replace + content.substr(index + search.length);
  29104. index += replace.length - search.length + 1;
  29105. }
  29106. } while (index !== -1);
  29107. return content;
  29108. }
  29109. function replaceImageUrl(content, targetUrl, replacementUrl) {
  29110. content = replaceString(content, 'src="' + targetUrl + '"', 'src="' + replacementUrl + '"');
  29111. content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"');
  29112. return content;
  29113. }
  29114. function replaceUrlInUndoStack(targetUrl, replacementUrl) {
  29115. Arr.each(editor.undoManager.data, function(level) {
  29116. level.content = replaceImageUrl(level.content, targetUrl, replacementUrl);
  29117. });
  29118. }
  29119. function openNotification() {
  29120. return editor.notificationManager.open({
  29121. text: editor.translate('Image uploading...'),
  29122. type: 'info',
  29123. timeout: -1,
  29124. progressBar: true
  29125. });
  29126. }
  29127. function replaceImageUri(image, resultUri) {
  29128. blobCache.removeByUri(image.src);
  29129. replaceUrlInUndoStack(image.src, resultUri);
  29130. editor.$(image).attr({
  29131. src: resultUri,
  29132. 'data-mce-src': editor.convertURL(resultUri, 'src')
  29133. });
  29134. }
  29135. function uploadImages(callback) {
  29136. if (!uploader) {
  29137. uploader = new Uploader(uploadStatus, {
  29138. url: settings.images_upload_url,
  29139. basePath: settings.images_upload_base_path,
  29140. credentials: settings.images_upload_credentials,
  29141. handler: settings.images_upload_handler
  29142. });
  29143. }
  29144. return scanForImages().then(aliveGuard(function(imageInfos) {
  29145. var blobInfos;
  29146. blobInfos = Arr.map(imageInfos, function(imageInfo) {
  29147. return imageInfo.blobInfo;
  29148. });
  29149. return uploader.upload(blobInfos, openNotification).then(aliveGuard(function(result) {
  29150. result = Arr.map(result, function(uploadInfo, index) {
  29151. var image = imageInfos[index].image;
  29152. if (uploadInfo.status && editor.settings.images_replace_blob_uris !== false) {
  29153. replaceImageUri(image, uploadInfo.url);
  29154. }
  29155. return {
  29156. element: image,
  29157. status: uploadInfo.status
  29158. };
  29159. });
  29160. if (callback) {
  29161. callback(result);
  29162. }
  29163. return result;
  29164. }));
  29165. }));
  29166. }
  29167. function uploadImagesAuto(callback) {
  29168. if (settings.automatic_uploads !== false) {
  29169. return uploadImages(callback);
  29170. }
  29171. }
  29172. function isValidDataUriImage(imgElm) {
  29173. return settings.images_dataimg_filter ? settings.images_dataimg_filter(imgElm) : true;
  29174. }
  29175. function scanForImages() {
  29176. if (!imageScanner) {
  29177. imageScanner = new ImageScanner(uploadStatus, blobCache);
  29178. }
  29179. return imageScanner.findAll(editor.getBody(), isValidDataUriImage).then(aliveGuard(function(result) {
  29180. Arr.each(result, function(resultItem) {
  29181. replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri());
  29182. resultItem.image.src = resultItem.blobInfo.blobUri();
  29183. resultItem.image.removeAttribute('data-mce-src');
  29184. });
  29185. return result;
  29186. }));
  29187. }
  29188. function destroy() {
  29189. blobCache.destroy();
  29190. uploadStatus.destroy();
  29191. imageScanner = uploader = null;
  29192. }
  29193. function replaceBlobUris(content) {
  29194. return content.replace(/src="(blob:[^"]+)"/g, function(match, blobUri) {
  29195. var resultUri = uploadStatus.getResultUri(blobUri);
  29196. if (resultUri) {
  29197. return 'src="' + resultUri + '"';
  29198. }
  29199. var blobInfo = blobCache.getByUri(blobUri);
  29200. if (!blobInfo) {
  29201. blobInfo = Arr.reduce(editor.editorManager.editors, function(result, editor) {
  29202. return result || editor.editorUpload.blobCache.getByUri(blobUri);
  29203. }, null);
  29204. }
  29205. if (blobInfo) {
  29206. return 'src="data:' + blobInfo.blob().type + ';base64,' + blobInfo.base64() + '"';
  29207. }
  29208. return match;
  29209. });
  29210. }
  29211. editor.on('setContent', function() {
  29212. if (editor.settings.automatic_uploads !== false) {
  29213. uploadImagesAuto();
  29214. } else {
  29215. scanForImages();
  29216. }
  29217. });
  29218. editor.on('RawSaveContent', function(e) {
  29219. e.content = replaceBlobUris(e.content);
  29220. });
  29221. editor.on('getContent', function(e) {
  29222. if (e.source_view || e.format == 'raw') {
  29223. return;
  29224. }
  29225. e.content = replaceBlobUris(e.content);
  29226. });
  29227. editor.on('PostRender', function() {
  29228. editor.parser.addNodeFilter('img', function(images) {
  29229. Arr.each(images, function(img) {
  29230. var src = img.attr('src');
  29231. if (blobCache.getByUri(src)) {
  29232. return;
  29233. }
  29234. var resultUri = uploadStatus.getResultUri(src);
  29235. if (resultUri) {
  29236. img.attr('src', resultUri);
  29237. }
  29238. });
  29239. });
  29240. });
  29241. return {
  29242. blobCache: blobCache,
  29243. uploadImages: uploadImages,
  29244. uploadImagesAuto: uploadImagesAuto,
  29245. scanForImages: scanForImages,
  29246. destroy: destroy
  29247. };
  29248. };
  29249. });
  29250. // Included from: js/tinymce/classes/caret/FakeCaret.js
  29251. /**
  29252. * FakeCaret.js
  29253. *
  29254. * Released under LGPL License.
  29255. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  29256. *
  29257. * License: http://www.tinymce.com/license
  29258. * Contributing: http://www.tinymce.com/contributing
  29259. */
  29260. /**
  29261. * This module contains logic for rendering a fake visual caret.
  29262. *
  29263. * @private
  29264. * @class tinymce.caret.FakeCaret
  29265. */
  29266. define("tinymce/caret/FakeCaret", [
  29267. "tinymce/caret/CaretContainer",
  29268. "tinymce/caret/CaretPosition",
  29269. "tinymce/dom/NodeType",
  29270. "tinymce/dom/RangeUtils",
  29271. "tinymce/dom/DomQuery",
  29272. "tinymce/geom/ClientRect",
  29273. "tinymce/util/Delay"
  29274. ], function(CaretContainer, CaretPosition, NodeType, RangeUtils, $, ClientRect, Delay) {
  29275. var isContentEditableFalse = NodeType.isContentEditableFalse;
  29276. return function(rootNode, isBlock) {
  29277. var cursorInterval, $lastVisualCaret, caretContainerNode;
  29278. function getAbsoluteClientRect(node, before) {
  29279. var clientRect = ClientRect.collapse(node.getBoundingClientRect(), before),
  29280. docElm, scrollX, scrollY, margin, rootRect;
  29281. if (rootNode.tagName == 'BODY') {
  29282. docElm = rootNode.ownerDocument.documentElement;
  29283. scrollX = rootNode.scrollLeft || docElm.scrollLeft;
  29284. scrollY = rootNode.scrollTop || docElm.scrollTop;
  29285. } else {
  29286. rootRect = rootNode.getBoundingClientRect();
  29287. scrollX = rootNode.scrollLeft - rootRect.left;
  29288. scrollY = rootNode.scrollTop - rootRect.top;
  29289. }
  29290. clientRect.left += scrollX;
  29291. clientRect.right += scrollX;
  29292. clientRect.top += scrollY;
  29293. clientRect.bottom += scrollY;
  29294. clientRect.width = 1;
  29295. margin = node.offsetWidth - node.clientWidth;
  29296. if (margin > 0) {
  29297. if (before) {
  29298. margin *= -1;
  29299. }
  29300. clientRect.left += margin;
  29301. clientRect.right += margin;
  29302. }
  29303. return clientRect;
  29304. }
  29305. function trimInlineCaretContainers() {
  29306. var contentEditableFalseNodes, node, sibling, i, data;
  29307. contentEditableFalseNodes = $('*[contentEditable=false]', rootNode);
  29308. for (i = 0; i < contentEditableFalseNodes.length; i++) {
  29309. node = contentEditableFalseNodes[i];
  29310. sibling = node.previousSibling;
  29311. if (CaretContainer.endsWithCaretContainer(sibling)) {
  29312. data = sibling.data;
  29313. if (data.length == 1) {
  29314. sibling.parentNode.removeChild(sibling);
  29315. } else {
  29316. sibling.deleteData(data.length - 1, 1);
  29317. }
  29318. }
  29319. sibling = node.nextSibling;
  29320. if (CaretContainer.startsWithCaretContainer(sibling)) {
  29321. data = sibling.data;
  29322. if (data.length == 1) {
  29323. sibling.parentNode.removeChild(sibling);
  29324. } else {
  29325. sibling.deleteData(0, 1);
  29326. }
  29327. }
  29328. }
  29329. return null;
  29330. }
  29331. function show(before, node) {
  29332. var clientRect, rng, container;
  29333. hide();
  29334. if (isBlock(node)) {
  29335. caretContainerNode = CaretContainer.insertBlock('p', node, before);
  29336. clientRect = getAbsoluteClientRect(node, before);
  29337. $(caretContainerNode).css('top', clientRect.top);
  29338. $lastVisualCaret = $('<div class="mce-visual-caret" data-mce-bogus="all"></div>').css(clientRect).appendTo(rootNode);
  29339. if (before) {
  29340. $lastVisualCaret.addClass('mce-visual-caret-before');
  29341. }
  29342. startBlink();
  29343. rng = node.ownerDocument.createRange();
  29344. container = caretContainerNode.firstChild;
  29345. rng.setStart(container, 0);
  29346. rng.setEnd(container, 1);
  29347. } else {
  29348. caretContainerNode = CaretContainer.insertInline(node, before);
  29349. rng = node.ownerDocument.createRange();
  29350. if (isContentEditableFalse(caretContainerNode.nextSibling)) {
  29351. rng.setStart(caretContainerNode, 0);
  29352. rng.setEnd(caretContainerNode, 0);
  29353. } else {
  29354. rng.setStart(caretContainerNode, 1);
  29355. rng.setEnd(caretContainerNode, 1);
  29356. }
  29357. return rng;
  29358. }
  29359. return rng;
  29360. }
  29361. function hide() {
  29362. trimInlineCaretContainers();
  29363. if (caretContainerNode) {
  29364. CaretContainer.remove(caretContainerNode);
  29365. caretContainerNode = null;
  29366. }
  29367. if ($lastVisualCaret) {
  29368. $lastVisualCaret.remove();
  29369. $lastVisualCaret = null;
  29370. }
  29371. clearInterval(cursorInterval);
  29372. }
  29373. function startBlink() {
  29374. cursorInterval = Delay.setInterval(function() {
  29375. $('div.mce-visual-caret', rootNode).toggleClass('mce-visual-caret-hidden');
  29376. }, 500);
  29377. }
  29378. function destroy() {
  29379. Delay.clearInterval(cursorInterval);
  29380. }
  29381. function getCss() {
  29382. return (
  29383. '.mce-visual-caret {' +
  29384. 'position: absolute;' +
  29385. 'background-color: black;' +
  29386. 'background-color: currentcolor;' +
  29387. '}' +
  29388. '.mce-visual-caret-hidden {' +
  29389. 'display: none;' +
  29390. '}' +
  29391. '*[data-mce-caret] {' +
  29392. 'position: absolute;' +
  29393. 'left: -1000px;' +
  29394. 'right: auto;' +
  29395. 'top: 0;' +
  29396. 'margin: 0;' +
  29397. 'padding: 0;' +
  29398. '}'
  29399. );
  29400. }
  29401. return {
  29402. show: show,
  29403. hide: hide,
  29404. getCss: getCss,
  29405. destroy: destroy
  29406. };
  29407. };
  29408. });
  29409. // Included from: js/tinymce/classes/dom/Dimensions.js
  29410. /**
  29411. * Dimensions.js
  29412. *
  29413. * Released under LGPL License.
  29414. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  29415. *
  29416. * License: http://www.tinymce.com/license
  29417. * Contributing: http://www.tinymce.com/contributing
  29418. */
  29419. /**
  29420. * This module measures nodes and returns client rects. The client rects has an
  29421. * extra node property.
  29422. *
  29423. * @private
  29424. * @class tinymce.dom.Dimensions
  29425. */
  29426. define("tinymce/dom/Dimensions", [
  29427. "tinymce/util/Arr",
  29428. "tinymce/dom/NodeType",
  29429. "tinymce/geom/ClientRect"
  29430. ], function(Arr, NodeType, ClientRect) {
  29431. function getClientRects(node) {
  29432. function toArrayWithNode(clientRects) {
  29433. return Arr.map(clientRects, function(clientRect) {
  29434. clientRect = ClientRect.clone(clientRect);
  29435. clientRect.node = node;
  29436. return clientRect;
  29437. });
  29438. }
  29439. if (Arr.isArray(node)) {
  29440. return Arr.reduce(node, function(result, node) {
  29441. return result.concat(getClientRects(node));
  29442. }, []);
  29443. }
  29444. if (NodeType.isElement(node)) {
  29445. return toArrayWithNode(node.getClientRects());
  29446. }
  29447. if (NodeType.isText(node)) {
  29448. var rng = node.ownerDocument.createRange();
  29449. rng.setStart(node, 0);
  29450. rng.setEnd(node, node.data.length);
  29451. return toArrayWithNode(rng.getClientRects());
  29452. }
  29453. }
  29454. return {
  29455. /**
  29456. * Returns the client rects for a specific node.
  29457. *
  29458. * @method getClientRects
  29459. * @param {Array/DOMNode} node Node or array of nodes to get client rects on.
  29460. * @param {Array} Array of client rects with a extra node property.
  29461. */
  29462. getClientRects: getClientRects
  29463. };
  29464. });
  29465. // Included from: js/tinymce/classes/caret/LineWalker.js
  29466. /**
  29467. * LineWalker.js
  29468. *
  29469. * Released under LGPL License.
  29470. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  29471. *
  29472. * License: http://www.tinymce.com/license
  29473. * Contributing: http://www.tinymce.com/contributing
  29474. */
  29475. /**
  29476. * This module lets you walk the document line by line
  29477. * returing nodes and client rects for each line.
  29478. *
  29479. * @private
  29480. * @class tinymce.caret.LineWalker
  29481. */
  29482. define("tinymce/caret/LineWalker", [
  29483. "tinymce/util/Fun",
  29484. "tinymce/util/Arr",
  29485. "tinymce/dom/Dimensions",
  29486. "tinymce/caret/CaretCandidate",
  29487. "tinymce/caret/CaretUtils",
  29488. "tinymce/caret/CaretWalker",
  29489. "tinymce/caret/CaretPosition",
  29490. "tinymce/geom/ClientRect"
  29491. ], function(Fun, Arr, Dimensions, CaretCandidate, CaretUtils, CaretWalker, CaretPosition, ClientRect) {
  29492. var curry = Fun.curry;
  29493. function findUntil(direction, rootNode, predicateFn, node) {
  29494. while ((node = CaretUtils.findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) {
  29495. if (predicateFn(node)) {
  29496. return;
  29497. }
  29498. }
  29499. }
  29500. function walkUntil(direction, isAboveFn, isBeflowFn, rootNode, predicateFn, caretPosition) {
  29501. var line = 0, node, result = [], targetClientRect;
  29502. function add(node) {
  29503. var i, clientRect, clientRects;
  29504. clientRects = Dimensions.getClientRects(node);
  29505. if (direction == -1) {
  29506. clientRects = clientRects.reverse();
  29507. }
  29508. for (i = 0; i < clientRects.length; i++) {
  29509. clientRect = clientRects[i];
  29510. if (isBeflowFn(clientRect, targetClientRect)) {
  29511. continue;
  29512. }
  29513. if (result.length > 0 && isAboveFn(clientRect, Arr.last(result))) {
  29514. line++;
  29515. }
  29516. clientRect.line = line;
  29517. if (predicateFn(clientRect)) {
  29518. return true;
  29519. }
  29520. result.push(clientRect);
  29521. }
  29522. }
  29523. targetClientRect = Arr.last(caretPosition.getClientRects());
  29524. if (!targetClientRect) {
  29525. return result;
  29526. }
  29527. node = caretPosition.getNode();
  29528. add(node);
  29529. findUntil(direction, rootNode, add, node);
  29530. return result;
  29531. }
  29532. function aboveLineNumber(lineNumber, clientRect) {
  29533. return clientRect.line > lineNumber;
  29534. }
  29535. function isLine(lineNumber, clientRect) {
  29536. return clientRect.line === lineNumber;
  29537. }
  29538. var upUntil = curry(walkUntil, -1, ClientRect.isAbove, ClientRect.isBelow);
  29539. var downUntil = curry(walkUntil, 1, ClientRect.isBelow, ClientRect.isAbove);
  29540. function positionsUntil(direction, rootNode, predicateFn, node) {
  29541. var caretWalker = new CaretWalker(rootNode), walkFn, isBelowFn, isAboveFn,
  29542. caretPosition, result = [], line = 0, clientRect, targetClientRect;
  29543. function getClientRect(caretPosition) {
  29544. if (direction == 1) {
  29545. return Arr.last(caretPosition.getClientRects());
  29546. }
  29547. return Arr.last(caretPosition.getClientRects());
  29548. }
  29549. if (direction == 1) {
  29550. walkFn = caretWalker.next;
  29551. isBelowFn = ClientRect.isBelow;
  29552. isAboveFn = ClientRect.isAbove;
  29553. caretPosition = CaretPosition.after(node);
  29554. } else {
  29555. walkFn = caretWalker.prev;
  29556. isBelowFn = ClientRect.isAbove;
  29557. isAboveFn = ClientRect.isBelow;
  29558. caretPosition = CaretPosition.before(node);
  29559. }
  29560. targetClientRect = getClientRect(caretPosition);
  29561. do {
  29562. if (!caretPosition.isVisible()) {
  29563. continue;
  29564. }
  29565. clientRect = getClientRect(caretPosition);
  29566. if (isAboveFn(clientRect, targetClientRect)) {
  29567. continue;
  29568. }
  29569. if (result.length > 0 && isBelowFn(clientRect, Arr.last(result))) {
  29570. line++;
  29571. }
  29572. clientRect = ClientRect.clone(clientRect);
  29573. clientRect.position = caretPosition;
  29574. clientRect.line = line;
  29575. if (predicateFn(clientRect)) {
  29576. return result;
  29577. }
  29578. result.push(clientRect);
  29579. } while ((caretPosition = walkFn(caretPosition)));
  29580. return result;
  29581. }
  29582. return {
  29583. upUntil: upUntil,
  29584. downUntil: downUntil,
  29585. /**
  29586. * Find client rects with line and caret position until the predicate returns true.
  29587. *
  29588. * @method positionsUntil
  29589. * @param {Number} direction Direction forward/backward 1/-1.
  29590. * @param {DOMNode} rootNode Root node to walk within.
  29591. * @param {function} predicateFn Gets the client rect as it's input.
  29592. * @param {DOMNode} node Node to start walking from.
  29593. * @return {Array} Array of client rects with line and position properties.
  29594. */
  29595. positionsUntil: positionsUntil,
  29596. isAboveLine: curry(aboveLineNumber),
  29597. isLine: curry(isLine)
  29598. };
  29599. });
  29600. // Included from: js/tinymce/classes/caret/LineUtils.js
  29601. /**
  29602. * LineUtils.js
  29603. *
  29604. * Released under LGPL License.
  29605. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  29606. *
  29607. * License: http://www.tinymce.com/license
  29608. * Contributing: http://www.tinymce.com/contributing
  29609. */
  29610. /**
  29611. * Utility functions for working with lines.
  29612. *
  29613. * @private
  29614. * @class tinymce.caret.LineUtils
  29615. */
  29616. define("tinymce/caret/LineUtils", [
  29617. "tinymce/util/Fun",
  29618. "tinymce/util/Arr",
  29619. "tinymce/dom/NodeType",
  29620. "tinymce/dom/Dimensions",
  29621. "tinymce/geom/ClientRect",
  29622. "tinymce/caret/CaretUtils",
  29623. "tinymce/caret/CaretCandidate"
  29624. ], function(Fun, Arr, NodeType, Dimensions, ClientRect, CaretUtils, CaretCandidate) {
  29625. var isContentEditableFalse = NodeType.isContentEditableFalse,
  29626. findNode = CaretUtils.findNode,
  29627. curry = Fun.curry;
  29628. function distanceToRectLeft(clientRect, clientX) {
  29629. return Math.abs(clientRect.left - clientX);
  29630. }
  29631. function distanceToRectRight(clientRect, clientX) {
  29632. return Math.abs(clientRect.right - clientX);
  29633. }
  29634. function findClosestClientRect(clientRects, clientX) {
  29635. function isInside(clientX, clientRect) {
  29636. return clientX >= clientRect.left && clientX <= clientRect.right;
  29637. }
  29638. return Arr.reduce(clientRects, function(oldClientRect, clientRect) {
  29639. var oldDistance, newDistance;
  29640. oldDistance = Math.min(distanceToRectLeft(oldClientRect, clientX), distanceToRectRight(oldClientRect, clientX));
  29641. newDistance = Math.min(distanceToRectLeft(clientRect, clientX), distanceToRectRight(clientRect, clientX));
  29642. if (isInside(clientX, clientRect)) {
  29643. return clientRect;
  29644. }
  29645. if (isInside(clientX, oldClientRect)) {
  29646. return oldClientRect;
  29647. }
  29648. // cE=false has higher priority
  29649. if (newDistance == oldDistance && isContentEditableFalse(clientRect.node)) {
  29650. return clientRect;
  29651. }
  29652. if (newDistance < oldDistance) {
  29653. return clientRect;
  29654. }
  29655. return oldClientRect;
  29656. });
  29657. }
  29658. function walkUntil(direction, rootNode, predicateFn, node) {
  29659. while ((node = findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) {
  29660. if (predicateFn(node)) {
  29661. return;
  29662. }
  29663. }
  29664. }
  29665. function findLineNodeRects(rootNode, targetNodeRect) {
  29666. var clientRects = [];
  29667. function collect(checkPosFn, node) {
  29668. var lineRects;
  29669. lineRects = Arr.filter(Dimensions.getClientRects(node), function(clientRect) {
  29670. return !checkPosFn(clientRect, targetNodeRect);
  29671. });
  29672. clientRects = clientRects.concat(lineRects);
  29673. return lineRects.length === 0;
  29674. }
  29675. clientRects.push(targetNodeRect);
  29676. walkUntil(-1, rootNode, curry(collect, ClientRect.isAbove), targetNodeRect.node);
  29677. walkUntil(1, rootNode, curry(collect, ClientRect.isBelow), targetNodeRect.node);
  29678. return clientRects;
  29679. }
  29680. function getContentEditableFalseChildren(rootNode) {
  29681. return Arr.filter(Arr.toArray(rootNode.getElementsByTagName('*')), isContentEditableFalse);
  29682. }
  29683. function caretInfo(clientRect, clientX) {
  29684. return {
  29685. node: clientRect.node,
  29686. before: distanceToRectLeft(clientRect, clientX) < distanceToRectRight(clientRect, clientX)
  29687. };
  29688. }
  29689. function closestCaret(rootNode, clientX, clientY) {
  29690. var contentEditableFalseNodeRects, closestNodeRect;
  29691. contentEditableFalseNodeRects = Dimensions.getClientRects(getContentEditableFalseChildren(rootNode));
  29692. contentEditableFalseNodeRects = Arr.filter(contentEditableFalseNodeRects, function(clientRect) {
  29693. return clientY >= clientRect.top && clientY <= clientRect.bottom;
  29694. });
  29695. closestNodeRect = findClosestClientRect(contentEditableFalseNodeRects, clientX);
  29696. if (closestNodeRect) {
  29697. closestNodeRect = findClosestClientRect(findLineNodeRects(rootNode, closestNodeRect), clientX);
  29698. if (closestNodeRect && isContentEditableFalse(closestNodeRect.node)) {
  29699. return caretInfo(closestNodeRect, clientX);
  29700. }
  29701. }
  29702. return null;
  29703. }
  29704. return {
  29705. findClosestClientRect: findClosestClientRect,
  29706. findLineNodeRects: findLineNodeRects,
  29707. closestCaret: closestCaret
  29708. };
  29709. });
  29710. // Included from: js/tinymce/classes/DragDropOverrides.js
  29711. /**
  29712. * DragDropOverrides.js
  29713. *
  29714. * Released under LGPL License.
  29715. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  29716. *
  29717. * License: http://www.tinymce.com/license
  29718. * Contributing: http://www.tinymce.com/contributing
  29719. */
  29720. /**
  29721. * This module contains logic overriding the drag/drop logic of the editor.
  29722. *
  29723. * @private
  29724. * @class tinymce.DragDropOverrides
  29725. */
  29726. define("tinymce/DragDropOverrides", [
  29727. "tinymce/dom/NodeType",
  29728. "tinymce/util/Arr",
  29729. "tinymce/util/Fun"
  29730. ], function(
  29731. NodeType,
  29732. Arr,
  29733. Fun
  29734. ) {
  29735. var isContentEditableFalse = NodeType.isContentEditableFalse,
  29736. isContentEditableTrue = NodeType.isContentEditableTrue;
  29737. function init(editor) {
  29738. var $ = editor.$, rootDocument = document,
  29739. editableDoc = editor.getDoc(),
  29740. dom = editor.dom, state = {};
  29741. function isDraggable(elm) {
  29742. return isContentEditableFalse(elm);
  29743. }
  29744. function setBodyCursor(cursor) {
  29745. $(editor.getBody()).css('cursor', cursor);
  29746. }
  29747. function isValidDropTarget(elm) {
  29748. if (elm == state.element || editor.dom.isChildOf(elm, state.element)) {
  29749. return false;
  29750. }
  29751. if (isContentEditableFalse(elm)) {
  29752. return false;
  29753. }
  29754. return true;
  29755. }
  29756. function move(e) {
  29757. var deltaX, deltaY, pos, viewPort,
  29758. overflowX = 0, overflowY = 0, movement,
  29759. clientX, clientY, rootClientRect;
  29760. if (e.button !== 0) {
  29761. return;
  29762. }
  29763. deltaX = e.screenX - state.screenX;
  29764. deltaY = e.screenY - state.screenY;
  29765. movement = Math.max(Math.abs(deltaX), Math.abs(deltaY));
  29766. if (!state.dragging && movement > 10) {
  29767. state.dragging = true;
  29768. setBodyCursor('default');
  29769. state.clone = state.element.cloneNode(true);
  29770. pos = dom.getPos(state.element);
  29771. state.relX = state.clientX - pos.x;
  29772. state.relY = state.clientY - pos.y;
  29773. state.width = state.element.offsetWidth;
  29774. state.height = state.element.offsetHeight;
  29775. $(state.clone).css({
  29776. width: state.width,
  29777. height: state.height
  29778. }).removeAttr('data-mce-selected');
  29779. state.ghost = $('<div>').css({
  29780. position: 'absolute',
  29781. opacity: 0.5,
  29782. overflow: 'hidden',
  29783. width: state.width,
  29784. height: state.height
  29785. }).attr({
  29786. 'data-mce-bogus': 'all',
  29787. unselectable: 'on',
  29788. contenteditable: 'false'
  29789. }).addClass('mce-drag-container mce-reset').
  29790. append(state.clone).
  29791. appendTo(editor.getBody())[0];
  29792. viewPort = editor.dom.getViewPort(editor.getWin());
  29793. state.maxX = viewPort.w;
  29794. state.maxY = viewPort.h;
  29795. }
  29796. if (state.dragging) {
  29797. editor._selectionOverrides.hideFakeCaret();
  29798. editor.selection.placeCaretAt(e.clientX, e.clientY);
  29799. clientX = state.clientX + deltaX - state.relX;
  29800. clientY = state.clientY + deltaY + 5;
  29801. if (clientX + state.width > state.maxX) {
  29802. overflowX = (clientX + state.width) - state.maxX;
  29803. }
  29804. if (clientY + state.height > state.maxY) {
  29805. overflowY = (clientY + state.height) - state.maxY;
  29806. }
  29807. if (editor.getBody().nodeName != 'BODY') {
  29808. rootClientRect = editor.getBody().getBoundingClientRect();
  29809. } else {
  29810. rootClientRect = {left: 0, top: 0};
  29811. }
  29812. $(state.ghost).css({
  29813. left: clientX - rootClientRect.left,
  29814. top: clientY - rootClientRect.top,
  29815. width: state.width - overflowX,
  29816. height: state.height - overflowY
  29817. });
  29818. }
  29819. }
  29820. function drop(evt) {
  29821. var dropEvt;
  29822. if (state.dragging) {
  29823. // Hack for IE since it doesn't sync W3C Range with IE Specific range
  29824. editor.selection.setRng(editor.selection.getSel().getRangeAt(0));
  29825. if (isValidDropTarget(editor.selection.getNode())) {
  29826. var targetClone = state.element;
  29827. // Pass along clientX, clientY if we have them
  29828. dropEvt = editor.fire('drop', {
  29829. targetClone: targetClone,
  29830. clientX: evt.clientX,
  29831. clientY: evt.clientY
  29832. });
  29833. if (dropEvt.isDefaultPrevented()) {
  29834. return;
  29835. }
  29836. targetClone = dropEvt.targetClone;
  29837. editor.undoManager.transact(function() {
  29838. editor.insertContent(dom.getOuterHTML(targetClone));
  29839. $(state.element).remove();
  29840. });
  29841. }
  29842. }
  29843. stop();
  29844. }
  29845. function start(e) {
  29846. var ceElm, evt;
  29847. stop();
  29848. if (e.button !== 0) {
  29849. return;
  29850. }
  29851. ceElm = Arr.find(editor.dom.getParents(e.target), Fun.or(isContentEditableFalse, isContentEditableTrue));
  29852. if (isDraggable(ceElm)) {
  29853. evt = editor.fire('dragstart', {target: ceElm});
  29854. if (evt.isDefaultPrevented()) {
  29855. return;
  29856. }
  29857. editor.on('mousemove', move);
  29858. editor.on('mouseup', drop);
  29859. if (rootDocument != editableDoc) {
  29860. dom.bind(rootDocument, 'mousemove', move);
  29861. dom.bind(rootDocument, 'mouseup', drop);
  29862. }
  29863. state = {
  29864. screenX: e.screenX,
  29865. screenY: e.screenY,
  29866. clientX: e.clientX,
  29867. clientY: e.clientY,
  29868. element: ceElm
  29869. };
  29870. }
  29871. }
  29872. function stop() {
  29873. $(state.ghost).remove();
  29874. setBodyCursor(null);
  29875. editor.off('mousemove', move);
  29876. editor.off('mouseup', stop);
  29877. if (rootDocument != editableDoc) {
  29878. dom.unbind(rootDocument, 'mousemove', move);
  29879. dom.unbind(rootDocument, 'mouseup', stop);
  29880. }
  29881. state = {};
  29882. }
  29883. editor.on('mousedown', start);
  29884. // Blocks drop inside cE=false on IE
  29885. editor.on('drop', function(e) {
  29886. // FF doesn't pass out clientX/clientY for drop since this is for IE we just use null instead
  29887. var realTarget = typeof e.clientX !== 'undefined' ? editor.getDoc().elementFromPoint(e.clientX, e.clientY) : null;
  29888. if (isContentEditableFalse(realTarget) || isContentEditableFalse(editor.dom.getContentEditableParent(realTarget))) {
  29889. e.preventDefault();
  29890. }
  29891. });
  29892. }
  29893. return {
  29894. init: init
  29895. };
  29896. });
  29897. // Included from: js/tinymce/classes/SelectionOverrides.js
  29898. /**
  29899. * SelectionOverrides.js
  29900. *
  29901. * Released under LGPL License.
  29902. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  29903. *
  29904. * License: http://www.tinymce.com/license
  29905. * Contributing: http://www.tinymce.com/contributing
  29906. */
  29907. /**
  29908. * This module contains logic overriding the selection with keyboard/mouse
  29909. * around contentEditable=false regions.
  29910. *
  29911. * @example
  29912. * // Disable the default cE=false selection
  29913. * tinymce.activeEditor.on('ShowCaret BeforeObjectSelected', function(e) {
  29914. * e.preventDefault();
  29915. * });
  29916. *
  29917. * @private
  29918. * @class tinymce.SelectionOverrides
  29919. */
  29920. define("tinymce/SelectionOverrides", [
  29921. "tinymce/Env",
  29922. "tinymce/caret/CaretWalker",
  29923. "tinymce/caret/CaretPosition",
  29924. "tinymce/caret/CaretContainer",
  29925. "tinymce/caret/CaretUtils",
  29926. "tinymce/caret/FakeCaret",
  29927. "tinymce/caret/LineWalker",
  29928. "tinymce/caret/LineUtils",
  29929. "tinymce/dom/NodeType",
  29930. "tinymce/dom/RangeUtils",
  29931. "tinymce/geom/ClientRect",
  29932. "tinymce/util/VK",
  29933. "tinymce/util/Fun",
  29934. "tinymce/util/Arr",
  29935. "tinymce/util/Delay",
  29936. "tinymce/DragDropOverrides",
  29937. "tinymce/text/Zwsp"
  29938. ], function(
  29939. Env, CaretWalker, CaretPosition, CaretContainer, CaretUtils, FakeCaret, LineWalker,
  29940. LineUtils, NodeType, RangeUtils, ClientRect, VK, Fun, Arr, Delay, DragDropOverrides, Zwsp
  29941. ) {
  29942. var curry = Fun.curry,
  29943. isContentEditableTrue = NodeType.isContentEditableTrue,
  29944. isContentEditableFalse = NodeType.isContentEditableFalse,
  29945. isElement = NodeType.isElement,
  29946. isAfterContentEditableFalse = CaretUtils.isAfterContentEditableFalse,
  29947. isBeforeContentEditableFalse = CaretUtils.isBeforeContentEditableFalse,
  29948. getSelectedNode = RangeUtils.getSelectedNode;
  29949. function getVisualCaretPosition(walkFn, caretPosition) {
  29950. while ((caretPosition = walkFn(caretPosition))) {
  29951. if (caretPosition.isVisible()) {
  29952. return caretPosition;
  29953. }
  29954. }
  29955. return caretPosition;
  29956. }
  29957. function SelectionOverrides(editor) {
  29958. var rootNode = editor.getBody(), caretWalker = new CaretWalker(rootNode);
  29959. var getNextVisualCaretPosition = curry(getVisualCaretPosition, caretWalker.next);
  29960. var getPrevVisualCaretPosition = curry(getVisualCaretPosition, caretWalker.prev),
  29961. fakeCaret = new FakeCaret(editor.getBody(), isBlock),
  29962. realSelectionId = 'sel-' + editor.dom.uniqueId(),
  29963. selectedContentEditableNode, $ = editor.$;
  29964. function isBlock(node) {
  29965. return editor.dom.isBlock(node);
  29966. }
  29967. function setRange(range) {
  29968. //console.log('setRange', range);
  29969. if (range) {
  29970. editor.selection.setRng(range);
  29971. }
  29972. }
  29973. function getRange() {
  29974. return editor.selection.getRng();
  29975. }
  29976. function scrollIntoView(node, alignToTop) {
  29977. editor.selection.scrollIntoView(node, alignToTop);
  29978. }
  29979. function showCaret(direction, node, before) {
  29980. var e;
  29981. e = editor.fire('ShowCaret', {
  29982. target: node,
  29983. direction: direction,
  29984. before: before
  29985. });
  29986. if (e.isDefaultPrevented()) {
  29987. return null;
  29988. }
  29989. scrollIntoView(node, direction === -1);
  29990. return fakeCaret.show(before, node);
  29991. }
  29992. function selectNode(node) {
  29993. var e;
  29994. fakeCaret.hide();
  29995. e = editor.fire('BeforeObjectSelected', {target: node});
  29996. if (e.isDefaultPrevented()) {
  29997. return null;
  29998. }
  29999. return getNodeRange(node);
  30000. }
  30001. function getNodeRange(node) {
  30002. var rng = node.ownerDocument.createRange();
  30003. rng.selectNode(node);
  30004. return rng;
  30005. }
  30006. function isMoveInsideSameBlock(fromCaretPosition, toCaretPosition) {
  30007. var inSameBlock = CaretUtils.isInSameBlock(fromCaretPosition, toCaretPosition);
  30008. // Handle bogus BR <p>abc|<br></p>
  30009. if (!inSameBlock && NodeType.isBr(fromCaretPosition.getNode())) {
  30010. return true;
  30011. }
  30012. return inSameBlock;
  30013. }
  30014. function getNormalizedRangeEndPoint(direction, range) {
  30015. range = CaretUtils.normalizeRange(direction, rootNode, range);
  30016. if (direction == -1) {
  30017. return CaretPosition.fromRangeStart(range);
  30018. }
  30019. return CaretPosition.fromRangeEnd(range);
  30020. }
  30021. function isRangeInCaretContainerBlock(range) {
  30022. return CaretContainer.isCaretContainerBlock(range.startContainer);
  30023. }
  30024. function moveToCeFalseHorizontally(direction, getNextPosFn, isBeforeContentEditableFalseFn, range) {
  30025. var node, caretPosition, peekCaretPosition, rangeIsInContainerBlock;
  30026. if (!range.collapsed) {
  30027. node = getSelectedNode(range);
  30028. if (isContentEditableFalse(node)) {
  30029. return showCaret(direction, node, direction == -1);
  30030. }
  30031. }
  30032. rangeIsInContainerBlock = isRangeInCaretContainerBlock(range);
  30033. caretPosition = getNormalizedRangeEndPoint(direction, range);
  30034. if (isBeforeContentEditableFalseFn(caretPosition)) {
  30035. return selectNode(caretPosition.getNode(direction == -1));
  30036. }
  30037. caretPosition = getNextPosFn(caretPosition);
  30038. if (!caretPosition) {
  30039. if (rangeIsInContainerBlock) {
  30040. return range;
  30041. }
  30042. return null;
  30043. }
  30044. if (isBeforeContentEditableFalseFn(caretPosition)) {
  30045. return showCaret(direction, caretPosition.getNode(direction == -1), direction == 1);
  30046. }
  30047. // Peek ahead for handling of ab|c<span cE=false> -> abc|<span cE=false>
  30048. peekCaretPosition = getNextPosFn(caretPosition);
  30049. if (isBeforeContentEditableFalseFn(peekCaretPosition)) {
  30050. if (isMoveInsideSameBlock(caretPosition, peekCaretPosition)) {
  30051. return showCaret(direction, peekCaretPosition.getNode(direction == -1), direction == 1);
  30052. }
  30053. }
  30054. if (rangeIsInContainerBlock) {
  30055. return renderRangeCaret(caretPosition.toRange());
  30056. }
  30057. return null;
  30058. }
  30059. function moveToCeFalseVertically(direction, walkerFn, range) {
  30060. var caretPosition, linePositions, nextLinePositions,
  30061. closestNextLineRect, caretClientRect, clientX,
  30062. dist1, dist2, contentEditableFalseNode;
  30063. contentEditableFalseNode = getSelectedNode(range);
  30064. caretPosition = getNormalizedRangeEndPoint(direction, range);
  30065. linePositions = walkerFn(rootNode, LineWalker.isAboveLine(1), caretPosition);
  30066. nextLinePositions = Arr.filter(linePositions, LineWalker.isLine(1));
  30067. caretClientRect = Arr.last(caretPosition.getClientRects());
  30068. if (isBeforeContentEditableFalse(caretPosition)) {
  30069. contentEditableFalseNode = caretPosition.getNode();
  30070. }
  30071. if (isAfterContentEditableFalse(caretPosition)) {
  30072. contentEditableFalseNode = caretPosition.getNode(true);
  30073. }
  30074. if (!caretClientRect) {
  30075. return null;
  30076. }
  30077. clientX = caretClientRect.left;
  30078. closestNextLineRect = LineUtils.findClosestClientRect(nextLinePositions, clientX);
  30079. if (closestNextLineRect) {
  30080. if (isContentEditableFalse(closestNextLineRect.node)) {
  30081. dist1 = Math.abs(clientX - closestNextLineRect.left);
  30082. dist2 = Math.abs(clientX - closestNextLineRect.right);
  30083. return showCaret(direction, closestNextLineRect.node, dist1 < dist2);
  30084. }
  30085. }
  30086. if (contentEditableFalseNode) {
  30087. var caretPositions = LineWalker.positionsUntil(direction, rootNode, LineWalker.isAboveLine(1), contentEditableFalseNode);
  30088. closestNextLineRect = LineUtils.findClosestClientRect(Arr.filter(caretPositions, LineWalker.isLine(1)), clientX);
  30089. if (closestNextLineRect) {
  30090. return renderRangeCaret(closestNextLineRect.position.toRange());
  30091. }
  30092. closestNextLineRect = Arr.last(Arr.filter(caretPositions, LineWalker.isLine(0)));
  30093. if (closestNextLineRect) {
  30094. return renderRangeCaret(closestNextLineRect.position.toRange());
  30095. }
  30096. }
  30097. }
  30098. function exitPreBlock(direction, range) {
  30099. var pre, caretPos, newBlock;
  30100. function createTextBlock() {
  30101. var textBlock = editor.dom.create(editor.settings.forced_root_block);
  30102. if (!Env.ie || Env.ie >= 11) {
  30103. textBlock.innerHTML = '<br data-mce-bogus="1">';
  30104. }
  30105. return textBlock;
  30106. }
  30107. if (range.collapsed && editor.settings.forced_root_block) {
  30108. pre = editor.dom.getParent(range.startContainer, 'PRE');
  30109. if (!pre) {
  30110. return;
  30111. }
  30112. if (direction == 1) {
  30113. caretPos = getNextVisualCaretPosition(CaretPosition.fromRangeStart(range));
  30114. } else {
  30115. caretPos = getPrevVisualCaretPosition(CaretPosition.fromRangeStart(range));
  30116. }
  30117. if (!caretPos) {
  30118. newBlock = createTextBlock();
  30119. if (direction == 1) {
  30120. editor.$(pre).after(newBlock);
  30121. } else {
  30122. editor.$(pre).before(newBlock);
  30123. }
  30124. editor.selection.select(newBlock, true);
  30125. editor.selection.collapse();
  30126. }
  30127. }
  30128. }
  30129. function moveH(direction, getNextPosFn, isBeforeContentEditableFalseFn, range) {
  30130. var newRange;
  30131. newRange = moveToCeFalseHorizontally(direction, getNextPosFn, isBeforeContentEditableFalseFn, range);
  30132. if (newRange) {
  30133. return newRange;
  30134. }
  30135. newRange = exitPreBlock(direction, range);
  30136. if (newRange) {
  30137. return newRange;
  30138. }
  30139. return null;
  30140. }
  30141. function moveV(direction, walkerFn, range) {
  30142. var newRange;
  30143. newRange = moveToCeFalseVertically(direction, walkerFn, range);
  30144. if (newRange) {
  30145. return newRange;
  30146. }
  30147. newRange = exitPreBlock(direction, range);
  30148. if (newRange) {
  30149. return newRange;
  30150. }
  30151. return null;
  30152. }
  30153. function getBlockCaretContainer() {
  30154. return $('*[data-mce-caret]')[0];
  30155. }
  30156. function showBlockCaretContainer(blockCaretContainer) {
  30157. blockCaretContainer = $(blockCaretContainer);
  30158. if (blockCaretContainer.attr('data-mce-caret')) {
  30159. fakeCaret.hide();
  30160. blockCaretContainer.removeAttr('data-mce-caret');
  30161. blockCaretContainer.removeAttr('data-mce-bogus');
  30162. blockCaretContainer.removeAttr('style');
  30163. // Removes control rect on IE
  30164. setRange(getRange());
  30165. scrollIntoView(blockCaretContainer[0]);
  30166. }
  30167. }
  30168. function renderCaretAtRange(range) {
  30169. var caretPosition, ceRoot;
  30170. range = CaretUtils.normalizeRange(1, rootNode, range);
  30171. caretPosition = CaretPosition.fromRangeStart(range);
  30172. if (isContentEditableFalse(caretPosition.getNode())) {
  30173. return showCaret(1, caretPosition.getNode(), !caretPosition.isAtEnd());
  30174. }
  30175. if (isContentEditableFalse(caretPosition.getNode(true))) {
  30176. return showCaret(1, caretPosition.getNode(true), false);
  30177. }
  30178. // TODO: Should render caret before/after depending on where you click on the page forces after now
  30179. ceRoot = editor.dom.getParent(caretPosition.getNode(), Fun.or(isContentEditableFalse, isContentEditableTrue));
  30180. if (isContentEditableFalse(ceRoot)) {
  30181. return showCaret(1, ceRoot, false);
  30182. }
  30183. fakeCaret.hide();
  30184. return null;
  30185. }
  30186. function renderRangeCaret(range) {
  30187. var caretRange;
  30188. if (!range || !range.collapsed) {
  30189. return range;
  30190. }
  30191. caretRange = renderCaretAtRange(range);
  30192. if (caretRange) {
  30193. return caretRange;
  30194. }
  30195. return range;
  30196. }
  30197. function deleteContentEditableNode(node) {
  30198. var nextCaretPosition, prevCaretPosition, prevCeFalseElm, nextElement;
  30199. if (!isContentEditableFalse(node)) {
  30200. return null;
  30201. }
  30202. if (isContentEditableFalse(node.previousSibling)) {
  30203. prevCeFalseElm = node.previousSibling;
  30204. }
  30205. prevCaretPosition = getPrevVisualCaretPosition(CaretPosition.before(node));
  30206. if (!prevCaretPosition) {
  30207. nextCaretPosition = getNextVisualCaretPosition(CaretPosition.after(node));
  30208. }
  30209. if (nextCaretPosition && isElement(nextCaretPosition.getNode())) {
  30210. nextElement = nextCaretPosition.getNode();
  30211. }
  30212. CaretContainer.remove(node.previousSibling);
  30213. CaretContainer.remove(node.nextSibling);
  30214. editor.dom.remove(node);
  30215. clearContentEditableSelection();
  30216. if (editor.dom.isEmpty(editor.getBody())) {
  30217. editor.setContent('');
  30218. editor.focus();
  30219. return;
  30220. }
  30221. if (prevCeFalseElm) {
  30222. return CaretPosition.after(prevCeFalseElm).toRange();
  30223. }
  30224. if (nextElement) {
  30225. return CaretPosition.before(nextElement).toRange();
  30226. }
  30227. if (prevCaretPosition) {
  30228. return prevCaretPosition.toRange();
  30229. }
  30230. if (nextCaretPosition) {
  30231. return nextCaretPosition.toRange();
  30232. }
  30233. return null;
  30234. }
  30235. function mergeTextBlocks(direction, fromCaretPosition, toCaretPosition) {
  30236. var dom = editor.dom, fromBlock, toBlock, node, textBlocks;
  30237. if (direction === -1) {
  30238. if (isAfterContentEditableFalse(toCaretPosition) && isBlock(toCaretPosition.getNode(true))) {
  30239. return deleteContentEditableNode(toCaretPosition.getNode(true));
  30240. }
  30241. } else {
  30242. if (isBeforeContentEditableFalse(fromCaretPosition) && isBlock(fromCaretPosition.getNode())) {
  30243. return deleteContentEditableNode(fromCaretPosition.getNode());
  30244. }
  30245. }
  30246. textBlocks = editor.schema.getTextBlockElements();
  30247. fromBlock = dom.getParent(fromCaretPosition.getNode(), dom.isBlock);
  30248. toBlock = dom.getParent(toCaretPosition.getNode(), dom.isBlock);
  30249. // Verify that both blocks are text blocks
  30250. if (fromBlock === toBlock || !textBlocks[fromBlock.nodeName] || !textBlocks[toBlock.nodeName]) {
  30251. return null;
  30252. }
  30253. while ((node = fromBlock.firstChild)) {
  30254. toBlock.appendChild(node);
  30255. }
  30256. editor.dom.remove(fromBlock);
  30257. return toCaretPosition.toRange();
  30258. }
  30259. function backspaceDelete(direction, beforeFn, afterFn, range) {
  30260. var node, caretPosition, peekCaretPosition, newCaretPosition;
  30261. if (!range.collapsed) {
  30262. node = getSelectedNode(range);
  30263. if (isContentEditableFalse(node)) {
  30264. return renderRangeCaret(deleteContentEditableNode(node));
  30265. }
  30266. }
  30267. caretPosition = getNormalizedRangeEndPoint(direction, range);
  30268. if (afterFn(caretPosition) && CaretContainer.isCaretContainerBlock(range.startContainer)) {
  30269. newCaretPosition = direction == -1 ? caretWalker.prev(caretPosition) : caretWalker.next(caretPosition);
  30270. return newCaretPosition ? renderRangeCaret(newCaretPosition.toRange()) : range;
  30271. }
  30272. if (beforeFn(caretPosition)) {
  30273. return renderRangeCaret(deleteContentEditableNode(caretPosition.getNode(direction == -1)));
  30274. }
  30275. peekCaretPosition = direction == -1 ? caretWalker.prev(caretPosition) : caretWalker.next(caretPosition);
  30276. if (beforeFn(peekCaretPosition)) {
  30277. if (direction === -1) {
  30278. return mergeTextBlocks(direction, caretPosition, peekCaretPosition);
  30279. }
  30280. return mergeTextBlocks(direction, peekCaretPosition, caretPosition);
  30281. }
  30282. }
  30283. function registerEvents() {
  30284. var right = curry(moveH, 1, getNextVisualCaretPosition, isBeforeContentEditableFalse);
  30285. var left = curry(moveH, -1, getPrevVisualCaretPosition, isAfterContentEditableFalse);
  30286. var deleteForward = curry(backspaceDelete, 1, isBeforeContentEditableFalse, isAfterContentEditableFalse);
  30287. var backspace = curry(backspaceDelete, -1, isAfterContentEditableFalse, isBeforeContentEditableFalse);
  30288. var up = curry(moveV, -1, LineWalker.upUntil);
  30289. var down = curry(moveV, 1, LineWalker.downUntil);
  30290. function override(evt, moveFn) {
  30291. var range = moveFn(getRange());
  30292. if (range && !evt.isDefaultPrevented()) {
  30293. evt.preventDefault();
  30294. setRange(range);
  30295. }
  30296. }
  30297. function getContentEditableRoot(node) {
  30298. var root = editor.getBody();
  30299. while (node && node != root) {
  30300. if (isContentEditableTrue(node) || isContentEditableFalse(node)) {
  30301. return node;
  30302. }
  30303. node = node.parentNode;
  30304. }
  30305. return null;
  30306. }
  30307. function isXYWithinRange(clientX, clientY, range) {
  30308. if (range.collapsed) {
  30309. return false;
  30310. }
  30311. return Arr.reduce(range.getClientRects(), function(state, rect) {
  30312. return state || ClientRect.containsXY(rect, clientX, clientY);
  30313. }, false);
  30314. }
  30315. // Some browsers (Chrome) lets you place the caret after a cE=false
  30316. // Make sure we render the caret container in this case
  30317. editor.on('mouseup', function() {
  30318. var range = getRange();
  30319. if (range.collapsed) {
  30320. setRange(renderCaretAtRange(range));
  30321. }
  30322. });
  30323. editor.on('click', function(e) {
  30324. var contentEditableRoot;
  30325. // Prevent clicks on links in a cE=false element
  30326. contentEditableRoot = getContentEditableRoot(e.target);
  30327. if (contentEditableRoot) {
  30328. if (isContentEditableFalse(contentEditableRoot)) {
  30329. e.preventDefault();
  30330. }
  30331. }
  30332. });
  30333. function handleTouchSelect(editor) {
  30334. var moved = false;
  30335. editor.on('touchstart', function () {
  30336. moved = false;
  30337. });
  30338. editor.on('touchmove', function () {
  30339. moved = true;
  30340. });
  30341. editor.on('touchend', function (e) {
  30342. var contentEditableRoot = getContentEditableRoot(e.target);
  30343. if (isContentEditableFalse(contentEditableRoot)) {
  30344. if (!moved) {
  30345. e.preventDefault();
  30346. setContentEditableSelection(selectNode(contentEditableRoot));
  30347. }
  30348. } else {
  30349. clearContentEditableSelection();
  30350. }
  30351. });
  30352. }
  30353. var hasNormalCaretPosition = function (elm) {
  30354. var caretWalker = new CaretWalker(elm);
  30355. if (!elm.firstChild) {
  30356. return false;
  30357. }
  30358. var startPos = CaretPosition.before(elm.firstChild);
  30359. var newPos = caretWalker.next(startPos);
  30360. return newPos && !isBeforeContentEditableFalse(newPos) && !isAfterContentEditableFalse(newPos);
  30361. };
  30362. var isInSameBlock = function (node1, node2) {
  30363. var block1 = editor.dom.getParent(node1, editor.dom.isBlock);
  30364. var block2 = editor.dom.getParent(node2, editor.dom.isBlock);
  30365. return block1 === block2;
  30366. };
  30367. // Checks if the target node is in a block and if that block has a caret position better than the
  30368. // suggested caretNode this is to prevent the caret from being sucked in towards a cE=false block if
  30369. // they are adjacent on the vertical axis
  30370. var hasBetterMouseTarget = function (targetNode, caretNode) {
  30371. var targetBlock = editor.dom.getParent(targetNode, editor.dom.isBlock);
  30372. var caretBlock = editor.dom.getParent(caretNode, editor.dom.isBlock);
  30373. return targetBlock && !isInSameBlock(targetBlock, caretBlock) && hasNormalCaretPosition(targetBlock);
  30374. };
  30375. handleTouchSelect(editor);
  30376. editor.on('mousedown', function(e) {
  30377. var contentEditableRoot;
  30378. contentEditableRoot = getContentEditableRoot(e.target);
  30379. if (contentEditableRoot) {
  30380. if (isContentEditableFalse(contentEditableRoot)) {
  30381. e.preventDefault();
  30382. setContentEditableSelection(selectNode(contentEditableRoot));
  30383. } else {
  30384. clearContentEditableSelection();
  30385. if (!isXYWithinRange(e.clientX, e.clientY, editor.selection.getRng())) {
  30386. editor.selection.placeCaretAt(e.clientX, e.clientY);
  30387. }
  30388. }
  30389. } else {
  30390. clearContentEditableSelection();
  30391. fakeCaret.hide();
  30392. var caretInfo = LineUtils.closestCaret(rootNode, e.clientX, e.clientY);
  30393. if (caretInfo) {
  30394. if (!hasBetterMouseTarget(e.target, caretInfo.node)) {
  30395. e.preventDefault();
  30396. editor.getBody().focus();
  30397. setRange(showCaret(1, caretInfo.node, caretInfo.before));
  30398. }
  30399. }
  30400. }
  30401. });
  30402. editor.on('keydown', function(e) {
  30403. if (VK.modifierPressed(e)) {
  30404. return;
  30405. }
  30406. switch (e.keyCode) {
  30407. case VK.RIGHT:
  30408. override(e, right);
  30409. break;
  30410. case VK.DOWN:
  30411. override(e, down);
  30412. break;
  30413. case VK.LEFT:
  30414. override(e, left);
  30415. break;
  30416. case VK.UP:
  30417. override(e, up);
  30418. break;
  30419. case VK.DELETE:
  30420. override(e, deleteForward);
  30421. break;
  30422. case VK.BACKSPACE:
  30423. override(e, backspace);
  30424. break;
  30425. default:
  30426. if (isContentEditableFalse(editor.selection.getNode())) {
  30427. e.preventDefault();
  30428. }
  30429. break;
  30430. }
  30431. });
  30432. function paddEmptyContentEditableArea() {
  30433. var br, ceRoot = getContentEditableRoot(editor.selection.getNode());
  30434. if (isContentEditableTrue(ceRoot) && isBlock(ceRoot) && editor.dom.isEmpty(ceRoot)) {
  30435. br = editor.dom.create('br', {"data-mce-bogus": "1"});
  30436. editor.$(ceRoot).empty().append(br);
  30437. editor.selection.setRng(CaretPosition.before(br).toRange());
  30438. }
  30439. }
  30440. function handleBlockContainer(e) {
  30441. var blockCaretContainer = getBlockCaretContainer();
  30442. if (!blockCaretContainer) {
  30443. return;
  30444. }
  30445. if (e.type == 'compositionstart') {
  30446. e.preventDefault();
  30447. e.stopPropagation();
  30448. showBlockCaretContainer(blockCaretContainer);
  30449. return;
  30450. }
  30451. if (blockCaretContainer.innerHTML != '&nbsp;') {
  30452. showBlockCaretContainer(blockCaretContainer);
  30453. }
  30454. }
  30455. function handleEmptyBackspaceDelete(e) {
  30456. var prevent;
  30457. switch (e.keyCode) {
  30458. case VK.DELETE:
  30459. prevent = paddEmptyContentEditableArea();
  30460. break;
  30461. case VK.BACKSPACE:
  30462. prevent = paddEmptyContentEditableArea();
  30463. break;
  30464. }
  30465. if (prevent) {
  30466. e.preventDefault();
  30467. }
  30468. }
  30469. // Must be added to "top" since undoManager needs to be executed after
  30470. editor.on('keyup compositionstart', function(e) {
  30471. handleBlockContainer(e);
  30472. handleEmptyBackspaceDelete(e);
  30473. }, true);
  30474. editor.on('cut', function() {
  30475. var node = editor.selection.getNode();
  30476. if (isContentEditableFalse(node)) {
  30477. Delay.setEditorTimeout(editor, function() {
  30478. setRange(renderRangeCaret(deleteContentEditableNode(node)));
  30479. });
  30480. }
  30481. });
  30482. editor.on('getSelectionRange', function(e) {
  30483. var rng = e.range;
  30484. if (selectedContentEditableNode) {
  30485. if (!selectedContentEditableNode.parentNode) {
  30486. selectedContentEditableNode = null;
  30487. return;
  30488. }
  30489. rng = rng.cloneRange();
  30490. rng.selectNode(selectedContentEditableNode);
  30491. e.range = rng;
  30492. }
  30493. });
  30494. editor.on('setSelectionRange', function(e) {
  30495. var rng;
  30496. rng = setContentEditableSelection(e.range);
  30497. if (rng) {
  30498. e.range = rng;
  30499. }
  30500. });
  30501. editor.on('focus', function() {
  30502. // Make sure we have a proper fake caret on focus
  30503. Delay.setEditorTimeout(editor, function() {
  30504. editor.selection.setRng(renderRangeCaret(editor.selection.getRng()));
  30505. }, 0);
  30506. });
  30507. DragDropOverrides.init(editor);
  30508. }
  30509. function addCss() {
  30510. var styles = editor.contentStyles, rootClass = '.mce-content-body';
  30511. styles.push(fakeCaret.getCss());
  30512. styles.push(
  30513. rootClass + ' .mce-offscreen-selection {' +
  30514. 'position: absolute;' +
  30515. 'left: -9999999999px;' +
  30516. 'width: 100px;' +
  30517. 'height: 100px;' +
  30518. '}' +
  30519. rootClass + ' *[contentEditable=false] {' +
  30520. 'cursor: default;' +
  30521. '}' +
  30522. rootClass + ' *[contentEditable=true] {' +
  30523. 'cursor: text;' +
  30524. '}'
  30525. );
  30526. }
  30527. function isRangeInCaretContainer(rng) {
  30528. return CaretContainer.isCaretContainer(rng.startContainer) || CaretContainer.isCaretContainer(rng.endContainer);
  30529. }
  30530. function setContentEditableSelection(range) {
  30531. var node, $ = editor.$, dom = editor.dom, $realSelectionContainer, sel,
  30532. startContainer, startOffset, endOffset, e, caretPosition, targetClone, origTargetClone;
  30533. if (!range) {
  30534. clearContentEditableSelection();
  30535. return null;
  30536. }
  30537. if (range.collapsed) {
  30538. clearContentEditableSelection();
  30539. if (!isRangeInCaretContainer(range)) {
  30540. caretPosition = getNormalizedRangeEndPoint(1, range);
  30541. if (isContentEditableFalse(caretPosition.getNode())) {
  30542. return showCaret(1, caretPosition.getNode(), !caretPosition.isAtEnd());
  30543. }
  30544. if (isContentEditableFalse(caretPosition.getNode(true))) {
  30545. return showCaret(1, caretPosition.getNode(true), false);
  30546. }
  30547. }
  30548. return null;
  30549. }
  30550. startContainer = range.startContainer;
  30551. startOffset = range.startOffset;
  30552. endOffset = range.endOffset;
  30553. // Normalizes <span cE=false>[</span>] to [<span cE=false></span>]
  30554. if (startContainer.nodeType == 3 && startOffset == 0 && isContentEditableFalse(startContainer.parentNode)) {
  30555. startContainer = startContainer.parentNode;
  30556. startOffset = dom.nodeIndex(startContainer);
  30557. startContainer = startContainer.parentNode;
  30558. }
  30559. if (startContainer.nodeType != 1) {
  30560. clearContentEditableSelection();
  30561. return null;
  30562. }
  30563. if (endOffset == startOffset + 1) {
  30564. node = startContainer.childNodes[startOffset];
  30565. }
  30566. if (!isContentEditableFalse(node)) {
  30567. clearContentEditableSelection();
  30568. return null;
  30569. }
  30570. targetClone = origTargetClone = node.cloneNode(true);
  30571. e = editor.fire('ObjectSelected', {target: node, targetClone: targetClone});
  30572. if (e.isDefaultPrevented()) {
  30573. clearContentEditableSelection();
  30574. return null;
  30575. }
  30576. targetClone = e.targetClone;
  30577. $realSelectionContainer = $('#' + realSelectionId);
  30578. if ($realSelectionContainer.length === 0) {
  30579. $realSelectionContainer = $(
  30580. '<div data-mce-bogus="all" class="mce-offscreen-selection"></div>'
  30581. ).attr('id', realSelectionId);
  30582. $realSelectionContainer.appendTo(editor.getBody());
  30583. }
  30584. range = editor.dom.createRng();
  30585. // WHY is IE making things so hard! Copy on <i contentEditable="false">x</i> produces: <em>x</em>
  30586. if (targetClone === origTargetClone && Env.ie) {
  30587. $realSelectionContainer.empty().append(Zwsp.ZWSP).append(targetClone).append(Zwsp.ZWSP);
  30588. range.setStart($realSelectionContainer[0].firstChild, 0);
  30589. range.setEnd($realSelectionContainer[0].lastChild, 1);
  30590. } else {
  30591. $realSelectionContainer.empty().append('\u00a0').append(targetClone).append('\u00a0');
  30592. range.setStart($realSelectionContainer[0].firstChild, 1);
  30593. range.setEnd($realSelectionContainer[0].lastChild, 0);
  30594. }
  30595. $realSelectionContainer.css({
  30596. top: dom.getPos(node, editor.getBody()).y
  30597. });
  30598. $realSelectionContainer[0].focus();
  30599. sel = editor.selection.getSel();
  30600. sel.removeAllRanges();
  30601. sel.addRange(range);
  30602. editor.$('*[data-mce-selected]').removeAttr('data-mce-selected');
  30603. node.setAttribute('data-mce-selected', 1);
  30604. selectedContentEditableNode = node;
  30605. return range;
  30606. }
  30607. function clearContentEditableSelection() {
  30608. if (selectedContentEditableNode) {
  30609. selectedContentEditableNode.removeAttribute('data-mce-selected');
  30610. editor.$('#' + realSelectionId).remove();
  30611. selectedContentEditableNode = null;
  30612. }
  30613. }
  30614. function destroy() {
  30615. fakeCaret.destroy();
  30616. selectedContentEditableNode = null;
  30617. }
  30618. function hideFakeCaret() {
  30619. fakeCaret.hide();
  30620. }
  30621. if (Env.ceFalse) {
  30622. registerEvents();
  30623. addCss();
  30624. }
  30625. return {
  30626. showBlockCaretContainer: showBlockCaretContainer,
  30627. hideFakeCaret: hideFakeCaret,
  30628. destroy: destroy
  30629. };
  30630. }
  30631. return SelectionOverrides;
  30632. });
  30633. // Included from: js/tinymce/classes/util/Uuid.js
  30634. /**
  30635. * Uuid.js
  30636. *
  30637. * Released under LGPL License.
  30638. * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
  30639. *
  30640. * License: http://www.tinymce.com/license
  30641. * Contributing: http://www.tinymce.com/contributing
  30642. */
  30643. /**
  30644. * Generates unique ids.
  30645. *
  30646. * @class tinymce.util.Uuid
  30647. * @private
  30648. */
  30649. define("tinymce/util/Uuid", [
  30650. ], function() {
  30651. var count = 0;
  30652. var seed = function () {
  30653. var rnd = function () {
  30654. return Math.round(Math.random() * 0xFFFFFFFF).toString(36);
  30655. };
  30656. var now = new Date().getTime();
  30657. return 's' + now.toString(36) + rnd() + rnd() + rnd();
  30658. };
  30659. var uuid = function (prefix) {
  30660. return prefix + (count++) + seed();
  30661. };
  30662. return {
  30663. uuid: uuid
  30664. };
  30665. });
  30666. // Included from: js/tinymce/classes/Editor.js
  30667. /**
  30668. * Editor.js
  30669. *
  30670. * Released under LGPL License.
  30671. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  30672. *
  30673. * License: http://www.tinymce.com/license
  30674. * Contributing: http://www.tinymce.com/contributing
  30675. */
  30676. /*jshint scripturl:true */
  30677. /**
  30678. * Include the base event class documentation.
  30679. *
  30680. * @include ../../../tools/docs/tinymce.Event.js
  30681. */
  30682. /**
  30683. * This class contains the core logic for a TinyMCE editor.
  30684. *
  30685. * @class tinymce.Editor
  30686. * @mixes tinymce.util.Observable
  30687. * @example
  30688. * // Add a class to all paragraphs in the editor.
  30689. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  30690. *
  30691. * // Gets the current editors selection as text
  30692. * tinymce.activeEditor.selection.getContent({format: 'text'});
  30693. *
  30694. * // Creates a new editor instance
  30695. * var ed = new tinymce.Editor('textareaid', {
  30696. * some_setting: 1
  30697. * }, tinymce.EditorManager);
  30698. *
  30699. * // Select each item the user clicks on
  30700. * ed.on('click', function(e) {
  30701. * ed.selection.select(e.target);
  30702. * });
  30703. *
  30704. * ed.render();
  30705. */
  30706. define("tinymce/Editor", [
  30707. "tinymce/dom/DOMUtils",
  30708. "tinymce/dom/DomQuery",
  30709. "tinymce/AddOnManager",
  30710. "tinymce/NodeChange",
  30711. "tinymce/html/Node",
  30712. "tinymce/dom/Serializer",
  30713. "tinymce/html/Serializer",
  30714. "tinymce/dom/Selection",
  30715. "tinymce/Formatter",
  30716. "tinymce/UndoManager",
  30717. "tinymce/EnterKey",
  30718. "tinymce/ForceBlocks",
  30719. "tinymce/EditorCommands",
  30720. "tinymce/util/URI",
  30721. "tinymce/dom/ScriptLoader",
  30722. "tinymce/dom/EventUtils",
  30723. "tinymce/WindowManager",
  30724. "tinymce/NotificationManager",
  30725. "tinymce/html/Schema",
  30726. "tinymce/html/DomParser",
  30727. "tinymce/util/Quirks",
  30728. "tinymce/Env",
  30729. "tinymce/util/Tools",
  30730. "tinymce/util/Delay",
  30731. "tinymce/EditorObservable",
  30732. "tinymce/Mode",
  30733. "tinymce/Shortcuts",
  30734. "tinymce/EditorUpload",
  30735. "tinymce/SelectionOverrides",
  30736. "tinymce/util/Uuid"
  30737. ], function(
  30738. DOMUtils, DomQuery, AddOnManager, NodeChange, Node, DomSerializer, Serializer,
  30739. Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
  30740. URI, ScriptLoader, EventUtils, WindowManager, NotificationManager,
  30741. Schema, DomParser, Quirks, Env, Tools, Delay, EditorObservable, Mode, Shortcuts, EditorUpload,
  30742. SelectionOverrides, Uuid
  30743. ) {
  30744. // Shorten these names
  30745. var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
  30746. var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
  30747. var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
  30748. var Event = EventUtils.Event;
  30749. var isGecko = Env.gecko, ie = Env.ie;
  30750. /**
  30751. * Include documentation for all the events.
  30752. *
  30753. * @include ../../../tools/docs/tinymce.Editor.js
  30754. */
  30755. /**
  30756. * Constructs a editor instance by id.
  30757. *
  30758. * @constructor
  30759. * @method Editor
  30760. * @param {String} id Unique id for the editor.
  30761. * @param {Object} settings Settings for the editor.
  30762. * @param {tinymce.EditorManager} editorManager EditorManager instance.
  30763. */
  30764. function Editor(id, settings, editorManager) {
  30765. var self = this, documentBaseUrl, baseUri, defaultSettings;
  30766. documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
  30767. baseUri = editorManager.baseURI;
  30768. defaultSettings = editorManager.defaultSettings;
  30769. /**
  30770. * Name/value collection with editor settings.
  30771. *
  30772. * @property settings
  30773. * @type Object
  30774. * @example
  30775. * // Get the value of the theme setting
  30776. * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
  30777. */
  30778. settings = extend({
  30779. id: id,
  30780. theme: 'modern',
  30781. delta_width: 0,
  30782. delta_height: 0,
  30783. popup_css: '',
  30784. plugins: '',
  30785. document_base_url: documentBaseUrl,
  30786. add_form_submit_trigger: true,
  30787. submit_patch: true,
  30788. add_unload_trigger: true,
  30789. convert_urls: true,
  30790. relative_urls: true,
  30791. remove_script_host: true,
  30792. object_resizing: true,
  30793. doctype: '<!DOCTYPE html>',
  30794. visual: true,
  30795. font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
  30796. // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
  30797. font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
  30798. forced_root_block: 'p',
  30799. hidden_input: true,
  30800. padd_empty_editor: true,
  30801. render_ui: true,
  30802. indentation: '30px',
  30803. inline_styles: true,
  30804. convert_fonts_to_spans: true,
  30805. indent: 'simple',
  30806. indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
  30807. 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
  30808. indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
  30809. 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
  30810. validate: true,
  30811. entity_encoding: 'named',
  30812. url_converter: self.convertURL,
  30813. url_converter_scope: self,
  30814. ie7_compat: true
  30815. }, defaultSettings, settings);
  30816. // Merge external_plugins
  30817. if (defaultSettings && defaultSettings.external_plugins && settings.external_plugins) {
  30818. settings.external_plugins = extend({}, defaultSettings.external_plugins, settings.external_plugins);
  30819. }
  30820. self.settings = settings;
  30821. AddOnManager.language = settings.language || 'en';
  30822. AddOnManager.languageLoad = settings.language_load;
  30823. AddOnManager.baseURL = editorManager.baseURL;
  30824. /**
  30825. * Editor instance id, normally the same as the div/textarea that was replaced.
  30826. *
  30827. * @property id
  30828. * @type String
  30829. */
  30830. self.id = settings.id = id;
  30831. /**
  30832. * State to force the editor to return false on a isDirty call.
  30833. *
  30834. * @property isNotDirty
  30835. * @type Boolean
  30836. * @deprecated Use editor.setDirty instead.
  30837. */
  30838. self.setDirty(false);
  30839. /**
  30840. * Name/Value object containing plugin instances.
  30841. *
  30842. * @property plugins
  30843. * @type Object
  30844. * @example
  30845. * // Execute a method inside a plugin directly
  30846. * tinymce.activeEditor.plugins.someplugin.someMethod();
  30847. */
  30848. self.plugins = {};
  30849. /**
  30850. * URI object to document configured for the TinyMCE instance.
  30851. *
  30852. * @property documentBaseURI
  30853. * @type tinymce.util.URI
  30854. * @example
  30855. * // Get relative URL from the location of document_base_url
  30856. * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
  30857. *
  30858. * // Get absolute URL from the location of document_base_url
  30859. * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
  30860. */
  30861. self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
  30862. base_uri: baseUri
  30863. });
  30864. /**
  30865. * URI object to current document that holds the TinyMCE editor instance.
  30866. *
  30867. * @property baseURI
  30868. * @type tinymce.util.URI
  30869. * @example
  30870. * // Get relative URL from the location of the API
  30871. * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
  30872. *
  30873. * // Get absolute URL from the location of the API
  30874. * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
  30875. */
  30876. self.baseURI = baseUri;
  30877. /**
  30878. * Array with CSS files to load into the iframe.
  30879. *
  30880. * @property contentCSS
  30881. * @type Array
  30882. */
  30883. self.contentCSS = [];
  30884. /**
  30885. * Array of CSS styles to add to head of document when the editor loads.
  30886. *
  30887. * @property contentStyles
  30888. * @type Array
  30889. */
  30890. self.contentStyles = [];
  30891. // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
  30892. self.shortcuts = new Shortcuts(self);
  30893. self.loadedCSS = {};
  30894. self.editorCommands = new EditorCommands(self);
  30895. if (settings.target) {
  30896. self.targetElm = settings.target;
  30897. }
  30898. self.suffix = editorManager.suffix;
  30899. self.editorManager = editorManager;
  30900. self.inline = settings.inline;
  30901. self.settings.content_editable = self.inline;
  30902. if (settings.cache_suffix) {
  30903. Env.cacheSuffix = settings.cache_suffix.replace(/^[\?\&]+/, '');
  30904. }
  30905. if (settings.override_viewport === false) {
  30906. Env.overrideViewPort = false;
  30907. }
  30908. // Call setup
  30909. editorManager.fire('SetupEditor', self);
  30910. self.execCallback('setup', self);
  30911. /**
  30912. * Dom query instance with default scope to the editor document and default element is the body of the editor.
  30913. *
  30914. * @property $
  30915. * @type tinymce.dom.DomQuery
  30916. * @example
  30917. * tinymce.activeEditor.$('p').css('color', 'red');
  30918. * tinymce.activeEditor.$().append('<p>new</p>');
  30919. */
  30920. self.$ = DomQuery.overrideDefaults(function() {
  30921. return {
  30922. context: self.inline ? self.getBody() : self.getDoc(),
  30923. element: self.getBody()
  30924. };
  30925. });
  30926. }
  30927. Editor.prototype = {
  30928. /**
  30929. * Renders the editor/adds it to the page.
  30930. *
  30931. * @method render
  30932. */
  30933. render: function() {
  30934. var self = this, settings = self.settings, id = self.id, suffix = self.suffix;
  30935. function readyHandler() {
  30936. DOM.unbind(window, 'ready', readyHandler);
  30937. self.render();
  30938. }
  30939. // Page is not loaded yet, wait for it
  30940. if (!Event.domLoaded) {
  30941. DOM.bind(window, 'ready', readyHandler);
  30942. return;
  30943. }
  30944. // Element not found, then skip initialization
  30945. if (!self.getElement()) {
  30946. return;
  30947. }
  30948. // No editable support old iOS versions etc
  30949. if (!Env.contentEditable) {
  30950. return;
  30951. }
  30952. // Hide target element early to prevent content flashing
  30953. if (!settings.inline) {
  30954. self.orgVisibility = self.getElement().style.visibility;
  30955. self.getElement().style.visibility = 'hidden';
  30956. } else {
  30957. self.inline = true;
  30958. }
  30959. var form = self.getElement().form || DOM.getParent(id, 'form');
  30960. if (form) {
  30961. self.formElement = form;
  30962. // Add hidden input for non input elements inside form elements
  30963. if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
  30964. DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
  30965. self.hasHiddenInput = true;
  30966. }
  30967. // Pass submit/reset from form to editor instance
  30968. self.formEventDelegate = function(e) {
  30969. self.fire(e.type, e);
  30970. };
  30971. DOM.bind(form, 'submit reset', self.formEventDelegate);
  30972. // Reset contents in editor when the form is reset
  30973. self.on('reset', function() {
  30974. self.setContent(self.startContent, {format: 'raw'});
  30975. });
  30976. // Check page uses id="submit" or name="submit" for it's submit button
  30977. if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
  30978. form._mceOldSubmit = form.submit;
  30979. form.submit = function() {
  30980. self.editorManager.triggerSave();
  30981. self.setDirty(false);
  30982. return form._mceOldSubmit(form);
  30983. };
  30984. }
  30985. }
  30986. /**
  30987. * Window manager reference, use this to open new windows and dialogs.
  30988. *
  30989. * @property windowManager
  30990. * @type tinymce.WindowManager
  30991. * @example
  30992. * // Shows an alert message
  30993. * tinymce.activeEditor.windowManager.alert('Hello world!');
  30994. *
  30995. * // Opens a new dialog with the file.htm file and the size 320x240
  30996. * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
  30997. * tinymce.activeEditor.windowManager.open({
  30998. * url: 'file.htm',
  30999. * width: 320,
  31000. * height: 240
  31001. * }, {
  31002. * custom_param: 1
  31003. * });
  31004. */
  31005. self.windowManager = new WindowManager(self);
  31006. /**
  31007. * Notification manager reference, use this to open new windows and dialogs.
  31008. *
  31009. * @property notificationManager
  31010. * @type tinymce.NotificationManager
  31011. * @example
  31012. * // Shows a notification info message.
  31013. * tinymce.activeEditor.notificationManager.open({text: 'Hello world!', type: 'info'});
  31014. */
  31015. self.notificationManager = new NotificationManager(self);
  31016. if (settings.encoding == 'xml') {
  31017. self.on('GetContent', function(e) {
  31018. if (e.save) {
  31019. e.content = DOM.encode(e.content);
  31020. }
  31021. });
  31022. }
  31023. if (settings.add_form_submit_trigger) {
  31024. self.on('submit', function() {
  31025. if (self.initialized) {
  31026. self.save();
  31027. }
  31028. });
  31029. }
  31030. if (settings.add_unload_trigger) {
  31031. self._beforeUnload = function() {
  31032. if (self.initialized && !self.destroyed && !self.isHidden()) {
  31033. self.save({format: 'raw', no_events: true, set_dirty: false});
  31034. }
  31035. };
  31036. self.editorManager.on('BeforeUnload', self._beforeUnload);
  31037. }
  31038. // Load scripts
  31039. function loadScripts() {
  31040. var scriptLoader = ScriptLoader.ScriptLoader;
  31041. if (settings.language && settings.language != 'en' && !settings.language_url) {
  31042. settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
  31043. }
  31044. if (settings.language_url) {
  31045. scriptLoader.add(settings.language_url);
  31046. }
  31047. if (settings.theme && typeof settings.theme != "function" &&
  31048. settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
  31049. var themeUrl = settings.theme_url;
  31050. if (themeUrl) {
  31051. themeUrl = self.documentBaseURI.toAbsolute(themeUrl);
  31052. } else {
  31053. themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js';
  31054. }
  31055. ThemeManager.load(settings.theme, themeUrl);
  31056. }
  31057. if (Tools.isArray(settings.plugins)) {
  31058. settings.plugins = settings.plugins.join(' ');
  31059. }
  31060. each(settings.external_plugins, function(url, name) {
  31061. PluginManager.load(name, url);
  31062. settings.plugins += ' ' + name;
  31063. });
  31064. each(settings.plugins.split(/[ ,]/), function(plugin) {
  31065. plugin = trim(plugin);
  31066. if (plugin && !PluginManager.urls[plugin]) {
  31067. if (plugin.charAt(0) == '-') {
  31068. plugin = plugin.substr(1, plugin.length);
  31069. var dependencies = PluginManager.dependencies(plugin);
  31070. each(dependencies, function(dep) {
  31071. var defaultSettings = {
  31072. prefix: 'plugins/',
  31073. resource: dep,
  31074. suffix: '/plugin' + suffix + '.js'
  31075. };
  31076. dep = PluginManager.createUrl(defaultSettings, dep);
  31077. PluginManager.load(dep.resource, dep);
  31078. });
  31079. } else {
  31080. PluginManager.load(plugin, {
  31081. prefix: 'plugins/',
  31082. resource: plugin,
  31083. suffix: '/plugin' + suffix + '.js'
  31084. });
  31085. }
  31086. }
  31087. });
  31088. scriptLoader.loadQueue(function() {
  31089. if (!self.removed) {
  31090. self.init();
  31091. }
  31092. });
  31093. }
  31094. self.editorManager.add(self);
  31095. loadScripts();
  31096. },
  31097. /**
  31098. * Initializes the editor this will be called automatically when
  31099. * all plugins/themes and language packs are loaded by the rendered method.
  31100. * This method will setup the iframe and create the theme and plugin instances.
  31101. *
  31102. * @method init
  31103. */
  31104. init: function() {
  31105. var self = this, settings = self.settings, elm = self.getElement();
  31106. var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = [];
  31107. this.editorManager.i18n.setCode(settings.language);
  31108. self.rtl = settings.rtl_ui || this.editorManager.i18n.rtl;
  31109. settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));
  31110. /**
  31111. * Reference to the theme instance that was used to generate the UI.
  31112. *
  31113. * @property theme
  31114. * @type tinymce.Theme
  31115. * @example
  31116. * // Executes a method on the theme directly
  31117. * tinymce.activeEditor.theme.someMethod();
  31118. */
  31119. if (settings.theme) {
  31120. if (typeof settings.theme != "function") {
  31121. settings.theme = settings.theme.replace(/-/, '');
  31122. Theme = ThemeManager.get(settings.theme);
  31123. self.theme = new Theme(self, ThemeManager.urls[settings.theme]);
  31124. if (self.theme.init) {
  31125. self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''), self.$);
  31126. }
  31127. } else {
  31128. self.theme = settings.theme;
  31129. }
  31130. }
  31131. function initPlugin(plugin) {
  31132. var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;
  31133. pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
  31134. plugin = trim(plugin);
  31135. if (Plugin && inArray(initializedPlugins, plugin) === -1) {
  31136. each(PluginManager.dependencies(plugin), function(dep) {
  31137. initPlugin(dep);
  31138. });
  31139. if (self.plugins[plugin]) {
  31140. return;
  31141. }
  31142. pluginInstance = new Plugin(self, pluginUrl, self.$);
  31143. self.plugins[plugin] = pluginInstance;
  31144. if (pluginInstance.init) {
  31145. pluginInstance.init(self, pluginUrl);
  31146. initializedPlugins.push(plugin);
  31147. }
  31148. }
  31149. }
  31150. // Create all plugins
  31151. each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);
  31152. // Measure box
  31153. if (settings.render_ui && self.theme) {
  31154. self.orgDisplay = elm.style.display;
  31155. if (typeof settings.theme != "function") {
  31156. w = settings.width || elm.style.width || elm.offsetWidth;
  31157. h = settings.height || elm.style.height || elm.offsetHeight;
  31158. minHeight = settings.min_height || 100;
  31159. re = /^[0-9\.]+(|px)$/i;
  31160. if (re.test('' + w)) {
  31161. w = Math.max(parseInt(w, 10), 100);
  31162. }
  31163. if (re.test('' + h)) {
  31164. h = Math.max(parseInt(h, 10), minHeight);
  31165. }
  31166. // Render UI
  31167. o = self.theme.renderUI({
  31168. targetNode: elm,
  31169. width: w,
  31170. height: h,
  31171. deltaWidth: settings.delta_width,
  31172. deltaHeight: settings.delta_height
  31173. });
  31174. // Resize editor
  31175. if (!settings.content_editable) {
  31176. h = (o.iframeHeight || h) + (typeof h == 'number' ? (o.deltaHeight || 0) : '');
  31177. if (h < minHeight) {
  31178. h = minHeight;
  31179. }
  31180. }
  31181. } else {
  31182. o = settings.theme(self, elm);
  31183. // Convert element type to id:s
  31184. if (o.editorContainer.nodeType) {
  31185. o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
  31186. }
  31187. // Convert element type to id:s
  31188. if (o.iframeContainer.nodeType) {
  31189. o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
  31190. }
  31191. // Use specified iframe height or the targets offsetHeight
  31192. h = o.iframeHeight || elm.offsetHeight;
  31193. }
  31194. self.editorContainer = o.editorContainer;
  31195. }
  31196. // Load specified content CSS last
  31197. if (settings.content_css) {
  31198. each(explode(settings.content_css), function(u) {
  31199. self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
  31200. });
  31201. }
  31202. // Load specified content CSS last
  31203. if (settings.content_style) {
  31204. self.contentStyles.push(settings.content_style);
  31205. }
  31206. // Content editable mode ends here
  31207. if (settings.content_editable) {
  31208. elm = n = o = null; // Fix IE leak
  31209. return self.initContentBody();
  31210. }
  31211. self.iframeHTML = settings.doctype + '<html><head>';
  31212. // We only need to override paths if we have to
  31213. // IE has a bug where it remove site absolute urls to relative ones if this is specified
  31214. if (settings.document_base_url != self.documentBaseUrl) {
  31215. self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
  31216. }
  31217. // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
  31218. if (!Env.caretAfter && settings.ie7_compat) {
  31219. self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
  31220. }
  31221. self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
  31222. // Load the CSS by injecting them into the HTML this will reduce "flicker"
  31223. // However we can't do that on Chrome since # will scroll to the editor for some odd reason see #2427
  31224. if (!/#$/.test(document.location.href)) {
  31225. for (i = 0; i < self.contentCSS.length; i++) {
  31226. var cssUrl = self.contentCSS[i];
  31227. self.iframeHTML += (
  31228. '<link type="text/css" ' +
  31229. 'rel="stylesheet" ' +
  31230. 'href="' + Tools._addCacheSuffix(cssUrl) + '" />'
  31231. );
  31232. self.loadedCSS[cssUrl] = true;
  31233. }
  31234. }
  31235. bodyId = settings.body_id || 'tinymce';
  31236. if (bodyId.indexOf('=') != -1) {
  31237. bodyId = self.getParam('body_id', '', 'hash');
  31238. bodyId = bodyId[self.id] || bodyId;
  31239. }
  31240. bodyClass = settings.body_class || '';
  31241. if (bodyClass.indexOf('=') != -1) {
  31242. bodyClass = self.getParam('body_class', '', 'hash');
  31243. bodyClass = bodyClass[self.id] || '';
  31244. }
  31245. if (settings.content_security_policy) {
  31246. self.iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + settings.content_security_policy + '" />';
  31247. }
  31248. self.iframeHTML += '</head><body id="' + bodyId +
  31249. '" class="mce-content-body ' + bodyClass +
  31250. '" data-id="' + self.id + '"><br></body></html>';
  31251. /*eslint no-script-url:0 */
  31252. var domainRelaxUrl = 'javascript:(function(){' +
  31253. 'document.open();document.domain="' + document.domain + '";' +
  31254. 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' +
  31255. 'document.close();ed.initContentBody(true);})()';
  31256. // Domain relaxing is required since the user has messed around with document.domain
  31257. if (document.domain != location.hostname) {
  31258. // Edge seems to be able to handle domain relaxing
  31259. if (Env.ie && Env.ie < 12) {
  31260. url = domainRelaxUrl;
  31261. }
  31262. }
  31263. // Create iframe
  31264. // TODO: ACC add the appropriate description on this.
  31265. var ifr = DOM.create('iframe', {
  31266. id: self.id + "_ifr",
  31267. //src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
  31268. frameBorder: '0',
  31269. allowTransparency: "true",
  31270. title: self.editorManager.translate(
  31271. "Rich Text Area. Press ALT-F9 for menu. " +
  31272. "Press ALT-F10 for toolbar. Press ALT-0 for help"
  31273. ),
  31274. style: {
  31275. width: '100%',
  31276. height: h,
  31277. display: 'block' // Important for Gecko to render the iframe correctly
  31278. }
  31279. });
  31280. ifr.onload = function() {
  31281. ifr.onload = null;
  31282. self.fire("load");
  31283. };
  31284. DOM.setAttrib(ifr, "src", url || 'javascript:""');
  31285. self.contentAreaContainer = o.iframeContainer;
  31286. self.iframeElement = ifr;
  31287. n = DOM.add(o.iframeContainer, ifr);
  31288. // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
  31289. // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
  31290. if (ie) {
  31291. try {
  31292. self.getDoc();
  31293. } catch (e) {
  31294. n.src = url = domainRelaxUrl;
  31295. }
  31296. }
  31297. if (o.editorContainer) {
  31298. DOM.get(o.editorContainer).style.display = self.orgDisplay;
  31299. self.hidden = DOM.isHidden(o.editorContainer);
  31300. }
  31301. self.getElement().style.display = 'none';
  31302. DOM.setAttrib(self.id, 'aria-hidden', true);
  31303. if (!url) {
  31304. self.initContentBody();
  31305. }
  31306. elm = n = o = null; // Cleanup
  31307. },
  31308. /**
  31309. * This method get called by the init method once the iframe is loaded.
  31310. * It will fill the iframe with contents, sets up DOM and selection objects for the iframe.
  31311. *
  31312. * @method initContentBody
  31313. * @private
  31314. */
  31315. initContentBody: function(skipWrite) {
  31316. var self = this, settings = self.settings, targetElm = self.getElement(), doc = self.getDoc(), body, contentCssText;
  31317. // Restore visibility on target element
  31318. if (!settings.inline) {
  31319. self.getElement().style.visibility = self.orgVisibility;
  31320. }
  31321. // Setup iframe body
  31322. if (!skipWrite && !settings.content_editable) {
  31323. doc.open();
  31324. doc.write(self.iframeHTML);
  31325. doc.close();
  31326. }
  31327. if (settings.content_editable) {
  31328. self.on('remove', function() {
  31329. var bodyEl = this.getBody();
  31330. DOM.removeClass(bodyEl, 'mce-content-body');
  31331. DOM.removeClass(bodyEl, 'mce-edit-focus');
  31332. DOM.setAttrib(bodyEl, 'contentEditable', null);
  31333. });
  31334. DOM.addClass(targetElm, 'mce-content-body');
  31335. self.contentDocument = doc = settings.content_document || document;
  31336. self.contentWindow = settings.content_window || window;
  31337. self.bodyElement = targetElm;
  31338. // Prevent leak in IE
  31339. settings.content_document = settings.content_window = null;
  31340. // TODO: Fix this
  31341. settings.root_name = targetElm.nodeName.toLowerCase();
  31342. }
  31343. // It will not steal focus while setting contentEditable
  31344. body = self.getBody();
  31345. body.disabled = true;
  31346. self.readonly = settings.readonly;
  31347. if (!self.readonly) {
  31348. if (self.inline && DOM.getStyle(body, 'position', true) == 'static') {
  31349. body.style.position = 'relative';
  31350. }
  31351. body.contentEditable = self.getParam('content_editable_state', true);
  31352. }
  31353. body.disabled = false;
  31354. self.editorUpload = new EditorUpload(self);
  31355. /**
  31356. * Schema instance, enables you to validate elements and its children.
  31357. *
  31358. * @property schema
  31359. * @type tinymce.html.Schema
  31360. */
  31361. self.schema = new Schema(settings);
  31362. /**
  31363. * DOM instance for the editor.
  31364. *
  31365. * @property dom
  31366. * @type tinymce.dom.DOMUtils
  31367. * @example
  31368. * // Adds a class to all paragraphs within the editor
  31369. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  31370. */
  31371. self.dom = new DOMUtils(doc, {
  31372. keep_values: true,
  31373. url_converter: self.convertURL,
  31374. url_converter_scope: self,
  31375. hex_colors: settings.force_hex_style_colors,
  31376. class_filter: settings.class_filter,
  31377. update_styles: true,
  31378. root_element: self.inline ? self.getBody() : null,
  31379. collect: settings.content_editable,
  31380. schema: self.schema,
  31381. onSetAttrib: function(e) {
  31382. self.fire('SetAttrib', e);
  31383. }
  31384. });
  31385. /**
  31386. * HTML parser will be used when contents is inserted into the editor.
  31387. *
  31388. * @property parser
  31389. * @type tinymce.html.DomParser
  31390. */
  31391. self.parser = new DomParser(settings, self.schema);
  31392. // Convert src and href into data-mce-src, data-mce-href and data-mce-style
  31393. self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
  31394. var i = nodes.length, node, dom = self.dom, value, internalName;
  31395. while (i--) {
  31396. node = nodes[i];
  31397. value = node.attr(name);
  31398. internalName = 'data-mce-' + name;
  31399. // Add internal attribute if we need to we don't on a refresh of the document
  31400. if (!node.attributes.map[internalName]) {
  31401. // Don't duplicate these since they won't get modified by any browser
  31402. if (value.indexOf('data:') === 0 || value.indexOf('blob:') === 0) {
  31403. continue;
  31404. }
  31405. if (name === "style") {
  31406. value = dom.serializeStyle(dom.parseStyle(value), node.name);
  31407. if (!value.length) {
  31408. value = null;
  31409. }
  31410. node.attr(internalName, value);
  31411. node.attr(name, value);
  31412. } else if (name === "tabindex") {
  31413. node.attr(internalName, value);
  31414. node.attr(name, null);
  31415. } else {
  31416. node.attr(internalName, self.convertURL(value, name, node.name));
  31417. }
  31418. }
  31419. }
  31420. });
  31421. // Keep scripts from executing
  31422. self.parser.addNodeFilter('script', function(nodes) {
  31423. var i = nodes.length, node, type;
  31424. while (i--) {
  31425. node = nodes[i];
  31426. type = node.attr('type') || 'no/type';
  31427. if (type.indexOf('mce-') !== 0) {
  31428. node.attr('type', 'mce-' + type);
  31429. }
  31430. }
  31431. });
  31432. self.parser.addNodeFilter('#cdata', function(nodes) {
  31433. var i = nodes.length, node;
  31434. while (i--) {
  31435. node = nodes[i];
  31436. node.type = 8;
  31437. node.name = '#comment';
  31438. node.value = '[CDATA[' + node.value + ']]';
  31439. }
  31440. });
  31441. self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
  31442. var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
  31443. while (i--) {
  31444. node = nodes[i];
  31445. if (node.isEmpty(nonEmptyElements)) {
  31446. node.append(new Node('br', 1)).shortEnded = true;
  31447. }
  31448. }
  31449. });
  31450. /**
  31451. * DOM serializer for the editor. Will be used when contents is extracted from the editor.
  31452. *
  31453. * @property serializer
  31454. * @type tinymce.dom.Serializer
  31455. * @example
  31456. * // Serializes the first paragraph in the editor into a string
  31457. * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
  31458. */
  31459. self.serializer = new DomSerializer(settings, self);
  31460. /**
  31461. * Selection instance for the editor.
  31462. *
  31463. * @property selection
  31464. * @type tinymce.dom.Selection
  31465. * @example
  31466. * // Sets some contents to the current selection in the editor
  31467. * tinymce.activeEditor.selection.setContent('Some contents');
  31468. *
  31469. * // Gets the current selection
  31470. * alert(tinymce.activeEditor.selection.getContent());
  31471. *
  31472. * // Selects the first paragraph found
  31473. * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
  31474. */
  31475. self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);
  31476. /**
  31477. * Formatter instance.
  31478. *
  31479. * @property formatter
  31480. * @type tinymce.Formatter
  31481. */
  31482. self.formatter = new Formatter(self);
  31483. /**
  31484. * Undo manager instance, responsible for handling undo levels.
  31485. *
  31486. * @property undoManager
  31487. * @type tinymce.UndoManager
  31488. * @example
  31489. * // Undoes the last modification to the editor
  31490. * tinymce.activeEditor.undoManager.undo();
  31491. */
  31492. self.undoManager = new UndoManager(self);
  31493. self.forceBlocks = new ForceBlocks(self);
  31494. self.enterKey = new EnterKey(self);
  31495. self._nodeChangeDispatcher = new NodeChange(self);
  31496. self._selectionOverrides = new SelectionOverrides(self);
  31497. self.fire('PreInit');
  31498. if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
  31499. doc.body.spellcheck = false; // Gecko
  31500. DOM.setAttrib(body, "spellcheck", "false");
  31501. }
  31502. self.quirks = new Quirks(self);
  31503. self.fire('PostRender');
  31504. if (settings.directionality) {
  31505. body.dir = settings.directionality;
  31506. }
  31507. if (settings.nowrap) {
  31508. body.style.whiteSpace = "nowrap";
  31509. }
  31510. if (settings.protect) {
  31511. self.on('BeforeSetContent', function(e) {
  31512. each(settings.protect, function(pattern) {
  31513. e.content = e.content.replace(pattern, function(str) {
  31514. return '<!--mce:protected ' + escape(str) + '-->';
  31515. });
  31516. });
  31517. });
  31518. }
  31519. self.on('SetContent', function() {
  31520. self.addVisual(self.getBody());
  31521. });
  31522. // Remove empty contents
  31523. if (settings.padd_empty_editor) {
  31524. self.on('PostProcess', function(e) {
  31525. e.content = e.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
  31526. });
  31527. }
  31528. self.load({initial: true, format: 'html'});
  31529. self.startContent = self.getContent({format: 'raw'});
  31530. /**
  31531. * Is set to true after the editor instance has been initialized
  31532. *
  31533. * @property initialized
  31534. * @type Boolean
  31535. * @example
  31536. * function isEditorInitialized(editor) {
  31537. * return editor && editor.initialized;
  31538. * }
  31539. */
  31540. self.initialized = true;
  31541. self.bindPendingEventDelegates();
  31542. self.fire('init');
  31543. self.focus(true);
  31544. self.nodeChanged({initial: true});
  31545. self.execCallback('init_instance_callback', self);
  31546. self.on('compositionstart compositionend', function(e) {
  31547. self.composing = e.type === 'compositionstart';
  31548. });
  31549. // Add editor specific CSS styles
  31550. if (self.contentStyles.length > 0) {
  31551. contentCssText = '';
  31552. each(self.contentStyles, function(style) {
  31553. contentCssText += style + "\r\n";
  31554. });
  31555. self.dom.addStyle(contentCssText);
  31556. }
  31557. // Load specified content CSS last
  31558. each(self.contentCSS, function(cssUrl) {
  31559. if (!self.loadedCSS[cssUrl]) {
  31560. self.dom.loadCSS(cssUrl);
  31561. self.loadedCSS[cssUrl] = true;
  31562. }
  31563. });
  31564. // Handle auto focus
  31565. if (settings.auto_focus) {
  31566. Delay.setEditorTimeout(self, function() {
  31567. var editor;
  31568. if (settings.auto_focus === true) {
  31569. editor = self;
  31570. } else {
  31571. editor = self.editorManager.get(settings.auto_focus);
  31572. }
  31573. if (!editor.destroyed) {
  31574. editor.focus();
  31575. }
  31576. }, 100);
  31577. }
  31578. // Clean up references for IE
  31579. targetElm = doc = body = null;
  31580. },
  31581. /**
  31582. * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
  31583. * it will also place DOM focus inside the editor.
  31584. *
  31585. * @method focus
  31586. * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor.
  31587. */
  31588. focus: function(skipFocus) {
  31589. var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
  31590. var controlElm, doc = self.getDoc(), body = self.getBody(), contentEditableHost;
  31591. function getContentEditableHost(node) {
  31592. return self.dom.getParent(node, function(node) {
  31593. return self.dom.getContentEditable(node) === "true";
  31594. });
  31595. }
  31596. if (!skipFocus) {
  31597. // Get selected control element
  31598. rng = selection.getRng();
  31599. if (rng.item) {
  31600. controlElm = rng.item(0);
  31601. }
  31602. self.quirks.refreshContentEditable();
  31603. // Move focus to contentEditable=true child if needed
  31604. contentEditableHost = getContentEditableHost(selection.getNode());
  31605. if (self.$.contains(body, contentEditableHost)) {
  31606. contentEditableHost.focus();
  31607. selection.normalize();
  31608. self.editorManager.setActive(self);
  31609. return;
  31610. }
  31611. // Focus the window iframe
  31612. if (!contentEditable) {
  31613. // WebKit needs this call to fire focusin event properly see #5948
  31614. // But Opera pre Blink engine will produce an empty selection so skip Opera
  31615. if (!Env.opera) {
  31616. self.getBody().focus();
  31617. }
  31618. self.getWin().focus();
  31619. }
  31620. // Focus the body as well since it's contentEditable
  31621. if (isGecko || contentEditable) {
  31622. // Check for setActive since it doesn't scroll to the element
  31623. if (body.setActive) {
  31624. // IE 11 sometimes throws "Invalid function" then fallback to focus
  31625. try {
  31626. body.setActive();
  31627. } catch (ex) {
  31628. body.focus();
  31629. }
  31630. } else {
  31631. body.focus();
  31632. }
  31633. if (contentEditable) {
  31634. selection.normalize();
  31635. }
  31636. }
  31637. // Restore selected control element
  31638. // This is needed when for example an image is selected within a
  31639. // layer a call to focus will then remove the control selection
  31640. if (controlElm && controlElm.ownerDocument == doc) {
  31641. rng = doc.body.createControlRange();
  31642. rng.addElement(controlElm);
  31643. rng.select();
  31644. }
  31645. }
  31646. self.editorManager.setActive(self);
  31647. },
  31648. /**
  31649. * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
  31650. * There new event model is a better way to add callback so this method might be removed in the future.
  31651. *
  31652. * @method execCallback
  31653. * @param {String} name Name of the callback to execute.
  31654. * @return {Object} Return value passed from callback function.
  31655. */
  31656. execCallback: function(name) {
  31657. var self = this, callback = self.settings[name], scope;
  31658. if (!callback) {
  31659. return;
  31660. }
  31661. // Look through lookup
  31662. if (self.callbackLookup && (scope = self.callbackLookup[name])) {
  31663. callback = scope.func;
  31664. scope = scope.scope;
  31665. }
  31666. if (typeof callback === 'string') {
  31667. scope = callback.replace(/\.\w+$/, '');
  31668. scope = scope ? resolve(scope) : 0;
  31669. callback = resolve(callback);
  31670. self.callbackLookup = self.callbackLookup || {};
  31671. self.callbackLookup[name] = {func: callback, scope: scope};
  31672. }
  31673. return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
  31674. },
  31675. /**
  31676. * Translates the specified string by replacing variables with language pack items it will also check if there is
  31677. * a key matching the input.
  31678. *
  31679. * @method translate
  31680. * @param {String} text String to translate by the language pack data.
  31681. * @return {String} Translated string.
  31682. */
  31683. translate: function(text) {
  31684. var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
  31685. if (!text) {
  31686. return '';
  31687. }
  31688. text = i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
  31689. return i18n.data[lang + '.' + b] || '{#' + b + '}';
  31690. });
  31691. return this.editorManager.translate(text);
  31692. },
  31693. /**
  31694. * Returns a language pack item by name/key.
  31695. *
  31696. * @method getLang
  31697. * @param {String} name Name/key to get from the language pack.
  31698. * @param {String} defaultVal Optional default value to retrieve.
  31699. */
  31700. getLang: function(name, defaultVal) {
  31701. return (
  31702. this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
  31703. (defaultVal !== undefined ? defaultVal : '{#' + name + '}')
  31704. );
  31705. },
  31706. /**
  31707. * Returns a configuration parameter by name.
  31708. *
  31709. * @method getParam
  31710. * @param {String} name Configruation parameter to retrieve.
  31711. * @param {String} defaultVal Optional default value to return.
  31712. * @param {String} type Optional type parameter.
  31713. * @return {String} Configuration parameter value or default value.
  31714. * @example
  31715. * // Returns a specific config value from the currently active editor
  31716. * var someval = tinymce.activeEditor.getParam('myvalue');
  31717. *
  31718. * // Returns a specific config value from a specific editor instance by id
  31719. * var someval2 = tinymce.get('my_editor').getParam('myvalue');
  31720. */
  31721. getParam: function(name, defaultVal, type) {
  31722. var value = name in this.settings ? this.settings[name] : defaultVal, output;
  31723. if (type === 'hash') {
  31724. output = {};
  31725. if (typeof value === 'string') {
  31726. each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
  31727. value = value.split('=');
  31728. if (value.length > 1) {
  31729. output[trim(value[0])] = trim(value[1]);
  31730. } else {
  31731. output[trim(value[0])] = trim(value);
  31732. }
  31733. });
  31734. } else {
  31735. output = value;
  31736. }
  31737. return output;
  31738. }
  31739. return value;
  31740. },
  31741. /**
  31742. * Dispatches out a onNodeChange event to all observers. This method should be called when you
  31743. * need to update the UI states or element path etc.
  31744. *
  31745. * @method nodeChanged
  31746. * @param {Object} args Optional args to pass to NodeChange event handlers.
  31747. */
  31748. nodeChanged: function(args) {
  31749. this._nodeChangeDispatcher.nodeChanged(args);
  31750. },
  31751. /**
  31752. * Adds a button that later gets created by the theme in the editors toolbars.
  31753. *
  31754. * @method addButton
  31755. * @param {String} name Button name to add.
  31756. * @param {Object} settings Settings object with title, cmd etc.
  31757. * @example
  31758. * // Adds a custom button to the editor that inserts contents when clicked
  31759. * tinymce.init({
  31760. * ...
  31761. *
  31762. * toolbar: 'example'
  31763. *
  31764. * setup: function(ed) {
  31765. * ed.addButton('example', {
  31766. * title: 'My title',
  31767. * image: '../js/tinymce/plugins/example/img/example.gif',
  31768. * onclick: function() {
  31769. * ed.insertContent('Hello world!!');
  31770. * }
  31771. * });
  31772. * }
  31773. * });
  31774. */
  31775. addButton: function(name, settings) {
  31776. var self = this;
  31777. if (settings.cmd) {
  31778. settings.onclick = function() {
  31779. self.execCommand(settings.cmd);
  31780. };
  31781. }
  31782. if (!settings.text && !settings.icon) {
  31783. settings.icon = name;
  31784. }
  31785. self.buttons = self.buttons || {};
  31786. settings.tooltip = settings.tooltip || settings.title;
  31787. self.buttons[name] = settings;
  31788. },
  31789. /**
  31790. * Adds a menu item to be used in the menus of the theme. There might be multiple instances
  31791. * of this menu item for example it might be used in the main menus of the theme but also in
  31792. * the context menu so make sure that it's self contained and supports multiple instances.
  31793. *
  31794. * @method addMenuItem
  31795. * @param {String} name Menu item name to add.
  31796. * @param {Object} settings Settings object with title, cmd etc.
  31797. * @example
  31798. * // Adds a custom menu item to the editor that inserts contents when clicked
  31799. * // The context option allows you to add the menu item to an existing default menu
  31800. * tinymce.init({
  31801. * ...
  31802. *
  31803. * setup: function(ed) {
  31804. * ed.addMenuItem('example', {
  31805. * text: 'My menu item',
  31806. * context: 'tools',
  31807. * onclick: function() {
  31808. * ed.insertContent('Hello world!!');
  31809. * }
  31810. * });
  31811. * }
  31812. * });
  31813. */
  31814. addMenuItem: function(name, settings) {
  31815. var self = this;
  31816. if (settings.cmd) {
  31817. settings.onclick = function() {
  31818. self.execCommand(settings.cmd);
  31819. };
  31820. }
  31821. self.menuItems = self.menuItems || {};
  31822. self.menuItems[name] = settings;
  31823. },
  31824. /**
  31825. * Adds a contextual toolbar to be rendered when the selector matches.
  31826. *
  31827. * @method addContextToolbar
  31828. * @param {function/string} predicate Predicate that needs to return true if provided strings get converted into CSS predicates.
  31829. * @param {String/Array} items String or array with items to add to the context toolbar.
  31830. */
  31831. addContextToolbar: function(predicate, items) {
  31832. var self = this, selector;
  31833. self.contextToolbars = self.contextToolbars || [];
  31834. // Convert selector to predicate
  31835. if (typeof predicate == "string") {
  31836. selector = predicate;
  31837. predicate = function(elm) {
  31838. return self.dom.is(elm, selector);
  31839. };
  31840. }
  31841. self.contextToolbars.push({
  31842. id: Uuid.uuid('mcet'),
  31843. predicate: predicate,
  31844. items: items
  31845. });
  31846. },
  31847. /**
  31848. * Adds a custom command to the editor, you can also override existing commands with this method.
  31849. * The command that you add can be executed with execCommand.
  31850. *
  31851. * @method addCommand
  31852. * @param {String} name Command name to add/override.
  31853. * @param {addCommandCallback} callback Function to execute when the command occurs.
  31854. * @param {Object} scope Optional scope to execute the function in.
  31855. * @example
  31856. * // Adds a custom command that later can be executed using execCommand
  31857. * tinymce.init({
  31858. * ...
  31859. *
  31860. * setup: function(ed) {
  31861. * // Register example command
  31862. * ed.addCommand('mycommand', function(ui, v) {
  31863. * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
  31864. * });
  31865. * }
  31866. * });
  31867. */
  31868. addCommand: function(name, callback, scope) {
  31869. /**
  31870. * Callback function that gets called when a command is executed.
  31871. *
  31872. * @callback addCommandCallback
  31873. * @param {Boolean} ui Display UI state true/false.
  31874. * @param {Object} value Optional value for command.
  31875. * @return {Boolean} True/false state if the command was handled or not.
  31876. */
  31877. this.editorCommands.addCommand(name, callback, scope);
  31878. },
  31879. /**
  31880. * Adds a custom query state command to the editor, you can also override existing commands with this method.
  31881. * The command that you add can be executed with queryCommandState function.
  31882. *
  31883. * @method addQueryStateHandler
  31884. * @param {String} name Command name to add/override.
  31885. * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrieval occurs.
  31886. * @param {Object} scope Optional scope to execute the function in.
  31887. */
  31888. addQueryStateHandler: function(name, callback, scope) {
  31889. /**
  31890. * Callback function that gets called when a queryCommandState is executed.
  31891. *
  31892. * @callback addQueryStateHandlerCallback
  31893. * @return {Boolean} True/false state if the command is enabled or not like is it bold.
  31894. */
  31895. this.editorCommands.addQueryStateHandler(name, callback, scope);
  31896. },
  31897. /**
  31898. * Adds a custom query value command to the editor, you can also override existing commands with this method.
  31899. * The command that you add can be executed with queryCommandValue function.
  31900. *
  31901. * @method addQueryValueHandler
  31902. * @param {String} name Command name to add/override.
  31903. * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrieval occurs.
  31904. * @param {Object} scope Optional scope to execute the function in.
  31905. */
  31906. addQueryValueHandler: function(name, callback, scope) {
  31907. /**
  31908. * Callback function that gets called when a queryCommandValue is executed.
  31909. *
  31910. * @callback addQueryValueHandlerCallback
  31911. * @return {Object} Value of the command or undefined.
  31912. */
  31913. this.editorCommands.addQueryValueHandler(name, callback, scope);
  31914. },
  31915. /**
  31916. * Adds a keyboard shortcut for some command or function.
  31917. *
  31918. * @method addShortcut
  31919. * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  31920. * @param {String} desc Text description for the command.
  31921. * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
  31922. * @param {Object} sc Optional scope to execute the function in.
  31923. * @return {Boolean} true/false state if the shortcut was added or not.
  31924. */
  31925. addShortcut: function(pattern, desc, cmdFunc, scope) {
  31926. this.shortcuts.add(pattern, desc, cmdFunc, scope);
  31927. },
  31928. /**
  31929. * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
  31930. * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
  31931. * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
  31932. * return true it will handle the command as a internal browser command.
  31933. *
  31934. * @method execCommand
  31935. * @param {String} cmd Command name to execute, for example mceLink or Bold.
  31936. * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
  31937. * @param {mixed} value Optional command value, this can be anything.
  31938. * @param {Object} args Optional arguments object.
  31939. */
  31940. execCommand: function(cmd, ui, value, args) {
  31941. return this.editorCommands.execCommand(cmd, ui, value, args);
  31942. },
  31943. /**
  31944. * Returns a command specific state, for example if bold is enabled or not.
  31945. *
  31946. * @method queryCommandState
  31947. * @param {string} cmd Command to query state from.
  31948. * @return {Boolean} Command specific state, for example if bold is enabled or not.
  31949. */
  31950. queryCommandState: function(cmd) {
  31951. return this.editorCommands.queryCommandState(cmd);
  31952. },
  31953. /**
  31954. * Returns a command specific value, for example the current font size.
  31955. *
  31956. * @method queryCommandValue
  31957. * @param {string} cmd Command to query value from.
  31958. * @return {Object} Command specific value, for example the current font size.
  31959. */
  31960. queryCommandValue: function(cmd) {
  31961. return this.editorCommands.queryCommandValue(cmd);
  31962. },
  31963. /**
  31964. * Returns true/false if the command is supported or not.
  31965. *
  31966. * @method queryCommandSupported
  31967. * @param {String} cmd Command that we check support for.
  31968. * @return {Boolean} true/false if the command is supported or not.
  31969. */
  31970. queryCommandSupported: function(cmd) {
  31971. return this.editorCommands.queryCommandSupported(cmd);
  31972. },
  31973. /**
  31974. * Shows the editor and hides any textarea/div that the editor is supposed to replace.
  31975. *
  31976. * @method show
  31977. */
  31978. show: function() {
  31979. var self = this;
  31980. if (self.hidden) {
  31981. self.hidden = false;
  31982. if (self.inline) {
  31983. self.getBody().contentEditable = true;
  31984. } else {
  31985. DOM.show(self.getContainer());
  31986. DOM.hide(self.id);
  31987. }
  31988. self.load();
  31989. self.fire('show');
  31990. }
  31991. },
  31992. /**
  31993. * Hides the editor and shows any textarea/div that the editor is supposed to replace.
  31994. *
  31995. * @method hide
  31996. */
  31997. hide: function() {
  31998. var self = this, doc = self.getDoc();
  31999. if (!self.hidden) {
  32000. // Fixed bug where IE has a blinking cursor left from the editor
  32001. if (ie && doc && !self.inline) {
  32002. doc.execCommand('SelectAll');
  32003. }
  32004. // We must save before we hide so Safari doesn't crash
  32005. self.save();
  32006. if (self.inline) {
  32007. self.getBody().contentEditable = false;
  32008. // Make sure the editor gets blurred
  32009. if (self == self.editorManager.focusedEditor) {
  32010. self.editorManager.focusedEditor = null;
  32011. }
  32012. } else {
  32013. DOM.hide(self.getContainer());
  32014. DOM.setStyle(self.id, 'display', self.orgDisplay);
  32015. }
  32016. self.hidden = true;
  32017. self.fire('hide');
  32018. }
  32019. },
  32020. /**
  32021. * Returns true/false if the editor is hidden or not.
  32022. *
  32023. * @method isHidden
  32024. * @return {Boolean} True/false if the editor is hidden or not.
  32025. */
  32026. isHidden: function() {
  32027. return !!this.hidden;
  32028. },
  32029. /**
  32030. * Sets the progress state, this will display a throbber/progess for the editor.
  32031. * This is ideal for asynchronous operations like an AJAX save call.
  32032. *
  32033. * @method setProgressState
  32034. * @param {Boolean} state Boolean state if the progress should be shown or hidden.
  32035. * @param {Number} time Optional time to wait before the progress gets shown.
  32036. * @return {Boolean} Same as the input state.
  32037. * @example
  32038. * // Show progress for the active editor
  32039. * tinymce.activeEditor.setProgressState(true);
  32040. *
  32041. * // Hide progress for the active editor
  32042. * tinymce.activeEditor.setProgressState(false);
  32043. *
  32044. * // Show progress after 3 seconds
  32045. * tinymce.activeEditor.setProgressState(true, 3000);
  32046. */
  32047. setProgressState: function(state, time) {
  32048. this.fire('ProgressState', {state: state, time: time});
  32049. },
  32050. /**
  32051. * Loads contents from the textarea or div element that got converted into an editor instance.
  32052. * This method will move the contents from that textarea or div into the editor by using setContent
  32053. * so all events etc that method has will get dispatched as well.
  32054. *
  32055. * @method load
  32056. * @param {Object} args Optional content object, this gets passed around through the whole load process.
  32057. * @return {String} HTML string that got set into the editor.
  32058. */
  32059. load: function(args) {
  32060. var self = this, elm = self.getElement(), html;
  32061. if (elm) {
  32062. args = args || {};
  32063. args.load = true;
  32064. html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
  32065. args.element = elm;
  32066. if (!args.no_events) {
  32067. self.fire('LoadContent', args);
  32068. }
  32069. args.element = elm = null;
  32070. return html;
  32071. }
  32072. },
  32073. /**
  32074. * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
  32075. * This method will move the HTML contents from the editor into that textarea or div by getContent
  32076. * so all events etc that method has will get dispatched as well.
  32077. *
  32078. * @method save
  32079. * @param {Object} args Optional content object, this gets passed around through the whole save process.
  32080. * @return {String} HTML string that got set into the textarea/div.
  32081. */
  32082. save: function(args) {
  32083. var self = this, elm = self.getElement(), html, form;
  32084. if (!elm || !self.initialized) {
  32085. return;
  32086. }
  32087. args = args || {};
  32088. args.save = true;
  32089. args.element = elm;
  32090. html = args.content = self.getContent(args);
  32091. if (!args.no_events) {
  32092. self.fire('SaveContent', args);
  32093. }
  32094. // Always run this internal event
  32095. if (args.format == 'raw') {
  32096. self.fire('RawSaveContent', args);
  32097. }
  32098. html = args.content;
  32099. if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
  32100. // Update DIV element when not in inline mode
  32101. if (!self.inline) {
  32102. elm.innerHTML = html;
  32103. }
  32104. // Update hidden form element
  32105. if ((form = DOM.getParent(self.id, 'form'))) {
  32106. each(form.elements, function(elm) {
  32107. if (elm.name == self.id) {
  32108. elm.value = html;
  32109. return false;
  32110. }
  32111. });
  32112. }
  32113. } else {
  32114. elm.value = html;
  32115. }
  32116. args.element = elm = null;
  32117. if (args.set_dirty !== false) {
  32118. self.setDirty(false);
  32119. }
  32120. return html;
  32121. },
  32122. /**
  32123. * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
  32124. * the different cleanup rules options.
  32125. *
  32126. * @method setContent
  32127. * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
  32128. * @param {Object} args Optional content object, this gets passed around through the whole set process.
  32129. * @return {String} HTML string that got set into the editor.
  32130. * @example
  32131. * // Sets the HTML contents of the activeEditor editor
  32132. * tinymce.activeEditor.setContent('<span>some</span> html');
  32133. *
  32134. * // Sets the raw contents of the activeEditor editor
  32135. * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
  32136. *
  32137. * // Sets the content of a specific editor (my_editor in this example)
  32138. * tinymce.get('my_editor').setContent(data);
  32139. *
  32140. * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
  32141. * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
  32142. */
  32143. setContent: function(content, args) {
  32144. var self = this, body = self.getBody(), forcedRootBlockName, padd;
  32145. // Setup args object
  32146. args = args || {};
  32147. args.format = args.format || 'html';
  32148. args.set = true;
  32149. args.content = content;
  32150. // Do preprocessing
  32151. if (!args.no_events) {
  32152. self.fire('BeforeSetContent', args);
  32153. }
  32154. content = args.content;
  32155. // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
  32156. // It will also be impossible to place the caret in the editor unless there is a BR element present
  32157. if (content.length === 0 || /^\s+$/.test(content)) {
  32158. padd = ie && ie < 11 ? '' : '<br data-mce-bogus="1">';
  32159. // Todo: There is a lot more root elements that need special padding
  32160. // so separate this and add all of them at some point.
  32161. if (body.nodeName == 'TABLE') {
  32162. content = '<tr><td>' + padd + '</td></tr>';
  32163. } else if (/^(UL|OL)$/.test(body.nodeName)) {
  32164. content = '<li>' + padd + '</li>';
  32165. }
  32166. forcedRootBlockName = self.settings.forced_root_block;
  32167. // Check if forcedRootBlock is configured and that the block is a valid child of the body
  32168. if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
  32169. // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly
  32170. content = padd;
  32171. content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content);
  32172. } else if (!ie && !content) {
  32173. // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
  32174. content = '<br data-mce-bogus="1">';
  32175. }
  32176. self.dom.setHTML(body, content);
  32177. self.fire('SetContent', args);
  32178. } else {
  32179. // Parse and serialize the html
  32180. if (args.format !== 'raw') {
  32181. content = new Serializer({
  32182. validate: self.validate
  32183. }, self.schema).serialize(
  32184. self.parser.parse(content, {isRootContent: true})
  32185. );
  32186. }
  32187. // Set the new cleaned contents to the editor
  32188. args.content = trim(content);
  32189. self.dom.setHTML(body, args.content);
  32190. // Do post processing
  32191. if (!args.no_events) {
  32192. self.fire('SetContent', args);
  32193. }
  32194. // Don't normalize selection if the focused element isn't the body in
  32195. // content editable mode since it will steal focus otherwise
  32196. /*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
  32197. self.selection.normalize();
  32198. }*/
  32199. }
  32200. return args.content;
  32201. },
  32202. /**
  32203. * Gets the content from the editor instance, this will cleanup the content before it gets returned using
  32204. * the different cleanup rules options.
  32205. *
  32206. * @method getContent
  32207. * @param {Object} args Optional content object, this gets passed around through the whole get process.
  32208. * @return {String} Cleaned content string, normally HTML contents.
  32209. * @example
  32210. * // Get the HTML contents of the currently active editor
  32211. * console.debug(tinymce.activeEditor.getContent());
  32212. *
  32213. * // Get the raw contents of the currently active editor
  32214. * tinymce.activeEditor.getContent({format: 'raw'});
  32215. *
  32216. * // Get content of a specific editor:
  32217. * tinymce.get('content id').getContent()
  32218. */
  32219. getContent: function(args) {
  32220. var self = this, content, body = self.getBody();
  32221. // Setup args object
  32222. args = args || {};
  32223. args.format = args.format || 'html';
  32224. args.get = true;
  32225. args.getInner = true;
  32226. // Do preprocessing
  32227. if (!args.no_events) {
  32228. self.fire('BeforeGetContent', args);
  32229. }
  32230. // Get raw contents or by default the cleaned contents
  32231. if (args.format == 'raw') {
  32232. content = self.serializer.getTrimmedContent();
  32233. } else if (args.format == 'text') {
  32234. content = body.innerText || body.textContent;
  32235. } else {
  32236. content = self.serializer.serialize(body, args);
  32237. }
  32238. // Trim whitespace in beginning/end of HTML
  32239. if (args.format != 'text') {
  32240. args.content = trim(content);
  32241. } else {
  32242. args.content = content;
  32243. }
  32244. // Do post processing
  32245. if (!args.no_events) {
  32246. self.fire('GetContent', args);
  32247. }
  32248. return args.content;
  32249. },
  32250. /**
  32251. * Inserts content at caret position.
  32252. *
  32253. * @method insertContent
  32254. * @param {String} content Content to insert.
  32255. * @param {Object} args Optional args to pass to insert call.
  32256. */
  32257. insertContent: function(content, args) {
  32258. if (args) {
  32259. content = extend({content: content}, args);
  32260. }
  32261. this.execCommand('mceInsertContent', false, content);
  32262. },
  32263. /**
  32264. * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
  32265. *
  32266. * The dirty state is automatically set to true if you do modifications to the content in other
  32267. * words when new undo levels is created or if you undo/redo to update the contents of the editor. It will also be set
  32268. * to false if you call editor.save().
  32269. *
  32270. * @method isDirty
  32271. * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
  32272. * @example
  32273. * if (tinymce.activeEditor.isDirty())
  32274. * alert("You must save your contents.");
  32275. */
  32276. isDirty: function() {
  32277. return !this.isNotDirty;
  32278. },
  32279. /**
  32280. * Explicitly sets the dirty state. This will fire the dirty event if the editor dirty state is changed from false to true
  32281. * by invoking this method.
  32282. *
  32283. * @method setDirty
  32284. * @param {Boolean} state True/false if the editor is considered dirty.
  32285. * @example
  32286. * function ajaxSave() {
  32287. * var editor = tinymce.get('elm1');
  32288. *
  32289. * // Save contents using some XHR call
  32290. * alert(editor.getContent());
  32291. *
  32292. * editor.setDirty(false); // Force not dirty state
  32293. * }
  32294. */
  32295. setDirty: function(state) {
  32296. var oldState = !this.isNotDirty;
  32297. this.isNotDirty = !state;
  32298. if (state && state != oldState) {
  32299. this.fire('dirty');
  32300. }
  32301. },
  32302. /**
  32303. * Sets the editor mode. Mode can be for example "design", "code" or "readonly".
  32304. *
  32305. * @method setMode
  32306. * @param {String} mode Mode to set the editor in.
  32307. */
  32308. setMode: function(mode) {
  32309. Mode.setMode(this, mode);
  32310. },
  32311. /**
  32312. * Returns the editors container element. The container element wrappes in
  32313. * all the elements added to the page for the editor. Such as UI, iframe etc.
  32314. *
  32315. * @method getContainer
  32316. * @return {Element} HTML DOM element for the editor container.
  32317. */
  32318. getContainer: function() {
  32319. var self = this;
  32320. if (!self.container) {
  32321. self.container = DOM.get(self.editorContainer || self.id + '_parent');
  32322. }
  32323. return self.container;
  32324. },
  32325. /**
  32326. * Returns the editors content area container element. The this element is the one who
  32327. * holds the iframe or the editable element.
  32328. *
  32329. * @method getContentAreaContainer
  32330. * @return {Element} HTML DOM element for the editor area container.
  32331. */
  32332. getContentAreaContainer: function() {
  32333. return this.contentAreaContainer;
  32334. },
  32335. /**
  32336. * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
  32337. *
  32338. * @method getElement
  32339. * @return {Element} HTML DOM element for the replaced element.
  32340. */
  32341. getElement: function() {
  32342. if (!this.targetElm) {
  32343. this.targetElm = DOM.get(this.id);
  32344. }
  32345. return this.targetElm;
  32346. },
  32347. /**
  32348. * Returns the iframes window object.
  32349. *
  32350. * @method getWin
  32351. * @return {Window} Iframe DOM window object.
  32352. */
  32353. getWin: function() {
  32354. var self = this, elm;
  32355. if (!self.contentWindow) {
  32356. elm = self.iframeElement;
  32357. if (elm) {
  32358. self.contentWindow = elm.contentWindow;
  32359. }
  32360. }
  32361. return self.contentWindow;
  32362. },
  32363. /**
  32364. * Returns the iframes document object.
  32365. *
  32366. * @method getDoc
  32367. * @return {Document} Iframe DOM document object.
  32368. */
  32369. getDoc: function() {
  32370. var self = this, win;
  32371. if (!self.contentDocument) {
  32372. win = self.getWin();
  32373. if (win) {
  32374. self.contentDocument = win.document;
  32375. }
  32376. }
  32377. return self.contentDocument;
  32378. },
  32379. /**
  32380. * Returns the root element of the editable area.
  32381. * For a non-inline iframe-based editor, returns the iframe's body element.
  32382. *
  32383. * @method getBody
  32384. * @return {Element} The root element of the editable area.
  32385. */
  32386. getBody: function() {
  32387. return this.bodyElement || this.getDoc().body;
  32388. },
  32389. /**
  32390. * URL converter function this gets executed each time a user adds an img, a or
  32391. * any other element that has a URL in it. This will be called both by the DOM and HTML
  32392. * manipulation functions.
  32393. *
  32394. * @method convertURL
  32395. * @param {string} url URL to convert.
  32396. * @param {string} name Attribute name src, href etc.
  32397. * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
  32398. * @return {string} Converted URL string.
  32399. */
  32400. convertURL: function(url, name, elm) {
  32401. var self = this, settings = self.settings;
  32402. // Use callback instead
  32403. if (settings.urlconverter_callback) {
  32404. return self.execCallback('urlconverter_callback', url, elm, true, name);
  32405. }
  32406. // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
  32407. if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
  32408. return url;
  32409. }
  32410. // Convert to relative
  32411. if (settings.relative_urls) {
  32412. return self.documentBaseURI.toRelative(url);
  32413. }
  32414. // Convert to absolute
  32415. url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
  32416. return url;
  32417. },
  32418. /**
  32419. * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
  32420. *
  32421. * @method addVisual
  32422. * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
  32423. */
  32424. addVisual: function(elm) {
  32425. var self = this, settings = self.settings, dom = self.dom, cls;
  32426. elm = elm || self.getBody();
  32427. if (self.hasVisual === undefined) {
  32428. self.hasVisual = settings.visual;
  32429. }
  32430. each(dom.select('table,a', elm), function(elm) {
  32431. var value;
  32432. switch (elm.nodeName) {
  32433. case 'TABLE':
  32434. cls = settings.visual_table_class || 'mce-item-table';
  32435. value = dom.getAttrib(elm, 'border');
  32436. if ((!value || value == '0') && self.hasVisual) {
  32437. dom.addClass(elm, cls);
  32438. } else {
  32439. dom.removeClass(elm, cls);
  32440. }
  32441. return;
  32442. case 'A':
  32443. if (!dom.getAttrib(elm, 'href', false)) {
  32444. value = dom.getAttrib(elm, 'name') || elm.id;
  32445. cls = settings.visual_anchor_class || 'mce-item-anchor';
  32446. if (value && self.hasVisual) {
  32447. dom.addClass(elm, cls);
  32448. } else {
  32449. dom.removeClass(elm, cls);
  32450. }
  32451. }
  32452. return;
  32453. }
  32454. });
  32455. self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
  32456. },
  32457. /**
  32458. * Removes the editor from the dom and tinymce collection.
  32459. *
  32460. * @method remove
  32461. */
  32462. remove: function() {
  32463. var self = this;
  32464. if (!self.removed) {
  32465. self.save();
  32466. self.removed = 1;
  32467. self.unbindAllNativeEvents();
  32468. // Remove any hidden input
  32469. if (self.hasHiddenInput) {
  32470. DOM.remove(self.getElement().nextSibling);
  32471. }
  32472. if (!self.inline) {
  32473. // IE 9 has a bug where the selection stops working if you place the
  32474. // caret inside the editor then remove the iframe
  32475. if (ie && ie < 10) {
  32476. self.getDoc().execCommand('SelectAll', false, null);
  32477. }
  32478. DOM.setStyle(self.id, 'display', self.orgDisplay);
  32479. self.getBody().onload = null; // Prevent #6816
  32480. }
  32481. self.fire('remove');
  32482. self.editorManager.remove(self);
  32483. DOM.remove(self.getContainer());
  32484. self._selectionOverrides.destroy();
  32485. self.editorUpload.destroy();
  32486. self.destroy();
  32487. }
  32488. },
  32489. /**
  32490. * Destroys the editor instance by removing all events, element references or other resources
  32491. * that could leak memory. This method will be called automatically when the page is unloaded
  32492. * but you can also call it directly if you know what you are doing.
  32493. *
  32494. * @method destroy
  32495. * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
  32496. */
  32497. destroy: function(automatic) {
  32498. var self = this, form;
  32499. // One time is enough
  32500. if (self.destroyed) {
  32501. return;
  32502. }
  32503. // If user manually calls destroy and not remove
  32504. // Users seems to have logic that calls destroy instead of remove
  32505. if (!automatic && !self.removed) {
  32506. self.remove();
  32507. return;
  32508. }
  32509. if (!automatic) {
  32510. self.editorManager.off('beforeunload', self._beforeUnload);
  32511. // Manual destroy
  32512. if (self.theme && self.theme.destroy) {
  32513. self.theme.destroy();
  32514. }
  32515. // Destroy controls, selection and dom
  32516. self.selection.destroy();
  32517. self.dom.destroy();
  32518. }
  32519. form = self.formElement;
  32520. if (form) {
  32521. if (form._mceOldSubmit) {
  32522. form.submit = form._mceOldSubmit;
  32523. form._mceOldSubmit = null;
  32524. }
  32525. DOM.unbind(form, 'submit reset', self.formEventDelegate);
  32526. }
  32527. self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
  32528. self.bodyElement = self.contentDocument = self.contentWindow = null;
  32529. self.iframeElement = self.targetElm = null;
  32530. if (self.selection) {
  32531. self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
  32532. }
  32533. self.destroyed = 1;
  32534. },
  32535. /**
  32536. * Uploads all data uri/blob uri images in the editor contents to server.
  32537. *
  32538. * @method uploadImages
  32539. * @param {function} callback Optional callback with images and status for each image.
  32540. * @return {tinymce.util.Promise} Promise instance.
  32541. */
  32542. uploadImages: function(callback) {
  32543. return this.editorUpload.uploadImages(callback);
  32544. },
  32545. // Internal functions
  32546. _scanForImages: function() {
  32547. return this.editorUpload.scanForImages();
  32548. }
  32549. };
  32550. extend(Editor.prototype, EditorObservable);
  32551. return Editor;
  32552. });
  32553. // Included from: js/tinymce/classes/util/I18n.js
  32554. /**
  32555. * I18n.js
  32556. *
  32557. * Released under LGPL License.
  32558. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  32559. *
  32560. * License: http://www.tinymce.com/license
  32561. * Contributing: http://www.tinymce.com/contributing
  32562. */
  32563. /**
  32564. * I18n class that handles translation of TinyMCE UI.
  32565. * Uses po style with csharp style parameters.
  32566. *
  32567. * @class tinymce.util.I18n
  32568. */
  32569. define("tinymce/util/I18n", [], function() {
  32570. "use strict";
  32571. var data = {}, code = "en";
  32572. return {
  32573. /**
  32574. * Sets the current language code.
  32575. *
  32576. * @method setCode
  32577. * @param {String} newCode Current language code.
  32578. */
  32579. setCode: function(newCode) {
  32580. if (newCode) {
  32581. code = newCode;
  32582. this.rtl = this.data[newCode] ? this.data[newCode]._dir === 'rtl' : false;
  32583. }
  32584. },
  32585. /**
  32586. * Returns the current language code.
  32587. *
  32588. * @method getCode
  32589. * @return {String} Current language code.
  32590. */
  32591. getCode: function() {
  32592. return code;
  32593. },
  32594. /**
  32595. * Property gets set to true if a RTL language pack was loaded.
  32596. *
  32597. * @property rtl
  32598. * @type Boolean
  32599. */
  32600. rtl: false,
  32601. /**
  32602. * Adds translations for a specific language code.
  32603. *
  32604. * @method add
  32605. * @param {String} code Language code like sv_SE.
  32606. * @param {Array} items Name/value array with English en_US to sv_SE.
  32607. */
  32608. add: function(code, items) {
  32609. var langData = data[code];
  32610. if (!langData) {
  32611. data[code] = langData = {};
  32612. }
  32613. for (var name in items) {
  32614. langData[name] = items[name];
  32615. }
  32616. this.setCode(code);
  32617. },
  32618. /**
  32619. * Translates the specified text.
  32620. *
  32621. * It has a few formats:
  32622. * I18n.translate("Text");
  32623. * I18n.translate(["Text {0}/{1}", 0, 1]);
  32624. * I18n.translate({raw: "Raw string"});
  32625. *
  32626. * @method translate
  32627. * @param {String/Object/Array} text Text to translate.
  32628. * @return {String} String that got translated.
  32629. */
  32630. translate: function(text) {
  32631. var langData;
  32632. langData = data[code];
  32633. if (!langData) {
  32634. langData = {};
  32635. }
  32636. if (typeof text == "undefined") {
  32637. return text;
  32638. }
  32639. if (typeof text != "string" && text.raw) {
  32640. return text.raw;
  32641. }
  32642. if (text.push) {
  32643. var values = text.slice(1);
  32644. text = (langData[text[0]] || text[0]).replace(/\{([0-9]+)\}/g, function(match1, match2) {
  32645. return values[match2];
  32646. });
  32647. }
  32648. return (langData[text] || text).replace(/{context:\w+}$/, '');
  32649. },
  32650. data: data
  32651. };
  32652. });
  32653. // Included from: js/tinymce/classes/FocusManager.js
  32654. /**
  32655. * FocusManager.js
  32656. *
  32657. * Released under LGPL License.
  32658. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  32659. *
  32660. * License: http://www.tinymce.com/license
  32661. * Contributing: http://www.tinymce.com/contributing
  32662. */
  32663. /**
  32664. * This class manages the focus/blur state of the editor. This class is needed since some
  32665. * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
  32666. *
  32667. * This class will fire two events focus and blur on the editor instances that got affected.
  32668. * It will also handle the restore of selection when the focus is lost and returned.
  32669. *
  32670. * @class tinymce.FocusManager
  32671. */
  32672. define("tinymce/FocusManager", [
  32673. "tinymce/dom/DOMUtils",
  32674. "tinymce/util/Delay",
  32675. "tinymce/Env"
  32676. ], function(DOMUtils, Delay, Env) {
  32677. var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;
  32678. /**
  32679. * Constructs a new focus manager instance.
  32680. *
  32681. * @constructor FocusManager
  32682. * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for.
  32683. */
  32684. function FocusManager(editorManager) {
  32685. function getActiveElement() {
  32686. try {
  32687. return document.activeElement;
  32688. } catch (ex) {
  32689. // IE sometimes fails to get the activeElement when resizing table
  32690. // TODO: Investigate this
  32691. return document.body;
  32692. }
  32693. }
  32694. // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
  32695. // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
  32696. function createBookmark(dom, rng) {
  32697. if (rng && rng.startContainer) {
  32698. // Verify that the range is within the root of the editor
  32699. if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
  32700. return;
  32701. }
  32702. return {
  32703. startContainer: rng.startContainer,
  32704. startOffset: rng.startOffset,
  32705. endContainer: rng.endContainer,
  32706. endOffset: rng.endOffset
  32707. };
  32708. }
  32709. return rng;
  32710. }
  32711. function bookmarkToRng(editor, bookmark) {
  32712. var rng;
  32713. if (bookmark.startContainer) {
  32714. rng = editor.getDoc().createRange();
  32715. rng.setStart(bookmark.startContainer, bookmark.startOffset);
  32716. rng.setEnd(bookmark.endContainer, bookmark.endOffset);
  32717. } else {
  32718. rng = bookmark;
  32719. }
  32720. return rng;
  32721. }
  32722. function isUIElement(elm) {
  32723. return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
  32724. }
  32725. function registerEvents(e) {
  32726. var editor = e.editor;
  32727. editor.on('init', function() {
  32728. // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
  32729. if (editor.inline || Env.ie) {
  32730. // Use the onbeforedeactivate event when available since it works better see #7023
  32731. if ("onbeforedeactivate" in document && Env.ie < 9) {
  32732. editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) {
  32733. if (e.target != editor.getBody()) {
  32734. return;
  32735. }
  32736. try {
  32737. editor.lastRng = editor.selection.getRng();
  32738. } catch (ex) {
  32739. // IE throws "Unexcpected call to method or property access" some times so lets ignore it
  32740. }
  32741. });
  32742. } else {
  32743. // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
  32744. editor.on('nodechange mouseup keyup', function(e) {
  32745. var node = getActiveElement();
  32746. // Only act on manual nodechanges
  32747. if (e.type == 'nodechange' && e.selectionChange) {
  32748. return;
  32749. }
  32750. // IE 11 reports active element as iframe not body of iframe
  32751. if (node && node.id == editor.id + '_ifr') {
  32752. node = editor.getBody();
  32753. }
  32754. if (editor.dom.isChildOf(node, editor.getBody())) {
  32755. editor.lastRng = editor.selection.getRng();
  32756. }
  32757. });
  32758. }
  32759. // Handles the issue with WebKit not retaining selection within inline document
  32760. // If the user releases the mouse out side the body since a mouse up event wont occur on the body
  32761. if (Env.webkit && !selectionChangeHandler) {
  32762. selectionChangeHandler = function() {
  32763. var activeEditor = editorManager.activeEditor;
  32764. if (activeEditor && activeEditor.selection) {
  32765. var rng = activeEditor.selection.getRng();
  32766. // Store when it's non collapsed
  32767. if (rng && !rng.collapsed) {
  32768. editor.lastRng = rng;
  32769. }
  32770. }
  32771. };
  32772. DOM.bind(document, 'selectionchange', selectionChangeHandler);
  32773. }
  32774. }
  32775. });
  32776. editor.on('setcontent', function() {
  32777. editor.lastRng = null;
  32778. });
  32779. // Remove last selection bookmark on mousedown see #6305
  32780. editor.on('mousedown', function() {
  32781. editor.selection.lastFocusBookmark = null;
  32782. });
  32783. editor.on('focusin', function() {
  32784. var focusedEditor = editorManager.focusedEditor, lastRng;
  32785. if (editor.selection.lastFocusBookmark) {
  32786. lastRng = bookmarkToRng(editor, editor.selection.lastFocusBookmark);
  32787. editor.selection.lastFocusBookmark = null;
  32788. editor.selection.setRng(lastRng);
  32789. }
  32790. if (focusedEditor != editor) {
  32791. if (focusedEditor) {
  32792. focusedEditor.fire('blur', {focusedEditor: editor});
  32793. }
  32794. editorManager.setActive(editor);
  32795. editorManager.focusedEditor = editor;
  32796. editor.fire('focus', {blurredEditor: focusedEditor});
  32797. editor.focus(true);
  32798. }
  32799. editor.lastRng = null;
  32800. });
  32801. editor.on('focusout', function() {
  32802. Delay.setEditorTimeout(editor, function() {
  32803. var focusedEditor = editorManager.focusedEditor;
  32804. // Still the same editor the blur was outside any editor UI
  32805. if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
  32806. editor.fire('blur', {focusedEditor: null});
  32807. editorManager.focusedEditor = null;
  32808. // Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
  32809. if (editor.selection) {
  32810. editor.selection.lastFocusBookmark = null;
  32811. }
  32812. }
  32813. });
  32814. });
  32815. // Check if focus is moved to an element outside the active editor by checking if the target node
  32816. // isn't within the body of the activeEditor nor a UI element such as a dialog child control
  32817. if (!documentFocusInHandler) {
  32818. documentFocusInHandler = function(e) {
  32819. var activeEditor = editorManager.activeEditor, target;
  32820. target = e.target;
  32821. if (activeEditor && target.ownerDocument == document) {
  32822. // Check to make sure we have a valid selection don't update the bookmark if it's
  32823. // a focusin to the body of the editor see #7025
  32824. if (activeEditor.selection && target != activeEditor.getBody()) {
  32825. activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
  32826. }
  32827. // Fire a blur event if the element isn't a UI element
  32828. if (target != document.body && !isUIElement(target) && editorManager.focusedEditor == activeEditor) {
  32829. activeEditor.fire('blur', {focusedEditor: null});
  32830. editorManager.focusedEditor = null;
  32831. }
  32832. }
  32833. };
  32834. DOM.bind(document, 'focusin', documentFocusInHandler);
  32835. }
  32836. // Handle edge case when user starts the selection inside the editor and releases
  32837. // the mouse outside the editor producing a new selection. This weird workaround is needed since
  32838. // Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
  32839. if (editor.inline && !documentMouseUpHandler) {
  32840. documentMouseUpHandler = function(e) {
  32841. var activeEditor = editorManager.activeEditor, dom = activeEditor.dom;
  32842. if (activeEditor.inline && dom && !dom.isChildOf(e.target, activeEditor.getBody())) {
  32843. var rng = activeEditor.selection.getRng();
  32844. if (!rng.collapsed) {
  32845. activeEditor.lastRng = rng;
  32846. }
  32847. }
  32848. };
  32849. DOM.bind(document, 'mouseup', documentMouseUpHandler);
  32850. }
  32851. }
  32852. function unregisterDocumentEvents(e) {
  32853. if (editorManager.focusedEditor == e.editor) {
  32854. editorManager.focusedEditor = null;
  32855. }
  32856. if (!editorManager.activeEditor) {
  32857. DOM.unbind(document, 'selectionchange', selectionChangeHandler);
  32858. DOM.unbind(document, 'focusin', documentFocusInHandler);
  32859. DOM.unbind(document, 'mouseup', documentMouseUpHandler);
  32860. selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
  32861. }
  32862. }
  32863. editorManager.on('AddEditor', registerEvents);
  32864. editorManager.on('RemoveEditor', unregisterDocumentEvents);
  32865. }
  32866. /**
  32867. * Returns true if the specified element is part of the UI for example an button or text input.
  32868. *
  32869. * @method isEditorUIElement
  32870. * @param {Element} elm Element to check if it's part of the UI or not.
  32871. * @return {Boolean} True/false state if the element is part of the UI or not.
  32872. */
  32873. FocusManager.isEditorUIElement = function(elm) {
  32874. // Needs to be converted to string since svg can have focus: #6776
  32875. return elm.className.toString().indexOf('mce-') !== -1;
  32876. };
  32877. return FocusManager;
  32878. });
  32879. // Included from: js/tinymce/classes/EditorManager.js
  32880. /**
  32881. * EditorManager.js
  32882. *
  32883. * Released under LGPL License.
  32884. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  32885. *
  32886. * License: http://www.tinymce.com/license
  32887. * Contributing: http://www.tinymce.com/contributing
  32888. */
  32889. /**
  32890. * This class used as a factory for manager for tinymce.Editor instances.
  32891. *
  32892. * @example
  32893. * tinymce.EditorManager.init({});
  32894. *
  32895. * @class tinymce.EditorManager
  32896. * @mixes tinymce.util.Observable
  32897. * @static
  32898. */
  32899. define("tinymce/EditorManager", [
  32900. "tinymce/Editor",
  32901. "tinymce/dom/DomQuery",
  32902. "tinymce/dom/DOMUtils",
  32903. "tinymce/util/URI",
  32904. "tinymce/Env",
  32905. "tinymce/util/Tools",
  32906. "tinymce/util/Promise",
  32907. "tinymce/util/Observable",
  32908. "tinymce/util/I18n",
  32909. "tinymce/FocusManager"
  32910. ], function(Editor, $, DOMUtils, URI, Env, Tools, Promise, Observable, I18n, FocusManager) {
  32911. var DOM = DOMUtils.DOM;
  32912. var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
  32913. var instanceCounter = 0, beforeUnloadDelegate, EditorManager, boundGlobalEvents = false;
  32914. function globalEventDelegate(e) {
  32915. each(EditorManager.editors, function(editor) {
  32916. if (e.type === 'scroll') {
  32917. editor.fire('ScrollWindow', e);
  32918. } else {
  32919. editor.fire('ResizeWindow', e);
  32920. }
  32921. });
  32922. }
  32923. function toggleGlobalEvents(editors, state) {
  32924. if (state !== boundGlobalEvents) {
  32925. if (state) {
  32926. $(window).on('resize scroll', globalEventDelegate);
  32927. } else {
  32928. $(window).off('resize scroll', globalEventDelegate);
  32929. }
  32930. boundGlobalEvents = state;
  32931. }
  32932. }
  32933. function removeEditorFromList(editor) {
  32934. var editors = EditorManager.editors, removedFromList;
  32935. delete editors[editor.id];
  32936. for (var i = 0; i < editors.length; i++) {
  32937. if (editors[i] == editor) {
  32938. editors.splice(i, 1);
  32939. removedFromList = true;
  32940. break;
  32941. }
  32942. }
  32943. // Select another editor since the active one was removed
  32944. if (EditorManager.activeEditor == editor) {
  32945. EditorManager.activeEditor = editors[0];
  32946. }
  32947. // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
  32948. if (EditorManager.focusedEditor == editor) {
  32949. EditorManager.focusedEditor = null;
  32950. }
  32951. return removedFromList;
  32952. }
  32953. function purgeDestroyedEditor(editor) {
  32954. // User has manually destroyed the editor lets clean up the mess
  32955. if (editor && editor.initialized && !(editor.getContainer() || editor.getBody()).parentNode) {
  32956. removeEditorFromList(editor);
  32957. editor.unbindAllNativeEvents();
  32958. editor.destroy(true);
  32959. editor.removed = true;
  32960. editor = null;
  32961. }
  32962. return editor;
  32963. }
  32964. EditorManager = {
  32965. /**
  32966. * Dom query instance.
  32967. *
  32968. * @property $
  32969. * @type tinymce.dom.DomQuery
  32970. */
  32971. $: $,
  32972. /**
  32973. * Major version of TinyMCE build.
  32974. *
  32975. * @property majorVersion
  32976. * @type String
  32977. */
  32978. majorVersion: '4',
  32979. /**
  32980. * Minor version of TinyMCE build.
  32981. *
  32982. * @property minorVersion
  32983. * @type String
  32984. */
  32985. minorVersion: '4.1',
  32986. /**
  32987. * Release date of TinyMCE build.
  32988. *
  32989. * @property releaseDate
  32990. * @type String
  32991. */
  32992. releaseDate: '2016-07-26',
  32993. /**
  32994. * Collection of editor instances.
  32995. *
  32996. * @property editors
  32997. * @type Object
  32998. * @example
  32999. * for (edId in tinymce.editors)
  33000. * tinymce.editors[edId].save();
  33001. */
  33002. editors: [],
  33003. /**
  33004. * Collection of language pack data.
  33005. *
  33006. * @property i18n
  33007. * @type Object
  33008. */
  33009. i18n: I18n,
  33010. /**
  33011. * Currently active editor instance.
  33012. *
  33013. * @property activeEditor
  33014. * @type tinymce.Editor
  33015. * @example
  33016. * tinyMCE.activeEditor.selection.getContent();
  33017. * tinymce.EditorManager.activeEditor.selection.getContent();
  33018. */
  33019. activeEditor: null,
  33020. setup: function() {
  33021. var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
  33022. // Get base URL for the current document
  33023. documentBaseURL = URI.getDocumentBaseUrl(document.location);
  33024. // Check if the URL is a document based format like: http://site/dir/file and file:///
  33025. // leave other formats like applewebdata://... intact
  33026. if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
  33027. documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
  33028. if (!/[\/\\]$/.test(documentBaseURL)) {
  33029. documentBaseURL += '/';
  33030. }
  33031. }
  33032. // If tinymce is defined and has a base use that or use the old tinyMCEPreInit
  33033. preInit = window.tinymce || window.tinyMCEPreInit;
  33034. if (preInit) {
  33035. baseURL = preInit.base || preInit.baseURL;
  33036. suffix = preInit.suffix;
  33037. } else {
  33038. // Get base where the tinymce script is located
  33039. var scripts = document.getElementsByTagName('script');
  33040. for (var i = 0; i < scripts.length; i++) {
  33041. src = scripts[i].src;
  33042. // Script types supported:
  33043. // tinymce.js tinymce.min.js tinymce.dev.js
  33044. // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js
  33045. // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js
  33046. var srcScript = src.substring(src.lastIndexOf('/'));
  33047. if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
  33048. if (srcScript.indexOf('.min') != -1) {
  33049. suffix = '.min';
  33050. }
  33051. baseURL = src.substring(0, src.lastIndexOf('/'));
  33052. break;
  33053. }
  33054. }
  33055. // We didn't find any baseURL by looking at the script elements
  33056. // Try to use the document.currentScript as a fallback
  33057. if (!baseURL && document.currentScript) {
  33058. src = document.currentScript.src;
  33059. if (src.indexOf('.min') != -1) {
  33060. suffix = '.min';
  33061. }
  33062. baseURL = src.substring(0, src.lastIndexOf('/'));
  33063. }
  33064. }
  33065. /**
  33066. * Base URL where the root directory if TinyMCE is located.
  33067. *
  33068. * @property baseURL
  33069. * @type String
  33070. */
  33071. self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
  33072. /**
  33073. * Document base URL where the current document is located.
  33074. *
  33075. * @property documentBaseURL
  33076. * @type String
  33077. */
  33078. self.documentBaseURL = documentBaseURL;
  33079. /**
  33080. * Absolute baseURI for the installation path of TinyMCE.
  33081. *
  33082. * @property baseURI
  33083. * @type tinymce.util.URI
  33084. */
  33085. self.baseURI = new URI(self.baseURL);
  33086. /**
  33087. * Current suffix to add to each plugin/theme that gets loaded for example ".min".
  33088. *
  33089. * @property suffix
  33090. * @type String
  33091. */
  33092. self.suffix = suffix;
  33093. self.focusManager = new FocusManager(self);
  33094. },
  33095. /**
  33096. * Overrides the default settings for editor instances.
  33097. *
  33098. * @method overrideDefaults
  33099. * @param {Object} defaultSettings Defaults settings object.
  33100. */
  33101. overrideDefaults: function(defaultSettings) {
  33102. var baseUrl, suffix;
  33103. baseUrl = defaultSettings.base_url;
  33104. if (baseUrl) {
  33105. this.baseURL = new URI(this.documentBaseURL).toAbsolute(baseUrl.replace(/\/+$/, ''));
  33106. this.baseURI = new URI(this.baseURL);
  33107. }
  33108. suffix = defaultSettings.suffix;
  33109. if (defaultSettings.suffix) {
  33110. this.suffix = suffix;
  33111. }
  33112. this.defaultSettings = defaultSettings;
  33113. },
  33114. /**
  33115. * Initializes a set of editors. This method will create editors based on various settings.
  33116. *
  33117. * @method init
  33118. * @param {Object} settings Settings object to be passed to each editor instance.
  33119. * @return {tinymce.util.Promise} Promise that gets resolved with an array of editors when all editor instances are initialized.
  33120. * @example
  33121. * // Initializes a editor using the longer method
  33122. * tinymce.EditorManager.init({
  33123. * some_settings : 'some value'
  33124. * });
  33125. *
  33126. * // Initializes a editor instance using the shorter version and with a promise
  33127. * tinymce.init({
  33128. * some_settings : 'some value'
  33129. * }).then(function(editors) {
  33130. * ...
  33131. * });
  33132. */
  33133. init: function(settings) {
  33134. var self = this, result, invalidInlineTargets;
  33135. invalidInlineTargets = Tools.makeMap(
  33136. 'area base basefont br col frame hr img input isindex link meta param embed source wbr track ' +
  33137. 'colgroup option tbody tfoot thead tr script noscript style textarea video audio iframe object menu',
  33138. ' '
  33139. );
  33140. function isInvalidInlineTarget(settings, elm) {
  33141. return settings.inline && elm.tagName.toLowerCase() in invalidInlineTargets;
  33142. }
  33143. function report(msg, elm) {
  33144. // Log in a non test environment
  33145. if (window.console && !window.test) {
  33146. window.console.log(msg, elm);
  33147. }
  33148. }
  33149. function createId(elm) {
  33150. var id = elm.id;
  33151. // Use element id, or unique name or generate a unique id
  33152. if (!id) {
  33153. id = elm.name;
  33154. if (id && !DOM.get(id)) {
  33155. id = elm.name;
  33156. } else {
  33157. // Generate unique name
  33158. id = DOM.uniqueId();
  33159. }
  33160. elm.setAttribute('id', id);
  33161. }
  33162. return id;
  33163. }
  33164. function execCallback(name) {
  33165. var callback = settings[name];
  33166. if (!callback) {
  33167. return;
  33168. }
  33169. return callback.apply(self, Array.prototype.slice.call(arguments, 2));
  33170. }
  33171. function hasClass(elm, className) {
  33172. return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className);
  33173. }
  33174. function findTargets(settings) {
  33175. var l, targets = [];
  33176. if (settings.types) {
  33177. each(settings.types, function(type) {
  33178. targets = targets.concat(DOM.select(type.selector));
  33179. });
  33180. return targets;
  33181. } else if (settings.selector) {
  33182. return DOM.select(settings.selector);
  33183. } else if (settings.target) {
  33184. return [settings.target];
  33185. }
  33186. // Fallback to old setting
  33187. switch (settings.mode) {
  33188. case "exact":
  33189. l = settings.elements || '';
  33190. if (l.length > 0) {
  33191. each(explode(l), function(id) {
  33192. var elm;
  33193. if ((elm = DOM.get(id))) {
  33194. targets.push(elm);
  33195. } else {
  33196. each(document.forms, function(f) {
  33197. each(f.elements, function(e) {
  33198. if (e.name === id) {
  33199. id = 'mce_editor_' + instanceCounter++;
  33200. DOM.setAttrib(e, 'id', id);
  33201. targets.push(e);
  33202. }
  33203. });
  33204. });
  33205. }
  33206. });
  33207. }
  33208. break;
  33209. case "textareas":
  33210. case "specific_textareas":
  33211. each(DOM.select('textarea'), function(elm) {
  33212. if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
  33213. return;
  33214. }
  33215. if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
  33216. targets.push(elm);
  33217. }
  33218. });
  33219. break;
  33220. }
  33221. return targets;
  33222. }
  33223. var provideResults = function(editors) {
  33224. result = editors;
  33225. };
  33226. function initEditors() {
  33227. var initCount = 0, editors = [], targets;
  33228. function createEditor(id, settings, targetElm) {
  33229. var editor = new Editor(id, settings, self);
  33230. editors.push(editor);
  33231. editor.on('init', function() {
  33232. if (++initCount === targets.length) {
  33233. provideResults(editors);
  33234. }
  33235. });
  33236. editor.targetElm = editor.targetElm || targetElm;
  33237. editor.render();
  33238. }
  33239. DOM.unbind(window, 'ready', initEditors);
  33240. execCallback('onpageload');
  33241. targets = $.unique(findTargets(settings));
  33242. // TODO: Deprecate this one
  33243. if (settings.types) {
  33244. each(settings.types, function(type) {
  33245. Tools.each(targets, function(elm) {
  33246. if (DOM.is(elm, type.selector)) {
  33247. createEditor(createId(elm), extend({}, settings, type), elm);
  33248. return false;
  33249. }
  33250. return true;
  33251. });
  33252. });
  33253. return;
  33254. }
  33255. Tools.each(targets, function(elm) {
  33256. purgeDestroyedEditor(self.get(elm.id));
  33257. });
  33258. targets = Tools.grep(targets, function(elm) {
  33259. return !self.get(elm.id);
  33260. });
  33261. each(targets, function(elm) {
  33262. if (isInvalidInlineTarget(settings, elm)) {
  33263. report('Could not initialize inline editor on invalid inline target element', elm);
  33264. } else {
  33265. createEditor(createId(elm), settings, elm);
  33266. }
  33267. });
  33268. }
  33269. self.settings = settings;
  33270. DOM.bind(window, 'ready', initEditors);
  33271. return new Promise(function(resolve) {
  33272. if (result) {
  33273. resolve(result);
  33274. } else {
  33275. provideResults = function(editors) {
  33276. resolve(editors);
  33277. };
  33278. }
  33279. });
  33280. },
  33281. /**
  33282. * Returns a editor instance by id.
  33283. *
  33284. * @method get
  33285. * @param {String/Number} id Editor instance id or index to return.
  33286. * @return {tinymce.Editor} Editor instance to return.
  33287. * @example
  33288. * // Adds an onclick event to an editor by id (shorter version)
  33289. * tinymce.get('mytextbox').on('click', function(e) {
  33290. * ed.windowManager.alert('Hello world!');
  33291. * });
  33292. *
  33293. * // Adds an onclick event to an editor by id (longer version)
  33294. * tinymce.EditorManager.get('mytextbox').on('click', function(e) {
  33295. * ed.windowManager.alert('Hello world!');
  33296. * });
  33297. */
  33298. get: function(id) {
  33299. if (!arguments.length) {
  33300. return this.editors;
  33301. }
  33302. return id in this.editors ? this.editors[id] : null;
  33303. },
  33304. /**
  33305. * Adds an editor instance to the editor collection. This will also set it as the active editor.
  33306. *
  33307. * @method add
  33308. * @param {tinymce.Editor} editor Editor instance to add to the collection.
  33309. * @return {tinymce.Editor} The same instance that got passed in.
  33310. */
  33311. add: function(editor) {
  33312. var self = this, editors = self.editors;
  33313. // Add named and index editor instance
  33314. editors[editor.id] = editor;
  33315. editors.push(editor);
  33316. toggleGlobalEvents(editors, true);
  33317. // Doesn't call setActive method since we don't want
  33318. // to fire a bunch of activate/deactivate calls while initializing
  33319. self.activeEditor = editor;
  33320. /**
  33321. * Fires when an editor is added to the EditorManager collection.
  33322. *
  33323. * @event AddEditor
  33324. * @param {Object} e Event arguments.
  33325. */
  33326. self.fire('AddEditor', {editor: editor});
  33327. if (!beforeUnloadDelegate) {
  33328. beforeUnloadDelegate = function() {
  33329. self.fire('BeforeUnload');
  33330. };
  33331. DOM.bind(window, 'beforeunload', beforeUnloadDelegate);
  33332. }
  33333. return editor;
  33334. },
  33335. /**
  33336. * Creates an editor instance and adds it to the EditorManager collection.
  33337. *
  33338. * @method createEditor
  33339. * @param {String} id Instance id to use for editor.
  33340. * @param {Object} settings Editor instance settings.
  33341. * @return {tinymce.Editor} Editor instance that got created.
  33342. */
  33343. createEditor: function(id, settings) {
  33344. return this.add(new Editor(id, settings, this));
  33345. },
  33346. /**
  33347. * Removes a editor or editors form page.
  33348. *
  33349. * @example
  33350. * // Remove all editors bound to divs
  33351. * tinymce.remove('div');
  33352. *
  33353. * // Remove all editors bound to textareas
  33354. * tinymce.remove('textarea');
  33355. *
  33356. * // Remove all editors
  33357. * tinymce.remove();
  33358. *
  33359. * // Remove specific instance by id
  33360. * tinymce.remove('#id');
  33361. *
  33362. * @method remove
  33363. * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove.
  33364. * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
  33365. */
  33366. remove: function(selector) {
  33367. var self = this, i, editors = self.editors, editor;
  33368. // Remove all editors
  33369. if (!selector) {
  33370. for (i = editors.length - 1; i >= 0; i--) {
  33371. self.remove(editors[i]);
  33372. }
  33373. return;
  33374. }
  33375. // Remove editors by selector
  33376. if (typeof selector == "string") {
  33377. selector = selector.selector || selector;
  33378. each(DOM.select(selector), function(elm) {
  33379. editor = editors[elm.id];
  33380. if (editor) {
  33381. self.remove(editor);
  33382. }
  33383. });
  33384. return;
  33385. }
  33386. // Remove specific editor
  33387. editor = selector;
  33388. // Not in the collection
  33389. if (!editors[editor.id]) {
  33390. return null;
  33391. }
  33392. /**
  33393. * Fires when an editor is removed from EditorManager collection.
  33394. *
  33395. * @event RemoveEditor
  33396. * @param {Object} e Event arguments.
  33397. */
  33398. if (removeEditorFromList(editor)) {
  33399. self.fire('RemoveEditor', {editor: editor});
  33400. }
  33401. if (!editors.length) {
  33402. DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
  33403. }
  33404. editor.remove();
  33405. toggleGlobalEvents(editors, editors.length > 0);
  33406. return editor;
  33407. },
  33408. /**
  33409. * Executes a specific command on the currently active editor.
  33410. *
  33411. * @method execCommand
  33412. * @param {String} cmd Command to perform for example Bold.
  33413. * @param {Boolean} ui Optional boolean state if a UI should be presented for the command or not.
  33414. * @param {String} value Optional value parameter like for example an URL to a link.
  33415. * @return {Boolean} true/false if the command was executed or not.
  33416. */
  33417. execCommand: function(cmd, ui, value) {
  33418. var self = this, editor = self.get(value);
  33419. // Manager commands
  33420. switch (cmd) {
  33421. case "mceAddEditor":
  33422. if (!self.get(value)) {
  33423. new Editor(value, self.settings, self).render();
  33424. }
  33425. return true;
  33426. case "mceRemoveEditor":
  33427. if (editor) {
  33428. editor.remove();
  33429. }
  33430. return true;
  33431. case 'mceToggleEditor':
  33432. if (!editor) {
  33433. self.execCommand('mceAddEditor', 0, value);
  33434. return true;
  33435. }
  33436. if (editor.isHidden()) {
  33437. editor.show();
  33438. } else {
  33439. editor.hide();
  33440. }
  33441. return true;
  33442. }
  33443. // Run command on active editor
  33444. if (self.activeEditor) {
  33445. return self.activeEditor.execCommand(cmd, ui, value);
  33446. }
  33447. return false;
  33448. },
  33449. /**
  33450. * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
  33451. *
  33452. * @method triggerSave
  33453. * @example
  33454. * // Saves all contents
  33455. * tinyMCE.triggerSave();
  33456. */
  33457. triggerSave: function() {
  33458. each(this.editors, function(editor) {
  33459. editor.save();
  33460. });
  33461. },
  33462. /**
  33463. * Adds a language pack, this gets called by the loaded language files like en.js.
  33464. *
  33465. * @method addI18n
  33466. * @param {String} code Optional language code.
  33467. * @param {Object} items Name/value object with translations.
  33468. */
  33469. addI18n: function(code, items) {
  33470. I18n.add(code, items);
  33471. },
  33472. /**
  33473. * Translates the specified string using the language pack items.
  33474. *
  33475. * @method translate
  33476. * @param {String/Array/Object} text String to translate
  33477. * @return {String} Translated string.
  33478. */
  33479. translate: function(text) {
  33480. return I18n.translate(text);
  33481. },
  33482. /**
  33483. * Sets the active editor instance and fires the deactivate/activate events.
  33484. *
  33485. * @method setActive
  33486. * @param {tinymce.Editor} editor Editor instance to set as the active instance.
  33487. */
  33488. setActive: function(editor) {
  33489. var activeEditor = this.activeEditor;
  33490. if (this.activeEditor != editor) {
  33491. if (activeEditor) {
  33492. activeEditor.fire('deactivate', {relatedTarget: editor});
  33493. }
  33494. editor.fire('activate', {relatedTarget: activeEditor});
  33495. }
  33496. this.activeEditor = editor;
  33497. }
  33498. };
  33499. extend(EditorManager, Observable);
  33500. EditorManager.setup();
  33501. // Export EditorManager as tinymce/tinymce in global namespace
  33502. window.tinymce = window.tinyMCE = EditorManager;
  33503. return EditorManager;
  33504. });
  33505. // Included from: js/tinymce/classes/LegacyInput.js
  33506. /**
  33507. * LegacyInput.js
  33508. *
  33509. * Released under LGPL License.
  33510. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  33511. *
  33512. * License: http://www.tinymce.com/license
  33513. * Contributing: http://www.tinymce.com/contributing
  33514. */
  33515. /**
  33516. * Converts legacy input to modern HTML.
  33517. *
  33518. * @class tinymce.LegacyInput
  33519. * @private
  33520. */
  33521. define("tinymce/LegacyInput", [
  33522. "tinymce/EditorManager",
  33523. "tinymce/util/Tools"
  33524. ], function(EditorManager, Tools) {
  33525. var each = Tools.each, explode = Tools.explode;
  33526. EditorManager.on('AddEditor', function(e) {
  33527. var editor = e.editor;
  33528. editor.on('preInit', function() {
  33529. var filters, fontSizes, dom, settings = editor.settings;
  33530. function replaceWithSpan(node, styles) {
  33531. each(styles, function(value, name) {
  33532. if (value) {
  33533. dom.setStyle(node, name, value);
  33534. }
  33535. });
  33536. dom.rename(node, 'span');
  33537. }
  33538. function convert(e) {
  33539. dom = editor.dom;
  33540. if (settings.convert_fonts_to_spans) {
  33541. each(dom.select('font,u,strike', e.node), function(node) {
  33542. filters[node.nodeName.toLowerCase()](dom, node);
  33543. });
  33544. }
  33545. }
  33546. if (settings.inline_styles) {
  33547. fontSizes = explode(settings.font_size_legacy_values);
  33548. filters = {
  33549. font: function(dom, node) {
  33550. replaceWithSpan(node, {
  33551. backgroundColor: node.style.backgroundColor,
  33552. color: node.color,
  33553. fontFamily: node.face,
  33554. fontSize: fontSizes[parseInt(node.size, 10) - 1]
  33555. });
  33556. },
  33557. u: function(dom, node) {
  33558. // HTML5 allows U element
  33559. if (editor.settings.schema === "html4") {
  33560. replaceWithSpan(node, {
  33561. textDecoration: 'underline'
  33562. });
  33563. }
  33564. },
  33565. strike: function(dom, node) {
  33566. replaceWithSpan(node, {
  33567. textDecoration: 'line-through'
  33568. });
  33569. }
  33570. };
  33571. editor.on('PreProcess SetContent', convert);
  33572. }
  33573. });
  33574. });
  33575. });
  33576. // Included from: js/tinymce/classes/util/XHR.js
  33577. /**
  33578. * XHR.js
  33579. *
  33580. * Released under LGPL License.
  33581. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  33582. *
  33583. * License: http://www.tinymce.com/license
  33584. * Contributing: http://www.tinymce.com/contributing
  33585. */
  33586. /**
  33587. * This class enables you to send XMLHTTPRequests cross browser.
  33588. * @class tinymce.util.XHR
  33589. * @mixes tinymce.util.Observable
  33590. * @static
  33591. * @example
  33592. * // Sends a low level Ajax request
  33593. * tinymce.util.XHR.send({
  33594. * url: 'someurl',
  33595. * success: function(text) {
  33596. * console.debug(text);
  33597. * }
  33598. * });
  33599. *
  33600. * // Add custom header to XHR request
  33601. * tinymce.util.XHR.on('beforeSend', function(e) {
  33602. * e.xhr.setRequestHeader('X-Requested-With', 'Something');
  33603. * });
  33604. */
  33605. define("tinymce/util/XHR", [
  33606. "tinymce/util/Observable",
  33607. "tinymce/util/Tools"
  33608. ], function(Observable, Tools) {
  33609. var XHR = {
  33610. /**
  33611. * Sends a XMLHTTPRequest.
  33612. * Consult the Wiki for details on what settings this method takes.
  33613. *
  33614. * @method send
  33615. * @param {Object} settings Object will target URL, callbacks and other info needed to make the request.
  33616. */
  33617. send: function(settings) {
  33618. var xhr, count = 0;
  33619. function ready() {
  33620. if (!settings.async || xhr.readyState == 4 || count++ > 10000) {
  33621. if (settings.success && count < 10000 && xhr.status == 200) {
  33622. settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings);
  33623. } else if (settings.error) {
  33624. settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings);
  33625. }
  33626. xhr = null;
  33627. } else {
  33628. setTimeout(ready, 10);
  33629. }
  33630. }
  33631. // Default settings
  33632. settings.scope = settings.scope || this;
  33633. settings.success_scope = settings.success_scope || settings.scope;
  33634. settings.error_scope = settings.error_scope || settings.scope;
  33635. settings.async = settings.async === false ? false : true;
  33636. settings.data = settings.data || '';
  33637. XHR.fire('beforeInitialize', {settings: settings});
  33638. xhr = new XMLHttpRequest();
  33639. if (xhr) {
  33640. if (xhr.overrideMimeType) {
  33641. xhr.overrideMimeType(settings.content_type);
  33642. }
  33643. xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async);
  33644. if (settings.crossDomain) {
  33645. xhr.withCredentials = true;
  33646. }
  33647. if (settings.content_type) {
  33648. xhr.setRequestHeader('Content-Type', settings.content_type);
  33649. }
  33650. if (settings.requestheaders) {
  33651. Tools.each(settings.requestheaders, function(header) {
  33652. xhr.setRequestHeader(header.key, header.value);
  33653. });
  33654. }
  33655. xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  33656. xhr = XHR.fire('beforeSend', {xhr: xhr, settings: settings}).xhr;
  33657. xhr.send(settings.data);
  33658. // Syncronous request
  33659. if (!settings.async) {
  33660. return ready();
  33661. }
  33662. // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
  33663. setTimeout(ready, 10);
  33664. }
  33665. }
  33666. };
  33667. Tools.extend(XHR, Observable);
  33668. return XHR;
  33669. });
  33670. // Included from: js/tinymce/classes/util/JSON.js
  33671. /**
  33672. * JSON.js
  33673. *
  33674. * Released under LGPL License.
  33675. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  33676. *
  33677. * License: http://www.tinymce.com/license
  33678. * Contributing: http://www.tinymce.com/contributing
  33679. */
  33680. /**
  33681. * JSON parser and serializer class.
  33682. *
  33683. * @class tinymce.util.JSON
  33684. * @static
  33685. * @example
  33686. * // JSON parse a string into an object
  33687. * var obj = tinymce.util.JSON.parse(somestring);
  33688. *
  33689. * // JSON serialize a object into an string
  33690. * var str = tinymce.util.JSON.serialize(obj);
  33691. */
  33692. define("tinymce/util/JSON", [], function() {
  33693. function serialize(o, quote) {
  33694. var i, v, t, name;
  33695. quote = quote || '"';
  33696. if (o === null) {
  33697. return 'null';
  33698. }
  33699. t = typeof o;
  33700. if (t == 'string') {
  33701. v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
  33702. /*eslint no-control-regex:0 */
  33703. return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
  33704. // Make sure single quotes never get encoded inside double quotes for JSON compatibility
  33705. if (quote === '"' && a === "'") {
  33706. return a;
  33707. }
  33708. i = v.indexOf(b);
  33709. if (i + 1) {
  33710. return '\\' + v.charAt(i + 1);
  33711. }
  33712. a = b.charCodeAt().toString(16);
  33713. return '\\u' + '0000'.substring(a.length) + a;
  33714. }) + quote;
  33715. }
  33716. if (t == 'object') {
  33717. if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
  33718. for (i = 0, v = '['; i < o.length; i++) {
  33719. v += (i > 0 ? ',' : '') + serialize(o[i], quote);
  33720. }
  33721. return v + ']';
  33722. }
  33723. v = '{';
  33724. for (name in o) {
  33725. if (o.hasOwnProperty(name)) {
  33726. v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name +
  33727. quote + ':' + serialize(o[name], quote) : '';
  33728. }
  33729. }
  33730. return v + '}';
  33731. }
  33732. return '' + o;
  33733. }
  33734. return {
  33735. /**
  33736. * Serializes the specified object as a JSON string.
  33737. *
  33738. * @method serialize
  33739. * @param {Object} obj Object to serialize as a JSON string.
  33740. * @param {String} quote Optional quote string defaults to ".
  33741. * @return {string} JSON string serialized from input.
  33742. */
  33743. serialize: serialize,
  33744. /**
  33745. * Unserializes/parses the specified JSON string into a object.
  33746. *
  33747. * @method parse
  33748. * @param {string} s JSON String to parse into a JavaScript object.
  33749. * @return {Object} Object from input JSON string or undefined if it failed.
  33750. */
  33751. parse: function(text) {
  33752. try {
  33753. // Trick uglify JS
  33754. return window[String.fromCharCode(101) + 'val']('(' + text + ')');
  33755. } catch (ex) {
  33756. // Ignore
  33757. }
  33758. }
  33759. /**#@-*/
  33760. };
  33761. });
  33762. // Included from: js/tinymce/classes/util/JSONRequest.js
  33763. /**
  33764. * JSONRequest.js
  33765. *
  33766. * Released under LGPL License.
  33767. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  33768. *
  33769. * License: http://www.tinymce.com/license
  33770. * Contributing: http://www.tinymce.com/contributing
  33771. */
  33772. /**
  33773. * This class enables you to use JSON-RPC to call backend methods.
  33774. *
  33775. * @class tinymce.util.JSONRequest
  33776. * @example
  33777. * var json = new tinymce.util.JSONRequest({
  33778. * url: 'somebackend.php'
  33779. * });
  33780. *
  33781. * // Send RPC call 1
  33782. * json.send({
  33783. * method: 'someMethod1',
  33784. * params: ['a', 'b'],
  33785. * success: function(result) {
  33786. * console.dir(result);
  33787. * }
  33788. * });
  33789. *
  33790. * // Send RPC call 2
  33791. * json.send({
  33792. * method: 'someMethod2',
  33793. * params: ['a', 'b'],
  33794. * success: function(result) {
  33795. * console.dir(result);
  33796. * }
  33797. * });
  33798. */
  33799. define("tinymce/util/JSONRequest", [
  33800. "tinymce/util/JSON",
  33801. "tinymce/util/XHR",
  33802. "tinymce/util/Tools"
  33803. ], function(JSON, XHR, Tools) {
  33804. var extend = Tools.extend;
  33805. function JSONRequest(settings) {
  33806. this.settings = extend({}, settings);
  33807. this.count = 0;
  33808. }
  33809. /**
  33810. * Simple helper function to send a JSON-RPC request without the need to initialize an object.
  33811. * Consult the Wiki API documentation for more details on what you can pass to this function.
  33812. *
  33813. * @method sendRPC
  33814. * @static
  33815. * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
  33816. */
  33817. JSONRequest.sendRPC = function(o) {
  33818. return new JSONRequest().send(o);
  33819. };
  33820. JSONRequest.prototype = {
  33821. /**
  33822. * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
  33823. *
  33824. * @method send
  33825. * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc.
  33826. */
  33827. send: function(args) {
  33828. var ecb = args.error, scb = args.success;
  33829. args = extend(this.settings, args);
  33830. args.success = function(c, x) {
  33831. c = JSON.parse(c);
  33832. if (typeof c == 'undefined') {
  33833. c = {
  33834. error: 'JSON Parse error.'
  33835. };
  33836. }
  33837. if (c.error) {
  33838. ecb.call(args.error_scope || args.scope, c.error, x);
  33839. } else {
  33840. scb.call(args.success_scope || args.scope, c.result);
  33841. }
  33842. };
  33843. args.error = function(ty, x) {
  33844. if (ecb) {
  33845. ecb.call(args.error_scope || args.scope, ty, x);
  33846. }
  33847. };
  33848. args.data = JSON.serialize({
  33849. id: args.id || 'c' + (this.count++),
  33850. method: args.method,
  33851. params: args.params
  33852. });
  33853. // JSON content type for Ruby on rails. Bug: #1883287
  33854. args.content_type = 'application/json';
  33855. XHR.send(args);
  33856. }
  33857. };
  33858. return JSONRequest;
  33859. });
  33860. // Included from: js/tinymce/classes/util/JSONP.js
  33861. /**
  33862. * JSONP.js
  33863. *
  33864. * Released under LGPL License.
  33865. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  33866. *
  33867. * License: http://www.tinymce.com/license
  33868. * Contributing: http://www.tinymce.com/contributing
  33869. */
  33870. define("tinymce/util/JSONP", [
  33871. "tinymce/dom/DOMUtils"
  33872. ], function(DOMUtils) {
  33873. return {
  33874. callbacks: {},
  33875. count: 0,
  33876. send: function(settings) {
  33877. var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count;
  33878. var id = 'tinymce_jsonp_' + count;
  33879. self.callbacks[count] = function(json) {
  33880. dom.remove(id);
  33881. delete self.callbacks[count];
  33882. settings.callback(json);
  33883. };
  33884. dom.add(dom.doc.body, 'script', {
  33885. id: id,
  33886. src: settings.url,
  33887. type: 'text/javascript'
  33888. });
  33889. self.count++;
  33890. }
  33891. };
  33892. });
  33893. // Included from: js/tinymce/classes/util/LocalStorage.js
  33894. /**
  33895. * LocalStorage.js
  33896. *
  33897. * Released under LGPL License.
  33898. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  33899. *
  33900. * License: http://www.tinymce.com/license
  33901. * Contributing: http://www.tinymce.com/contributing
  33902. */
  33903. /**
  33904. * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers.
  33905. * Storage is done using userData on IE 7 and a special serialization format. The format is designed
  33906. * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This
  33907. * makes it possible to store for example HTML data.
  33908. *
  33909. * Storage format for userData:
  33910. * <base 32 key length>,<key string>,<base 32 value length>,<value>,...
  33911. *
  33912. * For example this data key1=value1,key2=value2 would be:
  33913. * 4,key1,6,value1,4,key2,6,value2
  33914. *
  33915. * @class tinymce.util.LocalStorage
  33916. * @static
  33917. * @version 4.0
  33918. * @example
  33919. * tinymce.util.LocalStorage.setItem('key', 'value');
  33920. * var value = tinymce.util.LocalStorage.getItem('key');
  33921. */
  33922. define("tinymce/util/LocalStorage", [], function() {
  33923. var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport;
  33924. // Check for native support
  33925. try {
  33926. if (window.localStorage) {
  33927. return localStorage;
  33928. }
  33929. } catch (ex) {
  33930. // Ignore
  33931. }
  33932. userDataKey = "tinymce";
  33933. storageElm = document.documentElement;
  33934. hasOldIEDataSupport = !!storageElm.addBehavior;
  33935. if (hasOldIEDataSupport) {
  33936. storageElm.addBehavior('#default#userData');
  33937. }
  33938. /**
  33939. * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters.
  33940. */
  33941. function updateKeys() {
  33942. keys = [];
  33943. for (var key in items) {
  33944. keys.push(key);
  33945. }
  33946. LocalStorage.length = keys.length;
  33947. }
  33948. /**
  33949. * Loads the userData string and parses it into the items structure.
  33950. */
  33951. function load() {
  33952. var key, data, value, pos = 0;
  33953. items = {};
  33954. // localStorage can be disabled on WebKit/Gecko so make a dummy storage
  33955. if (!hasOldIEDataSupport) {
  33956. return;
  33957. }
  33958. function next(end) {
  33959. var value, nextPos;
  33960. nextPos = end !== undefined ? pos + end : data.indexOf(',', pos);
  33961. if (nextPos === -1 || nextPos > data.length) {
  33962. return null;
  33963. }
  33964. value = data.substring(pos, nextPos);
  33965. pos = nextPos + 1;
  33966. return value;
  33967. }
  33968. storageElm.load(userDataKey);
  33969. data = storageElm.getAttribute(userDataKey) || '';
  33970. do {
  33971. var offset = next();
  33972. if (offset === null) {
  33973. break;
  33974. }
  33975. key = next(parseInt(offset, 32) || 0);
  33976. if (key !== null) {
  33977. offset = next();
  33978. if (offset === null) {
  33979. break;
  33980. }
  33981. value = next(parseInt(offset, 32) || 0);
  33982. if (key) {
  33983. items[key] = value;
  33984. }
  33985. }
  33986. } while (key !== null);
  33987. updateKeys();
  33988. }
  33989. /**
  33990. * Saves the items structure into a the userData format.
  33991. */
  33992. function save() {
  33993. var value, data = '';
  33994. // localStorage can be disabled on WebKit/Gecko so make a dummy storage
  33995. if (!hasOldIEDataSupport) {
  33996. return;
  33997. }
  33998. for (var key in items) {
  33999. value = items[key];
  34000. data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value;
  34001. }
  34002. storageElm.setAttribute(userDataKey, data);
  34003. try {
  34004. storageElm.save(userDataKey);
  34005. } catch (ex) {
  34006. // Ignore disk full
  34007. }
  34008. updateKeys();
  34009. }
  34010. LocalStorage = {
  34011. /**
  34012. * Length of the number of items in storage.
  34013. *
  34014. * @property length
  34015. * @type Number
  34016. * @return {Number} Number of items in storage.
  34017. */
  34018. //length:0,
  34019. /**
  34020. * Returns the key name by index.
  34021. *
  34022. * @method key
  34023. * @param {Number} index Index of key to return.
  34024. * @return {String} Key value or null if it wasn't found.
  34025. */
  34026. key: function(index) {
  34027. return keys[index];
  34028. },
  34029. /**
  34030. * Returns the value if the specified key or null if it wasn't found.
  34031. *
  34032. * @method getItem
  34033. * @param {String} key Key of item to retrieve.
  34034. * @return {String} Value of the specified item or null if it wasn't found.
  34035. */
  34036. getItem: function(key) {
  34037. return key in items ? items[key] : null;
  34038. },
  34039. /**
  34040. * Sets the value of the specified item by it's key.
  34041. *
  34042. * @method setItem
  34043. * @param {String} key Key of the item to set.
  34044. * @param {String} value Value of the item to set.
  34045. */
  34046. setItem: function(key, value) {
  34047. items[key] = "" + value;
  34048. save();
  34049. },
  34050. /**
  34051. * Removes the specified item by key.
  34052. *
  34053. * @method removeItem
  34054. * @param {String} key Key of item to remove.
  34055. */
  34056. removeItem: function(key) {
  34057. delete items[key];
  34058. save();
  34059. },
  34060. /**
  34061. * Removes all items.
  34062. *
  34063. * @method clear
  34064. */
  34065. clear: function() {
  34066. items = {};
  34067. save();
  34068. }
  34069. };
  34070. load();
  34071. return LocalStorage;
  34072. });
  34073. // Included from: js/tinymce/classes/Compat.js
  34074. /**
  34075. * Compat.js
  34076. *
  34077. * Released under LGPL License.
  34078. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  34079. *
  34080. * License: http://www.tinymce.com/license
  34081. * Contributing: http://www.tinymce.com/contributing
  34082. */
  34083. /**
  34084. * TinyMCE core class.
  34085. *
  34086. * @static
  34087. * @class tinymce
  34088. * @borrow-members tinymce.EditorManager
  34089. * @borrow-members tinymce.util.Tools
  34090. */
  34091. define("tinymce/Compat", [
  34092. "tinymce/dom/DOMUtils",
  34093. "tinymce/dom/EventUtils",
  34094. "tinymce/dom/ScriptLoader",
  34095. "tinymce/AddOnManager",
  34096. "tinymce/util/Tools",
  34097. "tinymce/Env"
  34098. ], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
  34099. var tinymce = window.tinymce;
  34100. /**
  34101. * @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
  34102. * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
  34103. * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
  34104. * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
  34105. */
  34106. tinymce.DOM = DOMUtils.DOM;
  34107. tinymce.ScriptLoader = ScriptLoader.ScriptLoader;
  34108. tinymce.PluginManager = AddOnManager.PluginManager;
  34109. tinymce.ThemeManager = AddOnManager.ThemeManager;
  34110. tinymce.dom = tinymce.dom || {};
  34111. tinymce.dom.Event = EventUtils.Event;
  34112. Tools.each(Tools, function(func, key) {
  34113. tinymce[key] = func;
  34114. });
  34115. Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
  34116. tinymce[name] = Env[name.substr(2).toLowerCase()];
  34117. });
  34118. return {};
  34119. });
  34120. // Describe the different namespaces
  34121. /**
  34122. * Root level namespace this contains classes directly related to the TinyMCE editor.
  34123. *
  34124. * @namespace tinymce
  34125. */
  34126. /**
  34127. * Contains classes for handling the browsers DOM.
  34128. *
  34129. * @namespace tinymce.dom
  34130. */
  34131. /**
  34132. * Contains html parser and serializer logic.
  34133. *
  34134. * @namespace tinymce.html
  34135. */
  34136. /**
  34137. * Contains the different UI types such as buttons, listboxes etc.
  34138. *
  34139. * @namespace tinymce.ui
  34140. */
  34141. /**
  34142. * Contains various utility classes such as json parser, cookies etc.
  34143. *
  34144. * @namespace tinymce.util
  34145. */
  34146. /**
  34147. * Contains modules to handle data binding.
  34148. *
  34149. * @namespace tinymce.data
  34150. */
  34151. // Included from: js/tinymce/classes/ui/Layout.js
  34152. /**
  34153. * Layout.js
  34154. *
  34155. * Released under LGPL License.
  34156. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  34157. *
  34158. * License: http://www.tinymce.com/license
  34159. * Contributing: http://www.tinymce.com/contributing
  34160. */
  34161. /**
  34162. * Base layout manager class.
  34163. *
  34164. * @class tinymce.ui.Layout
  34165. */
  34166. define("tinymce/ui/Layout", [
  34167. "tinymce/util/Class",
  34168. "tinymce/util/Tools"
  34169. ], function(Class, Tools) {
  34170. "use strict";
  34171. return Class.extend({
  34172. Defaults: {
  34173. firstControlClass: 'first',
  34174. lastControlClass: 'last'
  34175. },
  34176. /**
  34177. * Constructs a layout instance with the specified settings.
  34178. *
  34179. * @constructor
  34180. * @param {Object} settings Name/value object with settings.
  34181. */
  34182. init: function(settings) {
  34183. this.settings = Tools.extend({}, this.Defaults, settings);
  34184. },
  34185. /**
  34186. * This method gets invoked before the layout renders the controls.
  34187. *
  34188. * @method preRender
  34189. * @param {tinymce.ui.Container} container Container instance to preRender.
  34190. */
  34191. preRender: function(container) {
  34192. container.bodyClasses.add(this.settings.containerClass);
  34193. },
  34194. /**
  34195. * Applies layout classes to the container.
  34196. *
  34197. * @private
  34198. */
  34199. applyClasses: function(items) {
  34200. var self = this, settings = self.settings, firstClass, lastClass, firstItem, lastItem;
  34201. firstClass = settings.firstControlClass;
  34202. lastClass = settings.lastControlClass;
  34203. items.each(function(item) {
  34204. item.classes.remove(firstClass).remove(lastClass).add(settings.controlClass);
  34205. if (item.visible()) {
  34206. if (!firstItem) {
  34207. firstItem = item;
  34208. }
  34209. lastItem = item;
  34210. }
  34211. });
  34212. if (firstItem) {
  34213. firstItem.classes.add(firstClass);
  34214. }
  34215. if (lastItem) {
  34216. lastItem.classes.add(lastClass);
  34217. }
  34218. },
  34219. /**
  34220. * Renders the specified container and any layout specific HTML.
  34221. *
  34222. * @method renderHtml
  34223. * @param {tinymce.ui.Container} container Container to render HTML for.
  34224. */
  34225. renderHtml: function(container) {
  34226. var self = this, html = '';
  34227. self.applyClasses(container.items());
  34228. container.items().each(function(item) {
  34229. html += item.renderHtml();
  34230. });
  34231. return html;
  34232. },
  34233. /**
  34234. * Recalculates the positions of the controls in the specified container.
  34235. *
  34236. * @method recalc
  34237. * @param {tinymce.ui.Container} container Container instance to recalc.
  34238. */
  34239. recalc: function() {
  34240. },
  34241. /**
  34242. * This method gets invoked after the layout renders the controls.
  34243. *
  34244. * @method postRender
  34245. * @param {tinymce.ui.Container} container Container instance to postRender.
  34246. */
  34247. postRender: function() {
  34248. },
  34249. isNative: function() {
  34250. return false;
  34251. }
  34252. });
  34253. });
  34254. // Included from: js/tinymce/classes/ui/AbsoluteLayout.js
  34255. /**
  34256. * AbsoluteLayout.js
  34257. *
  34258. * Released under LGPL License.
  34259. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  34260. *
  34261. * License: http://www.tinymce.com/license
  34262. * Contributing: http://www.tinymce.com/contributing
  34263. */
  34264. /**
  34265. * LayoutManager for absolute positioning. This layout manager is more of
  34266. * a base class for other layouts but can be created and used directly.
  34267. *
  34268. * @-x-less AbsoluteLayout.less
  34269. * @class tinymce.ui.AbsoluteLayout
  34270. * @extends tinymce.ui.Layout
  34271. */
  34272. define("tinymce/ui/AbsoluteLayout", [
  34273. "tinymce/ui/Layout"
  34274. ], function(Layout) {
  34275. "use strict";
  34276. return Layout.extend({
  34277. Defaults: {
  34278. containerClass: 'abs-layout',
  34279. controlClass: 'abs-layout-item'
  34280. },
  34281. /**
  34282. * Recalculates the positions of the controls in the specified container.
  34283. *
  34284. * @method recalc
  34285. * @param {tinymce.ui.Container} container Container instance to recalc.
  34286. */
  34287. recalc: function(container) {
  34288. container.items().filter(':visible').each(function(ctrl) {
  34289. var settings = ctrl.settings;
  34290. ctrl.layoutRect({
  34291. x: settings.x,
  34292. y: settings.y,
  34293. w: settings.w,
  34294. h: settings.h
  34295. });
  34296. if (ctrl.recalc) {
  34297. ctrl.recalc();
  34298. }
  34299. });
  34300. },
  34301. /**
  34302. * Renders the specified container and any layout specific HTML.
  34303. *
  34304. * @method renderHtml
  34305. * @param {tinymce.ui.Container} container Container to render HTML for.
  34306. */
  34307. renderHtml: function(container) {
  34308. return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container);
  34309. }
  34310. });
  34311. });
  34312. // Included from: js/tinymce/classes/ui/Button.js
  34313. /**
  34314. * Button.js
  34315. *
  34316. * Released under LGPL License.
  34317. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  34318. *
  34319. * License: http://www.tinymce.com/license
  34320. * Contributing: http://www.tinymce.com/contributing
  34321. */
  34322. /**
  34323. * This class is used to create buttons. You can create them directly or through the Factory.
  34324. *
  34325. * @example
  34326. * // Create and render a button to the body element
  34327. * tinymce.ui.Factory.create({
  34328. * type: 'button',
  34329. * text: 'My button'
  34330. * }).renderTo(document.body);
  34331. *
  34332. * @-x-less Button.less
  34333. * @class tinymce.ui.Button
  34334. * @extends tinymce.ui.Widget
  34335. */
  34336. define("tinymce/ui/Button", [
  34337. "tinymce/ui/Widget"
  34338. ], function(Widget) {
  34339. "use strict";
  34340. return Widget.extend({
  34341. Defaults: {
  34342. classes: "widget btn",
  34343. role: "button"
  34344. },
  34345. /**
  34346. * Constructs a new button instance with the specified settings.
  34347. *
  34348. * @constructor
  34349. * @param {Object} settings Name/value object with settings.
  34350. * @setting {String} size Size of the button small|medium|large.
  34351. * @setting {String} image Image to use for icon.
  34352. * @setting {String} icon Icon to use for button.
  34353. */
  34354. init: function(settings) {
  34355. var self = this, size;
  34356. self._super(settings);
  34357. settings = self.settings;
  34358. size = self.settings.size;
  34359. self.on('click mousedown', function(e) {
  34360. e.preventDefault();
  34361. });
  34362. self.on('touchstart', function(e) {
  34363. self.fire('click', e);
  34364. e.preventDefault();
  34365. });
  34366. if (settings.subtype) {
  34367. self.classes.add(settings.subtype);
  34368. }
  34369. if (size) {
  34370. self.classes.add('btn-' + size);
  34371. }
  34372. if (settings.icon) {
  34373. self.icon(settings.icon);
  34374. }
  34375. },
  34376. /**
  34377. * Sets/gets the current button icon.
  34378. *
  34379. * @method icon
  34380. * @param {String} [icon] New icon identifier.
  34381. * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance.
  34382. */
  34383. icon: function(icon) {
  34384. if (!arguments.length) {
  34385. return this.state.get('icon');
  34386. }
  34387. this.state.set('icon', icon);
  34388. return this;
  34389. },
  34390. /**
  34391. * Repaints the button for example after it's been resizes by a layout engine.
  34392. *
  34393. * @method repaint
  34394. */
  34395. repaint: function() {
  34396. var btnElm = this.getEl().firstChild,
  34397. btnStyle;
  34398. if (btnElm) {
  34399. btnStyle = btnElm.style;
  34400. btnStyle.width = btnStyle.height = "100%";
  34401. }
  34402. this._super();
  34403. },
  34404. /**
  34405. * Renders the control as a HTML string.
  34406. *
  34407. * @method renderHtml
  34408. * @return {String} HTML representing the control.
  34409. */
  34410. renderHtml: function() {
  34411. var self = this, id = self._id, prefix = self.classPrefix;
  34412. var icon = self.state.get('icon'), image, text = self.state.get('text'), textHtml = '';
  34413. image = self.settings.image;
  34414. if (image) {
  34415. icon = 'none';
  34416. // Support for [high dpi, low dpi] image sources
  34417. if (typeof image != "string") {
  34418. image = window.getSelection ? image[0] : image[1];
  34419. }
  34420. image = ' style="background-image: url(\'' + image + '\')"';
  34421. } else {
  34422. image = '';
  34423. }
  34424. if (text) {
  34425. self.classes.add('btn-has-text');
  34426. textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>';
  34427. }
  34428. icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
  34429. return (
  34430. '<div id="' + id + '" class="' + self.classes + '" tabindex="-1" aria-labelledby="' + id + '">' +
  34431. '<button role="presentation" type="button" tabindex="-1">' +
  34432. (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  34433. textHtml +
  34434. '</button>' +
  34435. '</div>'
  34436. );
  34437. },
  34438. bindStates: function() {
  34439. var self = this, $ = self.$, textCls = self.classPrefix + 'txt';
  34440. function setButtonText(text) {
  34441. var $span = $('span.' + textCls, self.getEl());
  34442. if (text) {
  34443. if (!$span[0]) {
  34444. $('button:first', self.getEl()).append('<span class="' + textCls + '"></span>');
  34445. $span = $('span.' + textCls, self.getEl());
  34446. }
  34447. $span.html(self.encode(text));
  34448. } else {
  34449. $span.remove();
  34450. }
  34451. self.classes.toggle('btn-has-text', !!text);
  34452. }
  34453. self.state.on('change:text', function(e) {
  34454. setButtonText(e.value);
  34455. });
  34456. self.state.on('change:icon', function(e) {
  34457. var icon = e.value, prefix = self.classPrefix;
  34458. self.settings.icon = icon;
  34459. icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  34460. var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];
  34461. if (icon) {
  34462. if (!iconElm || iconElm != btnElm.firstChild) {
  34463. iconElm = document.createElement('i');
  34464. btnElm.insertBefore(iconElm, btnElm.firstChild);
  34465. }
  34466. iconElm.className = icon;
  34467. } else if (iconElm) {
  34468. btnElm.removeChild(iconElm);
  34469. }
  34470. setButtonText(self.state.get('text'));
  34471. });
  34472. return self._super();
  34473. }
  34474. });
  34475. });
  34476. // Included from: js/tinymce/classes/ui/ButtonGroup.js
  34477. /**
  34478. * ButtonGroup.js
  34479. *
  34480. * Released under LGPL License.
  34481. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  34482. *
  34483. * License: http://www.tinymce.com/license
  34484. * Contributing: http://www.tinymce.com/contributing
  34485. */
  34486. /**
  34487. * This control enables you to put multiple buttons into a group. This is
  34488. * useful when you want to combine similar toolbar buttons into a group.
  34489. *
  34490. * @example
  34491. * // Create and render a buttongroup with two buttons to the body element
  34492. * tinymce.ui.Factory.create({
  34493. * type: 'buttongroup',
  34494. * items: [
  34495. * {text: 'Button A'},
  34496. * {text: 'Button B'}
  34497. * ]
  34498. * }).renderTo(document.body);
  34499. *
  34500. * @-x-less ButtonGroup.less
  34501. * @class tinymce.ui.ButtonGroup
  34502. * @extends tinymce.ui.Container
  34503. */
  34504. define("tinymce/ui/ButtonGroup", [
  34505. "tinymce/ui/Container"
  34506. ], function(Container) {
  34507. "use strict";
  34508. return Container.extend({
  34509. Defaults: {
  34510. defaultType: 'button',
  34511. role: 'group'
  34512. },
  34513. /**
  34514. * Renders the control as a HTML string.
  34515. *
  34516. * @method renderHtml
  34517. * @return {String} HTML representing the control.
  34518. */
  34519. renderHtml: function() {
  34520. var self = this, layout = self._layout;
  34521. self.classes.add('btn-group');
  34522. self.preRender();
  34523. layout.preRender(self);
  34524. return (
  34525. '<div id="' + self._id + '" class="' + self.classes + '">' +
  34526. '<div id="' + self._id + '-body">' +
  34527. (self.settings.html || '') + layout.renderHtml(self) +
  34528. '</div>' +
  34529. '</div>'
  34530. );
  34531. }
  34532. });
  34533. });
  34534. // Included from: js/tinymce/classes/ui/Checkbox.js
  34535. /**
  34536. * Checkbox.js
  34537. *
  34538. * Released under LGPL License.
  34539. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  34540. *
  34541. * License: http://www.tinymce.com/license
  34542. * Contributing: http://www.tinymce.com/contributing
  34543. */
  34544. /**
  34545. * This control creates a custom checkbox.
  34546. *
  34547. * @example
  34548. * // Create and render a checkbox to the body element
  34549. * tinymce.ui.Factory.create({
  34550. * type: 'checkbox',
  34551. * checked: true,
  34552. * text: 'My checkbox'
  34553. * }).renderTo(document.body);
  34554. *
  34555. * @-x-less Checkbox.less
  34556. * @class tinymce.ui.Checkbox
  34557. * @extends tinymce.ui.Widget
  34558. */
  34559. define("tinymce/ui/Checkbox", [
  34560. "tinymce/ui/Widget"
  34561. ], function(Widget) {
  34562. "use strict";
  34563. return Widget.extend({
  34564. Defaults: {
  34565. classes: "checkbox",
  34566. role: "checkbox",
  34567. checked: false
  34568. },
  34569. /**
  34570. * Constructs a new Checkbox instance with the specified settings.
  34571. *
  34572. * @constructor
  34573. * @param {Object} settings Name/value object with settings.
  34574. * @setting {Boolean} checked True if the checkbox should be checked by default.
  34575. */
  34576. init: function(settings) {
  34577. var self = this;
  34578. self._super(settings);
  34579. self.on('click mousedown', function(e) {
  34580. e.preventDefault();
  34581. });
  34582. self.on('click', function(e) {
  34583. e.preventDefault();
  34584. if (!self.disabled()) {
  34585. self.checked(!self.checked());
  34586. }
  34587. });
  34588. self.checked(self.settings.checked);
  34589. },
  34590. /**
  34591. * Getter/setter function for the checked state.
  34592. *
  34593. * @method checked
  34594. * @param {Boolean} [state] State to be set.
  34595. * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
  34596. */
  34597. checked: function(state) {
  34598. if (!arguments.length) {
  34599. return this.state.get('checked');
  34600. }
  34601. this.state.set('checked', state);
  34602. return this;
  34603. },
  34604. /**
  34605. * Getter/setter function for the value state.
  34606. *
  34607. * @method value
  34608. * @param {Boolean} [state] State to be set.
  34609. * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
  34610. */
  34611. value: function(state) {
  34612. if (!arguments.length) {
  34613. return this.checked();
  34614. }
  34615. return this.checked(state);
  34616. },
  34617. /**
  34618. * Renders the control as a HTML string.
  34619. *
  34620. * @method renderHtml
  34621. * @return {String} HTML representing the control.
  34622. */
  34623. renderHtml: function() {
  34624. var self = this, id = self._id, prefix = self.classPrefix;
  34625. return (
  34626. '<div id="' + id + '" class="' + self.classes + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' +
  34627. '<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' +
  34628. '<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self.state.get('text')) + '</span>' +
  34629. '</div>'
  34630. );
  34631. },
  34632. bindStates: function() {
  34633. var self = this;
  34634. function checked(state) {
  34635. self.classes.toggle("checked", state);
  34636. self.aria('checked', state);
  34637. }
  34638. self.state.on('change:text', function(e) {
  34639. self.getEl('al').firstChild.data = self.translate(e.value);
  34640. });
  34641. self.state.on('change:checked change:value', function(e) {
  34642. self.fire('change');
  34643. checked(e.value);
  34644. });
  34645. self.state.on('change:icon', function(e) {
  34646. var icon = e.value, prefix = self.classPrefix;
  34647. if (typeof icon == 'undefined') {
  34648. return self.settings.icon;
  34649. }
  34650. self.settings.icon = icon;
  34651. icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  34652. var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];
  34653. if (icon) {
  34654. if (!iconElm || iconElm != btnElm.firstChild) {
  34655. iconElm = document.createElement('i');
  34656. btnElm.insertBefore(iconElm, btnElm.firstChild);
  34657. }
  34658. iconElm.className = icon;
  34659. } else if (iconElm) {
  34660. btnElm.removeChild(iconElm);
  34661. }
  34662. });
  34663. if (self.state.get('checked')) {
  34664. checked(true);
  34665. }
  34666. return self._super();
  34667. }
  34668. });
  34669. });
  34670. // Included from: js/tinymce/classes/ui/ComboBox.js
  34671. /**
  34672. * ComboBox.js
  34673. *
  34674. * Released under LGPL License.
  34675. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  34676. *
  34677. * License: http://www.tinymce.com/license
  34678. * Contributing: http://www.tinymce.com/contributing
  34679. */
  34680. /**
  34681. * This class creates a combobox control. Select box that you select a value from or
  34682. * type a value into.
  34683. *
  34684. * @-x-less ComboBox.less
  34685. * @class tinymce.ui.ComboBox
  34686. * @extends tinymce.ui.Widget
  34687. */
  34688. define("tinymce/ui/ComboBox", [
  34689. "tinymce/ui/Widget",
  34690. "tinymce/ui/Factory",
  34691. "tinymce/ui/DomUtils",
  34692. "tinymce/dom/DomQuery"
  34693. ], function(Widget, Factory, DomUtils, $) {
  34694. "use strict";
  34695. return Widget.extend({
  34696. /**
  34697. * Constructs a new control instance with the specified settings.
  34698. *
  34699. * @constructor
  34700. * @param {Object} settings Name/value object with settings.
  34701. * @setting {String} placeholder Placeholder text to display.
  34702. */
  34703. init: function(settings) {
  34704. var self = this;
  34705. self._super(settings);
  34706. settings = self.settings;
  34707. self.classes.add('combobox');
  34708. self.subinput = true;
  34709. self.ariaTarget = 'inp'; // TODO: Figure out a better way
  34710. settings.menu = settings.menu || settings.values;
  34711. if (settings.menu) {
  34712. settings.icon = 'caret';
  34713. }
  34714. self.on('click', function(e) {
  34715. var elm = e.target, root = self.getEl();
  34716. if (!$.contains(root, elm) && elm != root) {
  34717. return;
  34718. }
  34719. while (elm && elm != root) {
  34720. if (elm.id && elm.id.indexOf('-open') != -1) {
  34721. self.fire('action');
  34722. if (settings.menu) {
  34723. self.showMenu();
  34724. if (e.aria) {
  34725. self.menu.items()[0].focus();
  34726. }
  34727. }
  34728. }
  34729. elm = elm.parentNode;
  34730. }
  34731. });
  34732. // TODO: Rework this
  34733. self.on('keydown', function(e) {
  34734. if (e.target.nodeName == "INPUT" && e.keyCode == 13) {
  34735. self.parents().reverse().each(function(ctrl) {
  34736. var stateValue = self.state.get('value'), inputValue = self.getEl('inp').value;
  34737. e.preventDefault();
  34738. self.state.set('value', inputValue);
  34739. if (stateValue != inputValue) {
  34740. self.fire('change');
  34741. }
  34742. if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
  34743. ctrl.fire('submit', {data: ctrl.toJSON()});
  34744. return false;
  34745. }
  34746. });
  34747. }
  34748. });
  34749. self.on('keyup', function(e) {
  34750. if (e.target.nodeName == "INPUT") {
  34751. self.state.set('value', e.target.value);
  34752. }
  34753. });
  34754. },
  34755. showMenu: function() {
  34756. var self = this, settings = self.settings, menu;
  34757. if (!self.menu) {
  34758. menu = settings.menu || [];
  34759. // Is menu array then auto constuct menu control
  34760. if (menu.length) {
  34761. menu = {
  34762. type: 'menu',
  34763. items: menu
  34764. };
  34765. } else {
  34766. menu.type = menu.type || 'menu';
  34767. }
  34768. self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
  34769. self.fire('createmenu');
  34770. self.menu.reflow();
  34771. self.menu.on('cancel', function(e) {
  34772. if (e.control === self.menu) {
  34773. self.focus();
  34774. }
  34775. });
  34776. self.menu.on('show hide', function(e) {
  34777. e.control.items().each(function(ctrl) {
  34778. ctrl.active(ctrl.value() == self.value());
  34779. });
  34780. }).fire('show');
  34781. self.menu.on('select', function(e) {
  34782. self.value(e.control.value());
  34783. });
  34784. self.on('focusin', function(e) {
  34785. if (e.target.tagName.toUpperCase() == 'INPUT') {
  34786. self.menu.hide();
  34787. }
  34788. });
  34789. self.aria('expanded', true);
  34790. }
  34791. self.menu.show();
  34792. self.menu.layoutRect({w: self.layoutRect().w});
  34793. self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
  34794. },
  34795. /**
  34796. * Focuses the input area of the control.
  34797. *
  34798. * @method focus
  34799. */
  34800. focus: function() {
  34801. this.getEl('inp').focus();
  34802. },
  34803. /**
  34804. * Repaints the control after a layout operation.
  34805. *
  34806. * @method repaint
  34807. */
  34808. repaint: function() {
  34809. var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect();
  34810. var width, lineHeight;
  34811. if (openElm) {
  34812. width = rect.w - DomUtils.getSize(openElm).width - 10;
  34813. } else {
  34814. width = rect.w - 10;
  34815. }
  34816. // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
  34817. var doc = document;
  34818. if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
  34819. lineHeight = (self.layoutRect().h - 2) + 'px';
  34820. }
  34821. $(elm.firstChild).css({
  34822. width: width,
  34823. lineHeight: lineHeight
  34824. });
  34825. self._super();
  34826. return self;
  34827. },
  34828. /**
  34829. * Post render method. Called after the control has been rendered to the target.
  34830. *
  34831. * @method postRender
  34832. * @return {tinymce.ui.ComboBox} Current combobox instance.
  34833. */
  34834. postRender: function() {
  34835. var self = this;
  34836. $(this.getEl('inp')).on('change', function(e) {
  34837. self.state.set('value', e.target.value);
  34838. self.fire('change', e);
  34839. });
  34840. return self._super();
  34841. },
  34842. /**
  34843. * Renders the control as a HTML string.
  34844. *
  34845. * @method renderHtml
  34846. * @return {String} HTML representing the control.
  34847. */
  34848. renderHtml: function() {
  34849. var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
  34850. var value = self.state.get('value') || '';
  34851. var icon, text, openBtnHtml = '', extraAttrs = '';
  34852. if ("spellcheck" in settings) {
  34853. extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
  34854. }
  34855. if (settings.maxLength) {
  34856. extraAttrs += ' maxlength="' + settings.maxLength + '"';
  34857. }
  34858. if (settings.size) {
  34859. extraAttrs += ' size="' + settings.size + '"';
  34860. }
  34861. if (settings.subtype) {
  34862. extraAttrs += ' type="' + settings.subtype + '"';
  34863. }
  34864. if (self.disabled()) {
  34865. extraAttrs += ' disabled="disabled"';
  34866. }
  34867. icon = settings.icon;
  34868. if (icon && icon != 'caret') {
  34869. icon = prefix + 'ico ' + prefix + 'i-' + settings.icon;
  34870. }
  34871. text = self.state.get('text');
  34872. if (icon || text) {
  34873. openBtnHtml = (
  34874. '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
  34875. '<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
  34876. (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
  34877. (text ? (icon ? ' ' : '') + text : '') +
  34878. '</button>' +
  34879. '</div>'
  34880. );
  34881. self.classes.add('has-open');
  34882. }
  34883. return (
  34884. '<div id="' + id + '" class="' + self.classes + '">' +
  34885. '<input id="' + id + '-inp" class="' + prefix + 'textbox" value="' +
  34886. self.encode(value, false) + '" hidefocus="1"' + extraAttrs + ' placeholder="' +
  34887. self.encode(settings.placeholder) + '" />' +
  34888. openBtnHtml +
  34889. '</div>'
  34890. );
  34891. },
  34892. value: function(value) {
  34893. if (arguments.length) {
  34894. this.state.set('value', value);
  34895. return this;
  34896. }
  34897. // Make sure the real state is in sync
  34898. if (this.state.get('rendered')) {
  34899. this.state.set('value', this.getEl('inp').value);
  34900. }
  34901. return this.state.get('value');
  34902. },
  34903. bindStates: function() {
  34904. var self = this;
  34905. self.state.on('change:value', function(e) {
  34906. if (self.getEl('inp').value != e.value) {
  34907. self.getEl('inp').value = e.value;
  34908. }
  34909. });
  34910. self.state.on('change:disabled', function(e) {
  34911. self.getEl('inp').disabled = e.value;
  34912. });
  34913. return self._super();
  34914. },
  34915. remove: function() {
  34916. $(this.getEl('inp')).off();
  34917. this._super();
  34918. }
  34919. });
  34920. });
  34921. // Included from: js/tinymce/classes/ui/ColorBox.js
  34922. /**
  34923. * ColorBox.js
  34924. *
  34925. * Released under LGPL License.
  34926. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  34927. *
  34928. * License: http://www.tinymce.com/license
  34929. * Contributing: http://www.tinymce.com/contributing
  34930. */
  34931. /**
  34932. * This widget lets you enter colors and browse for colors by pressing the color button. It also displays
  34933. * a preview of the current color.
  34934. *
  34935. * @-x-less ColorBox.less
  34936. * @class tinymce.ui.ColorBox
  34937. * @extends tinymce.ui.ComboBox
  34938. */
  34939. define("tinymce/ui/ColorBox", [
  34940. "tinymce/ui/ComboBox"
  34941. ], function(ComboBox) {
  34942. "use strict";
  34943. return ComboBox.extend({
  34944. /**
  34945. * Constructs a new control instance with the specified settings.
  34946. *
  34947. * @constructor
  34948. * @param {Object} settings Name/value object with settings.
  34949. */
  34950. init: function(settings) {
  34951. var self = this;
  34952. settings.spellcheck = false;
  34953. if (settings.onaction) {
  34954. settings.icon = 'none';
  34955. }
  34956. self._super(settings);
  34957. self.classes.add('colorbox');
  34958. self.on('change keyup postrender', function() {
  34959. self.repaintColor(self.value());
  34960. });
  34961. },
  34962. repaintColor: function(value) {
  34963. var elm = this.getEl().getElementsByTagName('i')[0];
  34964. if (elm) {
  34965. try {
  34966. elm.style.background = value;
  34967. } catch (ex) {
  34968. // Ignore
  34969. }
  34970. }
  34971. },
  34972. bindStates: function() {
  34973. var self = this;
  34974. self.state.on('change:value', function(e) {
  34975. if (self.state.get('rendered')) {
  34976. self.repaintColor(e.value);
  34977. }
  34978. });
  34979. return self._super();
  34980. }
  34981. });
  34982. });
  34983. // Included from: js/tinymce/classes/ui/PanelButton.js
  34984. /**
  34985. * PanelButton.js
  34986. *
  34987. * Released under LGPL License.
  34988. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  34989. *
  34990. * License: http://www.tinymce.com/license
  34991. * Contributing: http://www.tinymce.com/contributing
  34992. */
  34993. /**
  34994. * Creates a new panel button.
  34995. *
  34996. * @class tinymce.ui.PanelButton
  34997. * @extends tinymce.ui.Button
  34998. */
  34999. define("tinymce/ui/PanelButton", [
  35000. "tinymce/ui/Button",
  35001. "tinymce/ui/FloatPanel"
  35002. ], function(Button, FloatPanel) {
  35003. "use strict";
  35004. return Button.extend({
  35005. /**
  35006. * Shows the panel for the button.
  35007. *
  35008. * @method showPanel
  35009. */
  35010. showPanel: function() {
  35011. var self = this, settings = self.settings;
  35012. self.active(true);
  35013. if (!self.panel) {
  35014. var panelSettings = settings.panel;
  35015. // Wrap panel in grid layout if type if specified
  35016. // This makes it possible to add forms or other containers directly in the panel option
  35017. if (panelSettings.type) {
  35018. panelSettings = {
  35019. layout: 'grid',
  35020. items: panelSettings
  35021. };
  35022. }
  35023. panelSettings.role = panelSettings.role || 'dialog';
  35024. panelSettings.popover = true;
  35025. panelSettings.autohide = true;
  35026. panelSettings.ariaRoot = true;
  35027. self.panel = new FloatPanel(panelSettings).on('hide', function() {
  35028. self.active(false);
  35029. }).on('cancel', function(e) {
  35030. e.stopPropagation();
  35031. self.focus();
  35032. self.hidePanel();
  35033. }).parent(self).renderTo(self.getContainerElm());
  35034. self.panel.fire('show');
  35035. self.panel.reflow();
  35036. } else {
  35037. self.panel.show();
  35038. }
  35039. self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc']));
  35040. },
  35041. /**
  35042. * Hides the panel for the button.
  35043. *
  35044. * @method hidePanel
  35045. */
  35046. hidePanel: function() {
  35047. var self = this;
  35048. if (self.panel) {
  35049. self.panel.hide();
  35050. }
  35051. },
  35052. /**
  35053. * Called after the control has been rendered.
  35054. *
  35055. * @method postRender
  35056. */
  35057. postRender: function() {
  35058. var self = this;
  35059. self.aria('haspopup', true);
  35060. self.on('click', function(e) {
  35061. if (e.control === self) {
  35062. if (self.panel && self.panel.visible()) {
  35063. self.hidePanel();
  35064. } else {
  35065. self.showPanel();
  35066. self.panel.focus(!!e.aria);
  35067. }
  35068. }
  35069. });
  35070. return self._super();
  35071. },
  35072. remove: function() {
  35073. if (this.panel) {
  35074. this.panel.remove();
  35075. this.panel = null;
  35076. }
  35077. return this._super();
  35078. }
  35079. });
  35080. });
  35081. // Included from: js/tinymce/classes/ui/ColorButton.js
  35082. /**
  35083. * ColorButton.js
  35084. *
  35085. * Released under LGPL License.
  35086. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  35087. *
  35088. * License: http://www.tinymce.com/license
  35089. * Contributing: http://www.tinymce.com/contributing
  35090. */
  35091. /**
  35092. * This class creates a color button control. This is a split button in which the main
  35093. * button has a visual representation of the currently selected color. When clicked
  35094. * the caret button displays a color picker, allowing the user to select a new color.
  35095. *
  35096. * @-x-less ColorButton.less
  35097. * @class tinymce.ui.ColorButton
  35098. * @extends tinymce.ui.PanelButton
  35099. */
  35100. define("tinymce/ui/ColorButton", [
  35101. "tinymce/ui/PanelButton",
  35102. "tinymce/dom/DOMUtils"
  35103. ], function(PanelButton, DomUtils) {
  35104. "use strict";
  35105. var DOM = DomUtils.DOM;
  35106. return PanelButton.extend({
  35107. /**
  35108. * Constructs a new ColorButton instance with the specified settings.
  35109. *
  35110. * @constructor
  35111. * @param {Object} settings Name/value object with settings.
  35112. */
  35113. init: function(settings) {
  35114. this._super(settings);
  35115. this.classes.add('colorbutton');
  35116. },
  35117. /**
  35118. * Getter/setter for the current color.
  35119. *
  35120. * @method color
  35121. * @param {String} [color] Color to set.
  35122. * @return {String|tinymce.ui.ColorButton} Current color or current instance.
  35123. */
  35124. color: function(color) {
  35125. if (color) {
  35126. this._color = color;
  35127. this.getEl('preview').style.backgroundColor = color;
  35128. return this;
  35129. }
  35130. return this._color;
  35131. },
  35132. /**
  35133. * Resets the current color.
  35134. *
  35135. * @method resetColor
  35136. * @return {tinymce.ui.ColorButton} Current instance.
  35137. */
  35138. resetColor: function() {
  35139. this._color = null;
  35140. this.getEl('preview').style.backgroundColor = null;
  35141. return this;
  35142. },
  35143. /**
  35144. * Renders the control as a HTML string.
  35145. *
  35146. * @method renderHtml
  35147. * @return {String} HTML representing the control.
  35148. */
  35149. renderHtml: function() {
  35150. var self = this, id = self._id, prefix = self.classPrefix, text = self.state.get('text');
  35151. var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  35152. var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '',
  35153. textHtml = '';
  35154. if (text) {
  35155. self.classes.add('btn-has-text');
  35156. textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>';
  35157. }
  35158. return (
  35159. '<div id="' + id + '" class="' + self.classes + '" role="button" tabindex="-1" aria-haspopup="true">' +
  35160. '<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
  35161. (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  35162. '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
  35163. textHtml +
  35164. '</button>' +
  35165. '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
  35166. ' <i class="' + prefix + 'caret"></i>' +
  35167. '</button>' +
  35168. '</div>'
  35169. );
  35170. },
  35171. /**
  35172. * Called after the control has been rendered.
  35173. *
  35174. * @method postRender
  35175. */
  35176. postRender: function() {
  35177. var self = this, onClickHandler = self.settings.onclick;
  35178. self.on('click', function(e) {
  35179. if (e.aria && e.aria.key == 'down') {
  35180. return;
  35181. }
  35182. if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
  35183. e.stopImmediatePropagation();
  35184. onClickHandler.call(self, e);
  35185. }
  35186. });
  35187. delete self.settings.onclick;
  35188. return self._super();
  35189. }
  35190. });
  35191. });
  35192. // Included from: js/tinymce/classes/util/Color.js
  35193. /**
  35194. * Color.js
  35195. *
  35196. * Released under LGPL License.
  35197. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  35198. *
  35199. * License: http://www.tinymce.com/license
  35200. * Contributing: http://www.tinymce.com/contributing
  35201. */
  35202. /**
  35203. * This class lets you parse/serialize colors and convert rgb/hsb.
  35204. *
  35205. * @class tinymce.util.Color
  35206. * @example
  35207. * var white = new tinymce.util.Color({r: 255, g: 255, b: 255});
  35208. * var red = new tinymce.util.Color('#FF0000');
  35209. *
  35210. * console.log(white.toHex(), red.toHsv());
  35211. */
  35212. define("tinymce/util/Color", [], function() {
  35213. var min = Math.min, max = Math.max, round = Math.round;
  35214. /**
  35215. * Constructs a new color instance.
  35216. *
  35217. * @constructor
  35218. * @method Color
  35219. * @param {String} value Optional initial value to parse.
  35220. */
  35221. function Color(value) {
  35222. var self = this, r = 0, g = 0, b = 0;
  35223. function rgb2hsv(r, g, b) {
  35224. var h, s, v, d, minRGB, maxRGB;
  35225. h = 0;
  35226. s = 0;
  35227. v = 0;
  35228. r = r / 255;
  35229. g = g / 255;
  35230. b = b / 255;
  35231. minRGB = min(r, min(g, b));
  35232. maxRGB = max(r, max(g, b));
  35233. if (minRGB == maxRGB) {
  35234. v = minRGB;
  35235. return {
  35236. h: 0,
  35237. s: 0,
  35238. v: v * 100
  35239. };
  35240. }
  35241. /*eslint no-nested-ternary:0 */
  35242. d = (r == minRGB) ? g - b : ((b == minRGB) ? r - g : b - r);
  35243. h = (r == minRGB) ? 3 : ((b == minRGB) ? 1 : 5);
  35244. h = 60 * (h - d / (maxRGB - minRGB));
  35245. s = (maxRGB - minRGB) / maxRGB;
  35246. v = maxRGB;
  35247. return {
  35248. h: round(h),
  35249. s: round(s * 100),
  35250. v: round(v * 100)
  35251. };
  35252. }
  35253. function hsvToRgb(hue, saturation, brightness) {
  35254. var side, chroma, x, match;
  35255. hue = (parseInt(hue, 10) || 0) % 360;
  35256. saturation = parseInt(saturation, 10) / 100;
  35257. brightness = parseInt(brightness, 10) / 100;
  35258. saturation = max(0, min(saturation, 1));
  35259. brightness = max(0, min(brightness, 1));
  35260. if (saturation === 0) {
  35261. r = g = b = round(255 * brightness);
  35262. return;
  35263. }
  35264. side = hue / 60;
  35265. chroma = brightness * saturation;
  35266. x = chroma * (1 - Math.abs(side % 2 - 1));
  35267. match = brightness - chroma;
  35268. switch (Math.floor(side)) {
  35269. case 0:
  35270. r = chroma;
  35271. g = x;
  35272. b = 0;
  35273. break;
  35274. case 1:
  35275. r = x;
  35276. g = chroma;
  35277. b = 0;
  35278. break;
  35279. case 2:
  35280. r = 0;
  35281. g = chroma;
  35282. b = x;
  35283. break;
  35284. case 3:
  35285. r = 0;
  35286. g = x;
  35287. b = chroma;
  35288. break;
  35289. case 4:
  35290. r = x;
  35291. g = 0;
  35292. b = chroma;
  35293. break;
  35294. case 5:
  35295. r = chroma;
  35296. g = 0;
  35297. b = x;
  35298. break;
  35299. default:
  35300. r = g = b = 0;
  35301. }
  35302. r = round(255 * (r + match));
  35303. g = round(255 * (g + match));
  35304. b = round(255 * (b + match));
  35305. }
  35306. /**
  35307. * Returns the hex string of the current color. For example: #ff00ff
  35308. *
  35309. * @method toHex
  35310. * @return {String} Hex string of current color.
  35311. */
  35312. function toHex() {
  35313. function hex(val) {
  35314. val = parseInt(val, 10).toString(16);
  35315. return val.length > 1 ? val : '0' + val;
  35316. }
  35317. return '#' + hex(r) + hex(g) + hex(b);
  35318. }
  35319. /**
  35320. * Returns the r, g, b values of the color. Each channel has a range from 0-255.
  35321. *
  35322. * @method toRgb
  35323. * @return {Object} Object with r, g, b fields.
  35324. */
  35325. function toRgb() {
  35326. return {
  35327. r: r,
  35328. g: g,
  35329. b: b
  35330. };
  35331. }
  35332. /**
  35333. * Returns the h, s, v values of the color. Ranges: h=0-360, s=0-100, v=0-100.
  35334. *
  35335. * @method toHsv
  35336. * @return {Object} Object with h, s, v fields.
  35337. */
  35338. function toHsv() {
  35339. return rgb2hsv(r, g, b);
  35340. }
  35341. /**
  35342. * Parses the specified value and populates the color instance.
  35343. *
  35344. * Supported format examples:
  35345. * * rbg(255,0,0)
  35346. * * #ff0000
  35347. * * #fff
  35348. * * {r: 255, g: 0, b: 0}
  35349. * * {h: 360, s: 100, v: 100}
  35350. *
  35351. * @method parse
  35352. * @param {Object/String} value Color value to parse.
  35353. * @return {tinymce.util.Color} Current color instance.
  35354. */
  35355. function parse(value) {
  35356. var matches;
  35357. if (typeof value == 'object') {
  35358. if ("r" in value) {
  35359. r = value.r;
  35360. g = value.g;
  35361. b = value.b;
  35362. } else if ("v" in value) {
  35363. hsvToRgb(value.h, value.s, value.v);
  35364. }
  35365. } else {
  35366. if ((matches = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)[^\)]*\)/gi.exec(value))) {
  35367. r = parseInt(matches[1], 10);
  35368. g = parseInt(matches[2], 10);
  35369. b = parseInt(matches[3], 10);
  35370. } else if ((matches = /#([0-F]{2})([0-F]{2})([0-F]{2})/gi.exec(value))) {
  35371. r = parseInt(matches[1], 16);
  35372. g = parseInt(matches[2], 16);
  35373. b = parseInt(matches[3], 16);
  35374. } else if ((matches = /#([0-F])([0-F])([0-F])/gi.exec(value))) {
  35375. r = parseInt(matches[1] + matches[1], 16);
  35376. g = parseInt(matches[2] + matches[2], 16);
  35377. b = parseInt(matches[3] + matches[3], 16);
  35378. }
  35379. }
  35380. r = r < 0 ? 0 : (r > 255 ? 255 : r);
  35381. g = g < 0 ? 0 : (g > 255 ? 255 : g);
  35382. b = b < 0 ? 0 : (b > 255 ? 255 : b);
  35383. return self;
  35384. }
  35385. if (value) {
  35386. parse(value);
  35387. }
  35388. self.toRgb = toRgb;
  35389. self.toHsv = toHsv;
  35390. self.toHex = toHex;
  35391. self.parse = parse;
  35392. }
  35393. return Color;
  35394. });
  35395. // Included from: js/tinymce/classes/ui/ColorPicker.js
  35396. /**
  35397. * ColorPicker.js
  35398. *
  35399. * Released under LGPL License.
  35400. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  35401. *
  35402. * License: http://www.tinymce.com/license
  35403. * Contributing: http://www.tinymce.com/contributing
  35404. */
  35405. /**
  35406. * Color picker widget lets you select colors.
  35407. *
  35408. * @-x-less ColorPicker.less
  35409. * @class tinymce.ui.ColorPicker
  35410. * @extends tinymce.ui.Widget
  35411. */
  35412. define("tinymce/ui/ColorPicker", [
  35413. "tinymce/ui/Widget",
  35414. "tinymce/ui/DragHelper",
  35415. "tinymce/ui/DomUtils",
  35416. "tinymce/util/Color"
  35417. ], function(Widget, DragHelper, DomUtils, Color) {
  35418. "use strict";
  35419. return Widget.extend({
  35420. Defaults: {
  35421. classes: "widget colorpicker"
  35422. },
  35423. /**
  35424. * Constructs a new colorpicker instance with the specified settings.
  35425. *
  35426. * @constructor
  35427. * @param {Object} settings Name/value object with settings.
  35428. * @setting {String} color Initial color value.
  35429. */
  35430. init: function(settings) {
  35431. this._super(settings);
  35432. },
  35433. postRender: function() {
  35434. var self = this, color = self.color(), hsv, hueRootElm, huePointElm, svRootElm, svPointElm;
  35435. hueRootElm = self.getEl('h');
  35436. huePointElm = self.getEl('hp');
  35437. svRootElm = self.getEl('sv');
  35438. svPointElm = self.getEl('svp');
  35439. function getPos(elm, event) {
  35440. var pos = DomUtils.getPos(elm), x, y;
  35441. x = event.pageX - pos.x;
  35442. y = event.pageY - pos.y;
  35443. x = Math.max(0, Math.min(x / elm.clientWidth, 1));
  35444. y = Math.max(0, Math.min(y / elm.clientHeight, 1));
  35445. return {
  35446. x: x,
  35447. y: y
  35448. };
  35449. }
  35450. function updateColor(hsv, hueUpdate) {
  35451. var hue = (360 - hsv.h) / 360;
  35452. DomUtils.css(huePointElm, {
  35453. top: (hue * 100) + '%'
  35454. });
  35455. if (!hueUpdate) {
  35456. DomUtils.css(svPointElm, {
  35457. left: hsv.s + '%',
  35458. top: (100 - hsv.v) + '%'
  35459. });
  35460. }
  35461. svRootElm.style.background = new Color({s: 100, v: 100, h: hsv.h}).toHex();
  35462. self.color().parse({s: hsv.s, v: hsv.v, h: hsv.h});
  35463. }
  35464. function updateSaturationAndValue(e) {
  35465. var pos;
  35466. pos = getPos(svRootElm, e);
  35467. hsv.s = pos.x * 100;
  35468. hsv.v = (1 - pos.y) * 100;
  35469. updateColor(hsv);
  35470. self.fire('change');
  35471. }
  35472. function updateHue(e) {
  35473. var pos;
  35474. pos = getPos(hueRootElm, e);
  35475. hsv = color.toHsv();
  35476. hsv.h = (1 - pos.y) * 360;
  35477. updateColor(hsv, true);
  35478. self.fire('change');
  35479. }
  35480. self._repaint = function() {
  35481. hsv = color.toHsv();
  35482. updateColor(hsv);
  35483. };
  35484. self._super();
  35485. self._svdraghelper = new DragHelper(self._id + '-sv', {
  35486. start: updateSaturationAndValue,
  35487. drag: updateSaturationAndValue
  35488. });
  35489. self._hdraghelper = new DragHelper(self._id + '-h', {
  35490. start: updateHue,
  35491. drag: updateHue
  35492. });
  35493. self._repaint();
  35494. },
  35495. rgb: function() {
  35496. return this.color().toRgb();
  35497. },
  35498. value: function(value) {
  35499. var self = this;
  35500. if (arguments.length) {
  35501. self.color().parse(value);
  35502. if (self._rendered) {
  35503. self._repaint();
  35504. }
  35505. } else {
  35506. return self.color().toHex();
  35507. }
  35508. },
  35509. color: function() {
  35510. if (!this._color) {
  35511. this._color = new Color();
  35512. }
  35513. return this._color;
  35514. },
  35515. /**
  35516. * Renders the control as a HTML string.
  35517. *
  35518. * @method renderHtml
  35519. * @return {String} HTML representing the control.
  35520. */
  35521. renderHtml: function() {
  35522. var self = this, id = self._id, prefix = self.classPrefix, hueHtml;
  35523. var stops = '#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000';
  35524. function getOldIeFallbackHtml() {
  35525. var i, l, html = '', gradientPrefix, stopsList;
  35526. gradientPrefix = 'filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=';
  35527. stopsList = stops.split(',');
  35528. for (i = 0, l = stopsList.length - 1; i < l; i++) {
  35529. html += (
  35530. '<div class="' + prefix + 'colorpicker-h-chunk" style="' +
  35531. 'height:' + (100 / l) + '%;' +
  35532. gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ');' +
  35533. '-ms-' + gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ')' +
  35534. '"></div>'
  35535. );
  35536. }
  35537. return html;
  35538. }
  35539. var gradientCssText = (
  35540. 'background: -ms-linear-gradient(top,' + stops + ');' +
  35541. 'background: linear-gradient(to bottom,' + stops + ');'
  35542. );
  35543. hueHtml = (
  35544. '<div id="' + id + '-h" class="' + prefix + 'colorpicker-h" style="' + gradientCssText + '">' +
  35545. getOldIeFallbackHtml() +
  35546. '<div id="' + id + '-hp" class="' + prefix + 'colorpicker-h-marker"></div>' +
  35547. '</div>'
  35548. );
  35549. return (
  35550. '<div id="' + id + '" class="' + self.classes + '">' +
  35551. '<div id="' + id + '-sv" class="' + prefix + 'colorpicker-sv">' +
  35552. '<div class="' + prefix + 'colorpicker-overlay1">' +
  35553. '<div class="' + prefix + 'colorpicker-overlay2">' +
  35554. '<div id="' + id + '-svp" class="' + prefix + 'colorpicker-selector1">' +
  35555. '<div class="' + prefix + 'colorpicker-selector2"></div>' +
  35556. '</div>' +
  35557. '</div>' +
  35558. '</div>' +
  35559. '</div>' +
  35560. hueHtml +
  35561. '</div>'
  35562. );
  35563. }
  35564. });
  35565. });
  35566. // Included from: js/tinymce/classes/ui/Path.js
  35567. /**
  35568. * Path.js
  35569. *
  35570. * Released under LGPL License.
  35571. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  35572. *
  35573. * License: http://www.tinymce.com/license
  35574. * Contributing: http://www.tinymce.com/contributing
  35575. */
  35576. /**
  35577. * Creates a new path control.
  35578. *
  35579. * @-x-less Path.less
  35580. * @class tinymce.ui.Path
  35581. * @extends tinymce.ui.Widget
  35582. */
  35583. define("tinymce/ui/Path", [
  35584. "tinymce/ui/Widget"
  35585. ], function(Widget) {
  35586. "use strict";
  35587. return Widget.extend({
  35588. /**
  35589. * Constructs a instance with the specified settings.
  35590. *
  35591. * @constructor
  35592. * @param {Object} settings Name/value object with settings.
  35593. * @setting {String} delimiter Delimiter to display between row in path.
  35594. */
  35595. init: function(settings) {
  35596. var self = this;
  35597. if (!settings.delimiter) {
  35598. settings.delimiter = '\u00BB';
  35599. }
  35600. self._super(settings);
  35601. self.classes.add('path');
  35602. self.canFocus = true;
  35603. self.on('click', function(e) {
  35604. var index, target = e.target;
  35605. if ((index = target.getAttribute('data-index'))) {
  35606. self.fire('select', {value: self.row()[index], index: index});
  35607. }
  35608. });
  35609. self.row(self.settings.row);
  35610. },
  35611. /**
  35612. * Focuses the current control.
  35613. *
  35614. * @method focus
  35615. * @return {tinymce.ui.Control} Current control instance.
  35616. */
  35617. focus: function() {
  35618. var self = this;
  35619. self.getEl().firstChild.focus();
  35620. return self;
  35621. },
  35622. /**
  35623. * Sets/gets the data to be used for the path.
  35624. *
  35625. * @method row
  35626. * @param {Array} row Array with row name is rendered to path.
  35627. */
  35628. row: function(row) {
  35629. if (!arguments.length) {
  35630. return this.state.get('row');
  35631. }
  35632. this.state.set('row', row);
  35633. return this;
  35634. },
  35635. /**
  35636. * Renders the control as a HTML string.
  35637. *
  35638. * @method renderHtml
  35639. * @return {String} HTML representing the control.
  35640. */
  35641. renderHtml: function() {
  35642. var self = this;
  35643. return (
  35644. '<div id="' + self._id + '" class="' + self.classes + '">' +
  35645. self._getDataPathHtml(self.state.get('row')) +
  35646. '</div>'
  35647. );
  35648. },
  35649. bindStates: function() {
  35650. var self = this;
  35651. self.state.on('change:row', function(e) {
  35652. self.innerHtml(self._getDataPathHtml(e.value));
  35653. });
  35654. return self._super();
  35655. },
  35656. _getDataPathHtml: function(data) {
  35657. var self = this, parts = data || [], i, l, html = '', prefix = self.classPrefix;
  35658. for (i = 0, l = parts.length; i < l; i++) {
  35659. html += (
  35660. (i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
  35661. '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
  35662. i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>'
  35663. );
  35664. }
  35665. if (!html) {
  35666. html = '<div class="' + prefix + 'path-item">\u00a0</div>';
  35667. }
  35668. return html;
  35669. }
  35670. });
  35671. });
  35672. // Included from: js/tinymce/classes/ui/ElementPath.js
  35673. /**
  35674. * ElementPath.js
  35675. *
  35676. * Released under LGPL License.
  35677. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  35678. *
  35679. * License: http://www.tinymce.com/license
  35680. * Contributing: http://www.tinymce.com/contributing
  35681. */
  35682. /**
  35683. * This control creates an path for the current selections parent elements in TinyMCE.
  35684. *
  35685. * @class tinymce.ui.ElementPath
  35686. * @extends tinymce.ui.Path
  35687. */
  35688. define("tinymce/ui/ElementPath", [
  35689. "tinymce/ui/Path"
  35690. ], function(Path) {
  35691. return Path.extend({
  35692. /**
  35693. * Post render method. Called after the control has been rendered to the target.
  35694. *
  35695. * @method postRender
  35696. * @return {tinymce.ui.ElementPath} Current combobox instance.
  35697. */
  35698. postRender: function() {
  35699. var self = this, editor = self.settings.editor;
  35700. function isHidden(elm) {
  35701. if (elm.nodeType === 1) {
  35702. if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) {
  35703. return true;
  35704. }
  35705. if (elm.getAttribute('data-mce-type') === 'bookmark') {
  35706. return true;
  35707. }
  35708. }
  35709. return false;
  35710. }
  35711. if (editor.settings.elementpath !== false) {
  35712. self.on('select', function(e) {
  35713. editor.focus();
  35714. editor.selection.select(this.row()[e.index].element);
  35715. editor.nodeChanged();
  35716. });
  35717. editor.on('nodeChange', function(e) {
  35718. var outParents = [], parents = e.parents, i = parents.length;
  35719. while (i--) {
  35720. if (parents[i].nodeType == 1 && !isHidden(parents[i])) {
  35721. var args = editor.fire('ResolveName', {
  35722. name: parents[i].nodeName.toLowerCase(),
  35723. target: parents[i]
  35724. });
  35725. if (!args.isDefaultPrevented()) {
  35726. outParents.push({name: args.name, element: parents[i]});
  35727. }
  35728. if (args.isPropagationStopped()) {
  35729. break;
  35730. }
  35731. }
  35732. }
  35733. self.row(outParents);
  35734. });
  35735. }
  35736. return self._super();
  35737. }
  35738. });
  35739. });
  35740. // Included from: js/tinymce/classes/ui/FormItem.js
  35741. /**
  35742. * FormItem.js
  35743. *
  35744. * Released under LGPL License.
  35745. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  35746. *
  35747. * License: http://www.tinymce.com/license
  35748. * Contributing: http://www.tinymce.com/contributing
  35749. */
  35750. /**
  35751. * This class is a container created by the form element with
  35752. * a label and control item.
  35753. *
  35754. * @class tinymce.ui.FormItem
  35755. * @extends tinymce.ui.Container
  35756. * @setting {String} label Label to display for the form item.
  35757. */
  35758. define("tinymce/ui/FormItem", [
  35759. "tinymce/ui/Container"
  35760. ], function(Container) {
  35761. "use strict";
  35762. return Container.extend({
  35763. Defaults: {
  35764. layout: 'flex',
  35765. align: 'center',
  35766. defaults: {
  35767. flex: 1
  35768. }
  35769. },
  35770. /**
  35771. * Renders the control as a HTML string.
  35772. *
  35773. * @method renderHtml
  35774. * @return {String} HTML representing the control.
  35775. */
  35776. renderHtml: function() {
  35777. var self = this, layout = self._layout, prefix = self.classPrefix;
  35778. self.classes.add('formitem');
  35779. layout.preRender(self);
  35780. return (
  35781. '<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' +
  35782. (self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
  35783. self.settings.title + '</div>') : '') +
  35784. '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' +
  35785. (self.settings.html || '') + layout.renderHtml(self) +
  35786. '</div>' +
  35787. '</div>'
  35788. );
  35789. }
  35790. });
  35791. });
  35792. // Included from: js/tinymce/classes/ui/Form.js
  35793. /**
  35794. * Form.js
  35795. *
  35796. * Released under LGPL License.
  35797. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  35798. *
  35799. * License: http://www.tinymce.com/license
  35800. * Contributing: http://www.tinymce.com/contributing
  35801. */
  35802. /**
  35803. * This class creates a form container. A form container has the ability
  35804. * to automatically wrap items in tinymce.ui.FormItem instances.
  35805. *
  35806. * Each FormItem instance is a container for the label and the item.
  35807. *
  35808. * @example
  35809. * tinymce.ui.Factory.create({
  35810. * type: 'form',
  35811. * items: [
  35812. * {type: 'textbox', label: 'My text box'}
  35813. * ]
  35814. * }).renderTo(document.body);
  35815. *
  35816. * @class tinymce.ui.Form
  35817. * @extends tinymce.ui.Container
  35818. */
  35819. define("tinymce/ui/Form", [
  35820. "tinymce/ui/Container",
  35821. "tinymce/ui/FormItem",
  35822. "tinymce/util/Tools"
  35823. ], function(Container, FormItem, Tools) {
  35824. "use strict";
  35825. return Container.extend({
  35826. Defaults: {
  35827. containerCls: 'form',
  35828. layout: 'flex',
  35829. direction: 'column',
  35830. align: 'stretch',
  35831. flex: 1,
  35832. padding: 20,
  35833. labelGap: 30,
  35834. spacing: 10,
  35835. callbacks: {
  35836. submit: function() {
  35837. this.submit();
  35838. }
  35839. }
  35840. },
  35841. /**
  35842. * This method gets invoked before the control is rendered.
  35843. *
  35844. * @method preRender
  35845. */
  35846. preRender: function() {
  35847. var self = this, items = self.items();
  35848. if (!self.settings.formItemDefaults) {
  35849. self.settings.formItemDefaults = {
  35850. layout: 'flex',
  35851. autoResize: "overflow",
  35852. defaults: {flex: 1}
  35853. };
  35854. }
  35855. // Wrap any labeled items in FormItems
  35856. items.each(function(ctrl) {
  35857. var formItem, label = ctrl.settings.label;
  35858. if (label) {
  35859. formItem = new FormItem(Tools.extend({
  35860. items: {
  35861. type: 'label',
  35862. id: ctrl._id + '-l',
  35863. text: label,
  35864. flex: 0,
  35865. forId: ctrl._id,
  35866. disabled: ctrl.disabled()
  35867. }
  35868. }, self.settings.formItemDefaults));
  35869. formItem.type = 'formitem';
  35870. ctrl.aria('labelledby', ctrl._id + '-l');
  35871. if (typeof ctrl.settings.flex == "undefined") {
  35872. ctrl.settings.flex = 1;
  35873. }
  35874. self.replace(ctrl, formItem);
  35875. formItem.add(ctrl);
  35876. }
  35877. });
  35878. },
  35879. /**
  35880. * Fires a submit event with the serialized form.
  35881. *
  35882. * @method submit
  35883. * @return {Object} Event arguments object.
  35884. */
  35885. submit: function() {
  35886. return this.fire('submit', {data: this.toJSON()});
  35887. },
  35888. /**
  35889. * Post render method. Called after the control has been rendered to the target.
  35890. *
  35891. * @method postRender
  35892. * @return {tinymce.ui.ComboBox} Current combobox instance.
  35893. */
  35894. postRender: function() {
  35895. var self = this;
  35896. self._super();
  35897. self.fromJSON(self.settings.data);
  35898. },
  35899. bindStates: function() {
  35900. var self = this;
  35901. self._super();
  35902. function recalcLabels() {
  35903. var maxLabelWidth = 0, labels = [], i, labelGap, items;
  35904. if (self.settings.labelGapCalc === false) {
  35905. return;
  35906. }
  35907. if (self.settings.labelGapCalc == "children") {
  35908. items = self.find('formitem');
  35909. } else {
  35910. items = self.items();
  35911. }
  35912. items.filter('formitem').each(function(item) {
  35913. var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
  35914. maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
  35915. labels.push(labelCtrl);
  35916. });
  35917. labelGap = self.settings.labelGap || 0;
  35918. i = labels.length;
  35919. while (i--) {
  35920. labels[i].settings.minWidth = maxLabelWidth + labelGap;
  35921. }
  35922. }
  35923. self.on('show', recalcLabels);
  35924. recalcLabels();
  35925. }
  35926. });
  35927. });
  35928. // Included from: js/tinymce/classes/ui/FieldSet.js
  35929. /**
  35930. * FieldSet.js
  35931. *
  35932. * Released under LGPL License.
  35933. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  35934. *
  35935. * License: http://www.tinymce.com/license
  35936. * Contributing: http://www.tinymce.com/contributing
  35937. */
  35938. /**
  35939. * This class creates fieldset containers.
  35940. *
  35941. * @-x-less FieldSet.less
  35942. * @class tinymce.ui.FieldSet
  35943. * @extends tinymce.ui.Form
  35944. */
  35945. define("tinymce/ui/FieldSet", [
  35946. "tinymce/ui/Form"
  35947. ], function(Form) {
  35948. "use strict";
  35949. return Form.extend({
  35950. Defaults: {
  35951. containerCls: 'fieldset',
  35952. layout: 'flex',
  35953. direction: 'column',
  35954. align: 'stretch',
  35955. flex: 1,
  35956. padding: "25 15 5 15",
  35957. labelGap: 30,
  35958. spacing: 10,
  35959. border: 1
  35960. },
  35961. /**
  35962. * Renders the control as a HTML string.
  35963. *
  35964. * @method renderHtml
  35965. * @return {String} HTML representing the control.
  35966. */
  35967. renderHtml: function() {
  35968. var self = this, layout = self._layout, prefix = self.classPrefix;
  35969. self.preRender();
  35970. layout.preRender(self);
  35971. return (
  35972. '<fieldset id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' +
  35973. (self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
  35974. self.settings.title + '</legend>') : '') +
  35975. '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' +
  35976. (self.settings.html || '') + layout.renderHtml(self) +
  35977. '</div>' +
  35978. '</fieldset>'
  35979. );
  35980. }
  35981. });
  35982. });
  35983. // Included from: js/tinymce/classes/ui/FilePicker.js
  35984. /**
  35985. * FilePicker.js
  35986. *
  35987. * Released under LGPL License.
  35988. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  35989. *
  35990. * License: http://www.tinymce.com/license
  35991. * Contributing: http://www.tinymce.com/contributing
  35992. */
  35993. /*global tinymce:true */
  35994. /**
  35995. * This class creates a file picker control.
  35996. *
  35997. * @class tinymce.ui.FilePicker
  35998. * @extends tinymce.ui.ComboBox
  35999. */
  36000. define("tinymce/ui/FilePicker", [
  36001. "tinymce/ui/ComboBox",
  36002. "tinymce/util/Tools"
  36003. ], function(ComboBox, Tools) {
  36004. "use strict";
  36005. return ComboBox.extend({
  36006. /**
  36007. * Constructs a new control instance with the specified settings.
  36008. *
  36009. * @constructor
  36010. * @param {Object} settings Name/value object with settings.
  36011. */
  36012. init: function(settings) {
  36013. var self = this, editor = tinymce.activeEditor, editorSettings = editor.settings;
  36014. var actionCallback, fileBrowserCallback, fileBrowserCallbackTypes;
  36015. settings.spellcheck = false;
  36016. fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types;
  36017. if (fileBrowserCallbackTypes) {
  36018. fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/);
  36019. }
  36020. if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype]) {
  36021. fileBrowserCallback = editorSettings.file_picker_callback;
  36022. if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
  36023. actionCallback = function() {
  36024. var meta = self.fire('beforecall').meta;
  36025. meta = Tools.extend({filetype: settings.filetype}, meta);
  36026. // file_picker_callback(callback, currentValue, metaData)
  36027. fileBrowserCallback.call(
  36028. editor,
  36029. function(value, meta) {
  36030. self.value(value).fire('change', {meta: meta});
  36031. },
  36032. self.value(),
  36033. meta
  36034. );
  36035. };
  36036. } else {
  36037. // Legacy callback: file_picker_callback(id, currentValue, filetype, window)
  36038. fileBrowserCallback = editorSettings.file_browser_callback;
  36039. if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
  36040. actionCallback = function() {
  36041. fileBrowserCallback(
  36042. self.getEl('inp').id,
  36043. self.value(),
  36044. settings.filetype,
  36045. window
  36046. );
  36047. };
  36048. }
  36049. }
  36050. }
  36051. if (actionCallback) {
  36052. settings.icon = 'browse';
  36053. settings.onaction = actionCallback;
  36054. }
  36055. self._super(settings);
  36056. }
  36057. });
  36058. });
  36059. // Included from: js/tinymce/classes/ui/FitLayout.js
  36060. /**
  36061. * FitLayout.js
  36062. *
  36063. * Released under LGPL License.
  36064. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  36065. *
  36066. * License: http://www.tinymce.com/license
  36067. * Contributing: http://www.tinymce.com/contributing
  36068. */
  36069. /**
  36070. * This layout manager will resize the control to be the size of it's parent container.
  36071. * In other words width: 100% and height: 100%.
  36072. *
  36073. * @-x-less FitLayout.less
  36074. * @class tinymce.ui.FitLayout
  36075. * @extends tinymce.ui.AbsoluteLayout
  36076. */
  36077. define("tinymce/ui/FitLayout", [
  36078. "tinymce/ui/AbsoluteLayout"
  36079. ], function(AbsoluteLayout) {
  36080. "use strict";
  36081. return AbsoluteLayout.extend({
  36082. /**
  36083. * Recalculates the positions of the controls in the specified container.
  36084. *
  36085. * @method recalc
  36086. * @param {tinymce.ui.Container} container Container instance to recalc.
  36087. */
  36088. recalc: function(container) {
  36089. var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox;
  36090. container.items().filter(':visible').each(function(ctrl) {
  36091. ctrl.layoutRect({
  36092. x: paddingBox.left,
  36093. y: paddingBox.top,
  36094. w: contLayoutRect.innerW - paddingBox.right - paddingBox.left,
  36095. h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom
  36096. });
  36097. if (ctrl.recalc) {
  36098. ctrl.recalc();
  36099. }
  36100. });
  36101. }
  36102. });
  36103. });
  36104. // Included from: js/tinymce/classes/ui/FlexLayout.js
  36105. /**
  36106. * FlexLayout.js
  36107. *
  36108. * Released under LGPL License.
  36109. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  36110. *
  36111. * License: http://www.tinymce.com/license
  36112. * Contributing: http://www.tinymce.com/contributing
  36113. */
  36114. /**
  36115. * This layout manager works similar to the CSS flex box.
  36116. *
  36117. * @setting {String} direction row|row-reverse|column|column-reverse
  36118. * @setting {Number} flex A positive-number to flex by.
  36119. * @setting {String} align start|end|center|stretch
  36120. * @setting {String} pack start|end|justify
  36121. *
  36122. * @class tinymce.ui.FlexLayout
  36123. * @extends tinymce.ui.AbsoluteLayout
  36124. */
  36125. define("tinymce/ui/FlexLayout", [
  36126. "tinymce/ui/AbsoluteLayout"
  36127. ], function(AbsoluteLayout) {
  36128. "use strict";
  36129. return AbsoluteLayout.extend({
  36130. /**
  36131. * Recalculates the positions of the controls in the specified container.
  36132. *
  36133. * @method recalc
  36134. * @param {tinymce.ui.Container} container Container instance to recalc.
  36135. */
  36136. recalc: function(container) {
  36137. // A ton of variables, needs to be in the same scope for performance
  36138. var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
  36139. var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
  36140. var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
  36141. var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
  36142. var alignDeltaSizeName, alignContentSizeName;
  36143. var max = Math.max, min = Math.min;
  36144. // Get container items, properties and settings
  36145. items = container.items().filter(':visible');
  36146. contLayoutRect = container.layoutRect();
  36147. contPaddingBox = container.paddingBox;
  36148. contSettings = container.settings;
  36149. direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction;
  36150. align = contSettings.align;
  36151. pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack;
  36152. spacing = contSettings.spacing || 0;
  36153. if (direction == "row-reversed" || direction == "column-reverse") {
  36154. items = items.set(items.toArray().reverse());
  36155. direction = direction.split('-')[0];
  36156. }
  36157. // Setup axis variable name for row/column direction since the calculations is the same
  36158. if (direction == "column") {
  36159. posName = "y";
  36160. sizeName = "h";
  36161. minSizeName = "minH";
  36162. maxSizeName = "maxH";
  36163. innerSizeName = "innerH";
  36164. beforeName = 'top';
  36165. deltaSizeName = "deltaH";
  36166. contentSizeName = "contentH";
  36167. alignBeforeName = "left";
  36168. alignSizeName = "w";
  36169. alignAxisName = "x";
  36170. alignInnerSizeName = "innerW";
  36171. alignMinSizeName = "minW";
  36172. alignAfterName = "right";
  36173. alignDeltaSizeName = "deltaW";
  36174. alignContentSizeName = "contentW";
  36175. } else {
  36176. posName = "x";
  36177. sizeName = "w";
  36178. minSizeName = "minW";
  36179. maxSizeName = "maxW";
  36180. innerSizeName = "innerW";
  36181. beforeName = 'left';
  36182. deltaSizeName = "deltaW";
  36183. contentSizeName = "contentW";
  36184. alignBeforeName = "top";
  36185. alignSizeName = "h";
  36186. alignAxisName = "y";
  36187. alignInnerSizeName = "innerH";
  36188. alignMinSizeName = "minH";
  36189. alignAfterName = "bottom";
  36190. alignDeltaSizeName = "deltaH";
  36191. alignContentSizeName = "contentH";
  36192. }
  36193. // Figure out total flex, availableSpace and collect any max size elements
  36194. availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
  36195. maxAlignEndPos = totalFlex = 0;
  36196. for (i = 0, l = items.length; i < l; i++) {
  36197. ctrl = items[i];
  36198. ctrlLayoutRect = ctrl.layoutRect();
  36199. ctrlSettings = ctrl.settings;
  36200. flex = ctrlSettings.flex;
  36201. availableSpace -= (i < l - 1 ? spacing : 0);
  36202. if (flex > 0) {
  36203. totalFlex += flex;
  36204. // Flexed item has a max size then we need to check if we will hit that size
  36205. if (ctrlLayoutRect[maxSizeName]) {
  36206. maxSizeItems.push(ctrl);
  36207. }
  36208. ctrlLayoutRect.flex = flex;
  36209. }
  36210. availableSpace -= ctrlLayoutRect[minSizeName];
  36211. // Calculate the align end position to be used to check for overflow/underflow
  36212. size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
  36213. if (size > maxAlignEndPos) {
  36214. maxAlignEndPos = size;
  36215. }
  36216. }
  36217. // Calculate minW/minH
  36218. rect = {};
  36219. if (availableSpace < 0) {
  36220. rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
  36221. } else {
  36222. rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
  36223. }
  36224. rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
  36225. rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
  36226. rect[alignContentSizeName] = maxAlignEndPos;
  36227. rect.minW = min(rect.minW, contLayoutRect.maxW);
  36228. rect.minH = min(rect.minH, contLayoutRect.maxH);
  36229. rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
  36230. rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
  36231. // Resize container container if minSize was changed
  36232. if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
  36233. rect.w = rect.minW;
  36234. rect.h = rect.minH;
  36235. container.layoutRect(rect);
  36236. this.recalc(container);
  36237. // Forced recalc for example if items are hidden/shown
  36238. if (container._lastRect === null) {
  36239. var parentCtrl = container.parent();
  36240. if (parentCtrl) {
  36241. parentCtrl._lastRect = null;
  36242. parentCtrl.recalc();
  36243. }
  36244. }
  36245. return;
  36246. }
  36247. // Handle max size elements, check if they will become to wide with current options
  36248. ratio = availableSpace / totalFlex;
  36249. for (i = 0, l = maxSizeItems.length; i < l; i++) {
  36250. ctrl = maxSizeItems[i];
  36251. ctrlLayoutRect = ctrl.layoutRect();
  36252. maxSize = ctrlLayoutRect[maxSizeName];
  36253. size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;
  36254. if (size > maxSize) {
  36255. availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
  36256. totalFlex -= ctrlLayoutRect.flex;
  36257. ctrlLayoutRect.flex = 0;
  36258. ctrlLayoutRect.maxFlexSize = maxSize;
  36259. } else {
  36260. ctrlLayoutRect.maxFlexSize = 0;
  36261. }
  36262. }
  36263. // Setup new ratio, target layout rect, start position
  36264. ratio = availableSpace / totalFlex;
  36265. pos = contPaddingBox[beforeName];
  36266. rect = {};
  36267. // Handle pack setting moves the start position to end, center
  36268. if (totalFlex === 0) {
  36269. if (pack == "end") {
  36270. pos = availableSpace + contPaddingBox[beforeName];
  36271. } else if (pack == "center") {
  36272. pos = Math.round(
  36273. (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
  36274. ) + contPaddingBox[beforeName];
  36275. if (pos < 0) {
  36276. pos = contPaddingBox[beforeName];
  36277. }
  36278. } else if (pack == "justify") {
  36279. pos = contPaddingBox[beforeName];
  36280. spacing = Math.floor(availableSpace / (items.length - 1));
  36281. }
  36282. }
  36283. // Default aligning (start) the other ones needs to be calculated while doing the layout
  36284. rect[alignAxisName] = contPaddingBox[alignBeforeName];
  36285. // Start laying out controls
  36286. for (i = 0, l = items.length; i < l; i++) {
  36287. ctrl = items[i];
  36288. ctrlLayoutRect = ctrl.layoutRect();
  36289. size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
  36290. // Align the control on the other axis
  36291. if (align === "center") {
  36292. rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
  36293. } else if (align === "stretch") {
  36294. rect[alignSizeName] = max(
  36295. ctrlLayoutRect[alignMinSizeName] || 0,
  36296. contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
  36297. );
  36298. rect[alignAxisName] = contPaddingBox[alignBeforeName];
  36299. } else if (align === "end") {
  36300. rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top;
  36301. }
  36302. // Calculate new size based on flex
  36303. if (ctrlLayoutRect.flex > 0) {
  36304. size += ctrlLayoutRect.flex * ratio;
  36305. }
  36306. rect[sizeName] = size;
  36307. rect[posName] = pos;
  36308. ctrl.layoutRect(rect);
  36309. // Recalculate containers
  36310. if (ctrl.recalc) {
  36311. ctrl.recalc();
  36312. }
  36313. // Move x/y position
  36314. pos += size + spacing;
  36315. }
  36316. }
  36317. });
  36318. });
  36319. // Included from: js/tinymce/classes/ui/FlowLayout.js
  36320. /**
  36321. * FlowLayout.js
  36322. *
  36323. * Released under LGPL License.
  36324. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  36325. *
  36326. * License: http://www.tinymce.com/license
  36327. * Contributing: http://www.tinymce.com/contributing
  36328. */
  36329. /**
  36330. * This layout manager will place the controls by using the browsers native layout.
  36331. *
  36332. * @-x-less FlowLayout.less
  36333. * @class tinymce.ui.FlowLayout
  36334. * @extends tinymce.ui.Layout
  36335. */
  36336. define("tinymce/ui/FlowLayout", [
  36337. "tinymce/ui/Layout"
  36338. ], function(Layout) {
  36339. return Layout.extend({
  36340. Defaults: {
  36341. containerClass: 'flow-layout',
  36342. controlClass: 'flow-layout-item',
  36343. endClass: 'break'
  36344. },
  36345. /**
  36346. * Recalculates the positions of the controls in the specified container.
  36347. *
  36348. * @method recalc
  36349. * @param {tinymce.ui.Container} container Container instance to recalc.
  36350. */
  36351. recalc: function(container) {
  36352. container.items().filter(':visible').each(function(ctrl) {
  36353. if (ctrl.recalc) {
  36354. ctrl.recalc();
  36355. }
  36356. });
  36357. },
  36358. isNative: function() {
  36359. return true;
  36360. }
  36361. });
  36362. });
  36363. // Included from: js/tinymce/classes/ui/FormatControls.js
  36364. /**
  36365. * FormatControls.js
  36366. *
  36367. * Released under LGPL License.
  36368. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  36369. *
  36370. * License: http://www.tinymce.com/license
  36371. * Contributing: http://www.tinymce.com/contributing
  36372. */
  36373. /**
  36374. * Internal class containing all TinyMCE specific control types such as
  36375. * format listboxes, fontlist boxes, toolbar buttons etc.
  36376. *
  36377. * @class tinymce.ui.FormatControls
  36378. */
  36379. define("tinymce/ui/FormatControls", [
  36380. "tinymce/ui/Control",
  36381. "tinymce/ui/Widget",
  36382. "tinymce/ui/FloatPanel",
  36383. "tinymce/util/Tools",
  36384. "tinymce/EditorManager",
  36385. "tinymce/Env"
  36386. ], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) {
  36387. var each = Tools.each;
  36388. EditorManager.on('AddEditor', function(e) {
  36389. if (e.editor.rtl) {
  36390. Control.rtl = true;
  36391. }
  36392. registerControls(e.editor);
  36393. });
  36394. Control.translate = function(text) {
  36395. return EditorManager.translate(text);
  36396. };
  36397. Widget.tooltips = !Env.iOS;
  36398. function registerControls(editor) {
  36399. var formatMenu;
  36400. function createListBoxChangeHandler(items, formatName) {
  36401. return function() {
  36402. var self = this;
  36403. editor.on('nodeChange', function(e) {
  36404. var formatter = editor.formatter;
  36405. var value = null;
  36406. each(e.parents, function(node) {
  36407. each(items, function(item) {
  36408. if (formatName) {
  36409. if (formatter.matchNode(node, formatName, {value: item.value})) {
  36410. value = item.value;
  36411. }
  36412. } else {
  36413. if (formatter.matchNode(node, item.value)) {
  36414. value = item.value;
  36415. }
  36416. }
  36417. if (value) {
  36418. return false;
  36419. }
  36420. });
  36421. if (value) {
  36422. return false;
  36423. }
  36424. });
  36425. self.value(value);
  36426. });
  36427. };
  36428. }
  36429. function createFormats(formats) {
  36430. formats = formats.replace(/;$/, '').split(';');
  36431. var i = formats.length;
  36432. while (i--) {
  36433. formats[i] = formats[i].split('=');
  36434. }
  36435. return formats;
  36436. }
  36437. function createFormatMenu() {
  36438. var count = 0, newFormats = [];
  36439. var defaultStyleFormats = [
  36440. {title: 'Headings', items: [
  36441. {title: 'Heading 1', format: 'h1'},
  36442. {title: 'Heading 2', format: 'h2'},
  36443. {title: 'Heading 3', format: 'h3'},
  36444. {title: 'Heading 4', format: 'h4'},
  36445. {title: 'Heading 5', format: 'h5'},
  36446. {title: 'Heading 6', format: 'h6'}
  36447. ]},
  36448. {title: 'Inline', items: [
  36449. {title: 'Bold', icon: 'bold', format: 'bold'},
  36450. {title: 'Italic', icon: 'italic', format: 'italic'},
  36451. {title: 'Underline', icon: 'underline', format: 'underline'},
  36452. {title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'},
  36453. {title: 'Superscript', icon: 'superscript', format: 'superscript'},
  36454. {title: 'Subscript', icon: 'subscript', format: 'subscript'},
  36455. {title: 'Code', icon: 'code', format: 'code'}
  36456. ]},
  36457. {title: 'Blocks', items: [
  36458. {title: 'Paragraph', format: 'p'},
  36459. {title: 'Blockquote', format: 'blockquote'},
  36460. {title: 'Div', format: 'div'},
  36461. {title: 'Pre', format: 'pre'}
  36462. ]},
  36463. {title: 'Alignment', items: [
  36464. {title: 'Left', icon: 'alignleft', format: 'alignleft'},
  36465. {title: 'Center', icon: 'aligncenter', format: 'aligncenter'},
  36466. {title: 'Right', icon: 'alignright', format: 'alignright'},
  36467. {title: 'Justify', icon: 'alignjustify', format: 'alignjustify'}
  36468. ]}
  36469. ];
  36470. function createMenu(formats) {
  36471. var menu = [];
  36472. if (!formats) {
  36473. return;
  36474. }
  36475. each(formats, function(format) {
  36476. var menuItem = {
  36477. text: format.title,
  36478. icon: format.icon
  36479. };
  36480. if (format.items) {
  36481. menuItem.menu = createMenu(format.items);
  36482. } else {
  36483. var formatName = format.format || "custom" + count++;
  36484. if (!format.format) {
  36485. format.name = formatName;
  36486. newFormats.push(format);
  36487. }
  36488. menuItem.format = formatName;
  36489. menuItem.cmd = format.cmd;
  36490. }
  36491. menu.push(menuItem);
  36492. });
  36493. return menu;
  36494. }
  36495. function createStylesMenu() {
  36496. var menu;
  36497. if (editor.settings.style_formats_merge) {
  36498. if (editor.settings.style_formats) {
  36499. menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats));
  36500. } else {
  36501. menu = createMenu(defaultStyleFormats);
  36502. }
  36503. } else {
  36504. menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
  36505. }
  36506. return menu;
  36507. }
  36508. editor.on('init', function() {
  36509. each(newFormats, function(format) {
  36510. editor.formatter.register(format.name, format);
  36511. });
  36512. });
  36513. return {
  36514. type: 'menu',
  36515. items: createStylesMenu(),
  36516. onPostRender: function(e) {
  36517. editor.fire('renderFormatsMenu', {control: e.control});
  36518. },
  36519. itemDefaults: {
  36520. preview: true,
  36521. textStyle: function() {
  36522. if (this.settings.format) {
  36523. return editor.formatter.getCssText(this.settings.format);
  36524. }
  36525. },
  36526. onPostRender: function() {
  36527. var self = this;
  36528. self.parent().on('show', function() {
  36529. var formatName, command;
  36530. formatName = self.settings.format;
  36531. if (formatName) {
  36532. self.disabled(!editor.formatter.canApply(formatName));
  36533. self.active(editor.formatter.match(formatName));
  36534. }
  36535. command = self.settings.cmd;
  36536. if (command) {
  36537. self.active(editor.queryCommandState(command));
  36538. }
  36539. });
  36540. },
  36541. onclick: function() {
  36542. if (this.settings.format) {
  36543. toggleFormat(this.settings.format);
  36544. }
  36545. if (this.settings.cmd) {
  36546. editor.execCommand(this.settings.cmd);
  36547. }
  36548. }
  36549. }
  36550. };
  36551. }
  36552. formatMenu = createFormatMenu();
  36553. function initOnPostRender(name) {
  36554. return function() {
  36555. var self = this;
  36556. // TODO: Fix this
  36557. if (editor.formatter) {
  36558. editor.formatter.formatChanged(name, function(state) {
  36559. self.active(state);
  36560. });
  36561. } else {
  36562. editor.on('init', function() {
  36563. editor.formatter.formatChanged(name, function(state) {
  36564. self.active(state);
  36565. });
  36566. });
  36567. }
  36568. };
  36569. }
  36570. // Simple format controls <control/format>:<UI text>
  36571. each({
  36572. bold: 'Bold',
  36573. italic: 'Italic',
  36574. underline: 'Underline',
  36575. strikethrough: 'Strikethrough',
  36576. subscript: 'Subscript',
  36577. superscript: 'Superscript'
  36578. }, function(text, name) {
  36579. editor.addButton(name, {
  36580. tooltip: text,
  36581. onPostRender: initOnPostRender(name),
  36582. onclick: function() {
  36583. toggleFormat(name);
  36584. }
  36585. });
  36586. });
  36587. // Simple command controls <control>:[<UI text>,<Command>]
  36588. each({
  36589. outdent: ['Decrease indent', 'Outdent'],
  36590. indent: ['Increase indent', 'Indent'],
  36591. cut: ['Cut', 'Cut'],
  36592. copy: ['Copy', 'Copy'],
  36593. paste: ['Paste', 'Paste'],
  36594. help: ['Help', 'mceHelp'],
  36595. selectall: ['Select all', 'SelectAll'],
  36596. removeformat: ['Clear formatting', 'RemoveFormat'],
  36597. visualaid: ['Visual aids', 'mceToggleVisualAid'],
  36598. newdocument: ['New document', 'mceNewDocument']
  36599. }, function(item, name) {
  36600. editor.addButton(name, {
  36601. tooltip: item[0],
  36602. cmd: item[1]
  36603. });
  36604. });
  36605. // Simple command controls with format state
  36606. each({
  36607. blockquote: ['Blockquote', 'mceBlockQuote'],
  36608. numlist: ['Numbered list', 'InsertOrderedList'],
  36609. bullist: ['Bullet list', 'InsertUnorderedList'],
  36610. subscript: ['Subscript', 'Subscript'],
  36611. superscript: ['Superscript', 'Superscript'],
  36612. alignleft: ['Align left', 'JustifyLeft'],
  36613. aligncenter: ['Align center', 'JustifyCenter'],
  36614. alignright: ['Align right', 'JustifyRight'],
  36615. alignjustify: ['Justify', 'JustifyFull'],
  36616. alignnone: ['No alignment', 'JustifyNone']
  36617. }, function(item, name) {
  36618. editor.addButton(name, {
  36619. tooltip: item[0],
  36620. cmd: item[1],
  36621. onPostRender: initOnPostRender(name)
  36622. });
  36623. });
  36624. function toggleUndoRedoState(type) {
  36625. return function() {
  36626. var self = this;
  36627. type = type == 'redo' ? 'hasRedo' : 'hasUndo';
  36628. function checkState() {
  36629. return editor.undoManager ? editor.undoManager[type]() : false;
  36630. }
  36631. self.disabled(!checkState());
  36632. editor.on('Undo Redo AddUndo TypingUndo ClearUndos SwitchMode', function() {
  36633. self.disabled(editor.readonly || !checkState());
  36634. });
  36635. };
  36636. }
  36637. function toggleVisualAidState() {
  36638. var self = this;
  36639. editor.on('VisualAid', function(e) {
  36640. self.active(e.hasVisual);
  36641. });
  36642. self.active(editor.hasVisual);
  36643. }
  36644. editor.addButton('undo', {
  36645. tooltip: 'Undo',
  36646. onPostRender: toggleUndoRedoState('undo'),
  36647. cmd: 'undo'
  36648. });
  36649. editor.addButton('redo', {
  36650. tooltip: 'Redo',
  36651. onPostRender: toggleUndoRedoState('redo'),
  36652. cmd: 'redo'
  36653. });
  36654. editor.addMenuItem('newdocument', {
  36655. text: 'New document',
  36656. icon: 'newdocument',
  36657. cmd: 'mceNewDocument'
  36658. });
  36659. editor.addMenuItem('undo', {
  36660. text: 'Undo',
  36661. icon: 'undo',
  36662. shortcut: 'Meta+Z',
  36663. onPostRender: toggleUndoRedoState('undo'),
  36664. cmd: 'undo'
  36665. });
  36666. editor.addMenuItem('redo', {
  36667. text: 'Redo',
  36668. icon: 'redo',
  36669. shortcut: 'Meta+Y',
  36670. onPostRender: toggleUndoRedoState('redo'),
  36671. cmd: 'redo'
  36672. });
  36673. editor.addMenuItem('visualaid', {
  36674. text: 'Visual aids',
  36675. selectable: true,
  36676. onPostRender: toggleVisualAidState,
  36677. cmd: 'mceToggleVisualAid'
  36678. });
  36679. editor.addButton('remove', {
  36680. tooltip: 'Remove',
  36681. icon: 'remove',
  36682. cmd: 'Delete'
  36683. });
  36684. each({
  36685. cut: ['Cut', 'Cut', 'Meta+X'],
  36686. copy: ['Copy', 'Copy', 'Meta+C'],
  36687. paste: ['Paste', 'Paste', 'Meta+V'],
  36688. selectall: ['Select all', 'SelectAll', 'Meta+A'],
  36689. bold: ['Bold', 'Bold', 'Meta+B'],
  36690. italic: ['Italic', 'Italic', 'Meta+I'],
  36691. underline: ['Underline', 'Underline'],
  36692. strikethrough: ['Strikethrough', 'Strikethrough'],
  36693. subscript: ['Subscript', 'Subscript'],
  36694. superscript: ['Superscript', 'Superscript'],
  36695. removeformat: ['Clear formatting', 'RemoveFormat']
  36696. }, function(item, name) {
  36697. editor.addMenuItem(name, {
  36698. text: item[0],
  36699. icon: name,
  36700. shortcut: item[2],
  36701. cmd: item[1]
  36702. });
  36703. });
  36704. editor.on('mousedown', function() {
  36705. FloatPanel.hideAll();
  36706. });
  36707. function toggleFormat(fmt) {
  36708. if (fmt.control) {
  36709. fmt = fmt.control.value();
  36710. }
  36711. if (fmt) {
  36712. editor.execCommand('mceToggleFormat', false, fmt);
  36713. }
  36714. }
  36715. editor.addButton('styleselect', {
  36716. type: 'menubutton',
  36717. text: 'Formats',
  36718. menu: formatMenu
  36719. });
  36720. editor.addButton('formatselect', function() {
  36721. var items = [], blocks = createFormats(editor.settings.block_formats ||
  36722. 'Paragraph=p;' +
  36723. 'Heading 1=h1;' +
  36724. 'Heading 2=h2;' +
  36725. 'Heading 3=h3;' +
  36726. 'Heading 4=h4;' +
  36727. 'Heading 5=h5;' +
  36728. 'Heading 6=h6;' +
  36729. 'Preformatted=pre'
  36730. );
  36731. each(blocks, function(block) {
  36732. items.push({
  36733. text: block[0],
  36734. value: block[1],
  36735. textStyle: function() {
  36736. return editor.formatter.getCssText(block[1]);
  36737. }
  36738. });
  36739. });
  36740. return {
  36741. type: 'listbox',
  36742. text: blocks[0][0],
  36743. values: items,
  36744. fixedWidth: true,
  36745. onselect: toggleFormat,
  36746. onPostRender: createListBoxChangeHandler(items)
  36747. };
  36748. });
  36749. editor.addButton('fontselect', function() {
  36750. var defaultFontsFormats =
  36751. 'Andale Mono=andale mono,monospace;' +
  36752. 'Arial=arial,helvetica,sans-serif;' +
  36753. 'Arial Black=arial black,sans-serif;' +
  36754. 'Book Antiqua=book antiqua,palatino,serif;' +
  36755. 'Comic Sans MS=comic sans ms,sans-serif;' +
  36756. 'Courier New=courier new,courier,monospace;' +
  36757. 'Georgia=georgia,palatino,serif;' +
  36758. 'Helvetica=helvetica,arial,sans-serif;' +
  36759. 'Impact=impact,sans-serif;' +
  36760. 'Symbol=symbol;' +
  36761. 'Tahoma=tahoma,arial,helvetica,sans-serif;' +
  36762. 'Terminal=terminal,monaco,monospace;' +
  36763. 'Times New Roman=times new roman,times,serif;' +
  36764. 'Trebuchet MS=trebuchet ms,geneva,sans-serif;' +
  36765. 'Verdana=verdana,geneva,sans-serif;' +
  36766. 'Webdings=webdings;' +
  36767. 'Wingdings=wingdings,zapf dingbats';
  36768. var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats);
  36769. each(fonts, function(font) {
  36770. items.push({
  36771. text: {raw: font[0]},
  36772. value: font[1],
  36773. textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : ''
  36774. });
  36775. });
  36776. return {
  36777. type: 'listbox',
  36778. text: 'Font Family',
  36779. tooltip: 'Font Family',
  36780. values: items,
  36781. fixedWidth: true,
  36782. onPostRender: createListBoxChangeHandler(items, 'fontname'),
  36783. onselect: function(e) {
  36784. if (e.control.settings.value) {
  36785. editor.execCommand('FontName', false, e.control.settings.value);
  36786. }
  36787. }
  36788. };
  36789. });
  36790. editor.addButton('fontsizeselect', function() {
  36791. var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt';
  36792. var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
  36793. each(fontsize_formats.split(' '), function(item) {
  36794. var text = item, value = item;
  36795. // Allow text=value font sizes.
  36796. var values = item.split('=');
  36797. if (values.length > 1) {
  36798. text = values[0];
  36799. value = values[1];
  36800. }
  36801. items.push({text: text, value: value});
  36802. });
  36803. return {
  36804. type: 'listbox',
  36805. text: 'Font Sizes',
  36806. tooltip: 'Font Sizes',
  36807. values: items,
  36808. fixedWidth: true,
  36809. onPostRender: createListBoxChangeHandler(items, 'fontsize'),
  36810. onclick: function(e) {
  36811. if (e.control.settings.value) {
  36812. editor.execCommand('FontSize', false, e.control.settings.value);
  36813. }
  36814. }
  36815. };
  36816. });
  36817. editor.addMenuItem('formats', {
  36818. text: 'Formats',
  36819. menu: formatMenu
  36820. });
  36821. }
  36822. });
  36823. // Included from: js/tinymce/classes/ui/GridLayout.js
  36824. /**
  36825. * GridLayout.js
  36826. *
  36827. * Released under LGPL License.
  36828. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  36829. *
  36830. * License: http://www.tinymce.com/license
  36831. * Contributing: http://www.tinymce.com/contributing
  36832. */
  36833. /**
  36834. * This layout manager places controls in a grid.
  36835. *
  36836. * @setting {Number} spacing Spacing between controls.
  36837. * @setting {Number} spacingH Horizontal spacing between controls.
  36838. * @setting {Number} spacingV Vertical spacing between controls.
  36839. * @setting {Number} columns Number of columns to use.
  36840. * @setting {String/Array} alignH start|end|center|stretch or array of values for each column.
  36841. * @setting {String/Array} alignV start|end|center|stretch or array of values for each column.
  36842. * @setting {String} pack start|end
  36843. *
  36844. * @class tinymce.ui.GridLayout
  36845. * @extends tinymce.ui.AbsoluteLayout
  36846. */
  36847. define("tinymce/ui/GridLayout", [
  36848. "tinymce/ui/AbsoluteLayout"
  36849. ], function(AbsoluteLayout) {
  36850. "use strict";
  36851. return AbsoluteLayout.extend({
  36852. /**
  36853. * Recalculates the positions of the controls in the specified container.
  36854. *
  36855. * @method recalc
  36856. * @param {tinymce.ui.Container} container Container instance to recalc.
  36857. */
  36858. recalc: function(container) {
  36859. var settings, rows, cols, items, contLayoutRect, width, height, rect,
  36860. ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY,
  36861. colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight, reverseRows, idx;
  36862. // Get layout settings
  36863. settings = container.settings;
  36864. items = container.items().filter(':visible');
  36865. contLayoutRect = container.layoutRect();
  36866. cols = settings.columns || Math.ceil(Math.sqrt(items.length));
  36867. rows = Math.ceil(items.length / cols);
  36868. spacingH = settings.spacingH || settings.spacing || 0;
  36869. spacingV = settings.spacingV || settings.spacing || 0;
  36870. alignH = settings.alignH || settings.align;
  36871. alignV = settings.alignV || settings.align;
  36872. contPaddingBox = container.paddingBox;
  36873. reverseRows = 'reverseRows' in settings ? settings.reverseRows : container.isRtl();
  36874. if (alignH && typeof alignH == "string") {
  36875. alignH = [alignH];
  36876. }
  36877. if (alignV && typeof alignV == "string") {
  36878. alignV = [alignV];
  36879. }
  36880. // Zero padd columnWidths
  36881. for (x = 0; x < cols; x++) {
  36882. colWidths.push(0);
  36883. }
  36884. // Zero padd rowHeights
  36885. for (y = 0; y < rows; y++) {
  36886. rowHeights.push(0);
  36887. }
  36888. // Calculate columnWidths and rowHeights
  36889. for (y = 0; y < rows; y++) {
  36890. for (x = 0; x < cols; x++) {
  36891. ctrl = items[y * cols + x];
  36892. // Out of bounds
  36893. if (!ctrl) {
  36894. break;
  36895. }
  36896. ctrlLayoutRect = ctrl.layoutRect();
  36897. ctrlMinWidth = ctrlLayoutRect.minW;
  36898. ctrlMinHeight = ctrlLayoutRect.minH;
  36899. colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x];
  36900. rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y];
  36901. }
  36902. }
  36903. // Calculate maxX
  36904. availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right;
  36905. for (maxX = 0, x = 0; x < cols; x++) {
  36906. maxX += colWidths[x] + (x > 0 ? spacingH : 0);
  36907. availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x];
  36908. }
  36909. // Calculate maxY
  36910. availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom;
  36911. for (maxY = 0, y = 0; y < rows; y++) {
  36912. maxY += rowHeights[y] + (y > 0 ? spacingV : 0);
  36913. availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y];
  36914. }
  36915. maxX += contPaddingBox.left + contPaddingBox.right;
  36916. maxY += contPaddingBox.top + contPaddingBox.bottom;
  36917. // Calculate minW/minH
  36918. rect = {};
  36919. rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW);
  36920. rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH);
  36921. rect.contentW = rect.minW - contLayoutRect.deltaW;
  36922. rect.contentH = rect.minH - contLayoutRect.deltaH;
  36923. rect.minW = Math.min(rect.minW, contLayoutRect.maxW);
  36924. rect.minH = Math.min(rect.minH, contLayoutRect.maxH);
  36925. rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth);
  36926. rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight);
  36927. // Resize container container if minSize was changed
  36928. if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
  36929. rect.w = rect.minW;
  36930. rect.h = rect.minH;
  36931. container.layoutRect(rect);
  36932. this.recalc(container);
  36933. // Forced recalc for example if items are hidden/shown
  36934. if (container._lastRect === null) {
  36935. var parentCtrl = container.parent();
  36936. if (parentCtrl) {
  36937. parentCtrl._lastRect = null;
  36938. parentCtrl.recalc();
  36939. }
  36940. }
  36941. return;
  36942. }
  36943. // Update contentW/contentH so absEnd moves correctly
  36944. if (contLayoutRect.autoResize) {
  36945. rect = container.layoutRect(rect);
  36946. rect.contentW = rect.minW - contLayoutRect.deltaW;
  36947. rect.contentH = rect.minH - contLayoutRect.deltaH;
  36948. }
  36949. var flexV;
  36950. if (settings.packV == 'start') {
  36951. flexV = 0;
  36952. } else {
  36953. flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0;
  36954. }
  36955. // Calculate totalFlex
  36956. var totalFlex = 0;
  36957. var flexWidths = settings.flexWidths;
  36958. if (flexWidths) {
  36959. for (x = 0; x < flexWidths.length; x++) {
  36960. totalFlex += flexWidths[x];
  36961. }
  36962. } else {
  36963. totalFlex = cols;
  36964. }
  36965. // Calculate new column widths based on flex values
  36966. var ratio = availableWidth / totalFlex;
  36967. for (x = 0; x < cols; x++) {
  36968. colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio;
  36969. }
  36970. // Move/resize controls
  36971. posY = contPaddingBox.top;
  36972. for (y = 0; y < rows; y++) {
  36973. posX = contPaddingBox.left;
  36974. height = rowHeights[y] + flexV;
  36975. for (x = 0; x < cols; x++) {
  36976. if (reverseRows) {
  36977. idx = y * cols + cols - 1 - x;
  36978. } else {
  36979. idx = y * cols + x;
  36980. }
  36981. ctrl = items[idx];
  36982. // No more controls to render then break
  36983. if (!ctrl) {
  36984. break;
  36985. }
  36986. // Get control settings and calculate x, y
  36987. ctrlSettings = ctrl.settings;
  36988. ctrlLayoutRect = ctrl.layoutRect();
  36989. width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth);
  36990. ctrlLayoutRect.x = posX;
  36991. ctrlLayoutRect.y = posY;
  36992. // Align control horizontal
  36993. align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null);
  36994. if (align == "center") {
  36995. ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2);
  36996. } else if (align == "right") {
  36997. ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w;
  36998. } else if (align == "stretch") {
  36999. ctrlLayoutRect.w = width;
  37000. }
  37001. // Align control vertical
  37002. align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null);
  37003. if (align == "center") {
  37004. ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2);
  37005. } else if (align == "bottom") {
  37006. ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h;
  37007. } else if (align == "stretch") {
  37008. ctrlLayoutRect.h = height;
  37009. }
  37010. ctrl.layoutRect(ctrlLayoutRect);
  37011. posX += width + spacingH;
  37012. if (ctrl.recalc) {
  37013. ctrl.recalc();
  37014. }
  37015. }
  37016. posY += height + spacingV;
  37017. }
  37018. }
  37019. });
  37020. });
  37021. // Included from: js/tinymce/classes/ui/Iframe.js
  37022. /**
  37023. * Iframe.js
  37024. *
  37025. * Released under LGPL License.
  37026. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  37027. *
  37028. * License: http://www.tinymce.com/license
  37029. * Contributing: http://www.tinymce.com/contributing
  37030. */
  37031. /*jshint scripturl:true */
  37032. /**
  37033. * This class creates an iframe.
  37034. *
  37035. * @setting {String} url Url to open in the iframe.
  37036. *
  37037. * @-x-less Iframe.less
  37038. * @class tinymce.ui.Iframe
  37039. * @extends tinymce.ui.Widget
  37040. */
  37041. define("tinymce/ui/Iframe", [
  37042. "tinymce/ui/Widget",
  37043. "tinymce/util/Delay"
  37044. ], function(Widget, Delay) {
  37045. "use strict";
  37046. return Widget.extend({
  37047. /**
  37048. * Renders the control as a HTML string.
  37049. *
  37050. * @method renderHtml
  37051. * @return {String} HTML representing the control.
  37052. */
  37053. renderHtml: function() {
  37054. var self = this;
  37055. self.classes.add('iframe');
  37056. self.canFocus = false;
  37057. /*eslint no-script-url:0 */
  37058. return (
  37059. '<iframe id="' + self._id + '" class="' + self.classes + '" tabindex="-1" src="' +
  37060. (self.settings.url || "javascript:''") + '" frameborder="0"></iframe>'
  37061. );
  37062. },
  37063. /**
  37064. * Setter for the iframe source.
  37065. *
  37066. * @method src
  37067. * @param {String} src Source URL for iframe.
  37068. */
  37069. src: function(src) {
  37070. this.getEl().src = src;
  37071. },
  37072. /**
  37073. * Inner HTML for the iframe.
  37074. *
  37075. * @method html
  37076. * @param {String} html HTML string to set as HTML inside the iframe.
  37077. * @param {function} callback Optional callback to execute when the iframe body is filled with contents.
  37078. * @return {tinymce.ui.Iframe} Current iframe control.
  37079. */
  37080. html: function(html, callback) {
  37081. var self = this, body = this.getEl().contentWindow.document.body;
  37082. // Wait for iframe to initialize IE 10 takes time
  37083. if (!body) {
  37084. Delay.setTimeout(function() {
  37085. self.html(html);
  37086. });
  37087. } else {
  37088. body.innerHTML = html;
  37089. if (callback) {
  37090. callback();
  37091. }
  37092. }
  37093. return this;
  37094. }
  37095. });
  37096. });
  37097. // Included from: js/tinymce/classes/ui/InfoBox.js
  37098. /**
  37099. * InfoBox.js
  37100. *
  37101. * Released under LGPL License.
  37102. * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
  37103. *
  37104. * License: http://www.tinymce.com/license
  37105. * Contributing: http://www.tinymce.com/contributing
  37106. */
  37107. /**
  37108. * ....
  37109. *
  37110. * @-x-less InfoBox.less
  37111. * @class tinymce.ui.InfoBox
  37112. * @extends tinymce.ui.Widget
  37113. */
  37114. define("tinymce/ui/InfoBox", [
  37115. "tinymce/ui/Widget"
  37116. ], function(Widget) {
  37117. "use strict";
  37118. return Widget.extend({
  37119. /**
  37120. * Constructs a instance with the specified settings.
  37121. *
  37122. * @constructor
  37123. * @param {Object} settings Name/value object with settings.
  37124. * @setting {Boolean} multiline Multiline label.
  37125. */
  37126. init: function(settings) {
  37127. var self = this;
  37128. self._super(settings);
  37129. self.classes.add('widget').add('infobox');
  37130. self.canFocus = false;
  37131. },
  37132. severity: function(level) {
  37133. this.classes.remove('error');
  37134. this.classes.remove('warning');
  37135. this.classes.remove('success');
  37136. this.classes.add(level);
  37137. },
  37138. help: function(state) {
  37139. this.state.set('help', state);
  37140. },
  37141. /**
  37142. * Renders the control as a HTML string.
  37143. *
  37144. * @method renderHtml
  37145. * @return {String} HTML representing the control.
  37146. */
  37147. renderHtml: function() {
  37148. var self = this, prefix = self.classPrefix;
  37149. return (
  37150. '<div id="' + self._id + '" class="' + self.classes + '">' +
  37151. '<div id="' + self._id + '-body">' +
  37152. self.encode(self.state.get('text')) +
  37153. '<button role="button" tabindex="-1">' +
  37154. '<i class="' + prefix + 'ico ' + prefix + 'i-help"></i>' +
  37155. '</button>' +
  37156. '</div>' +
  37157. '</div>'
  37158. );
  37159. },
  37160. bindStates: function() {
  37161. var self = this;
  37162. self.state.on('change:text', function(e) {
  37163. self.getEl('body').firstChild.data = self.encode(e.value);
  37164. if (self.state.get('rendered')) {
  37165. self.updateLayoutRect();
  37166. }
  37167. });
  37168. self.state.on('change:help', function(e) {
  37169. self.classes.toggle('has-help', e.value);
  37170. if (self.state.get('rendered')) {
  37171. self.updateLayoutRect();
  37172. }
  37173. });
  37174. return self._super();
  37175. }
  37176. });
  37177. });
  37178. // Included from: js/tinymce/classes/ui/Label.js
  37179. /**
  37180. * Label.js
  37181. *
  37182. * Released under LGPL License.
  37183. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  37184. *
  37185. * License: http://www.tinymce.com/license
  37186. * Contributing: http://www.tinymce.com/contributing
  37187. */
  37188. /**
  37189. * This class creates a label element. A label is a simple text control
  37190. * that can be bound to other controls.
  37191. *
  37192. * @-x-less Label.less
  37193. * @class tinymce.ui.Label
  37194. * @extends tinymce.ui.Widget
  37195. */
  37196. define("tinymce/ui/Label", [
  37197. "tinymce/ui/Widget",
  37198. "tinymce/ui/DomUtils"
  37199. ], function(Widget, DomUtils) {
  37200. "use strict";
  37201. return Widget.extend({
  37202. /**
  37203. * Constructs a instance with the specified settings.
  37204. *
  37205. * @constructor
  37206. * @param {Object} settings Name/value object with settings.
  37207. * @setting {Boolean} multiline Multiline label.
  37208. */
  37209. init: function(settings) {
  37210. var self = this;
  37211. self._super(settings);
  37212. self.classes.add('widget').add('label');
  37213. self.canFocus = false;
  37214. if (settings.multiline) {
  37215. self.classes.add('autoscroll');
  37216. }
  37217. if (settings.strong) {
  37218. self.classes.add('strong');
  37219. }
  37220. },
  37221. /**
  37222. * Initializes the current controls layout rect.
  37223. * This will be executed by the layout managers to determine the
  37224. * default minWidth/minHeight etc.
  37225. *
  37226. * @method initLayoutRect
  37227. * @return {Object} Layout rect instance.
  37228. */
  37229. initLayoutRect: function() {
  37230. var self = this, layoutRect = self._super();
  37231. if (self.settings.multiline) {
  37232. var size = DomUtils.getSize(self.getEl());
  37233. // Check if the text fits within maxW if not then try word wrapping it
  37234. if (size.width > layoutRect.maxW) {
  37235. layoutRect.minW = layoutRect.maxW;
  37236. self.classes.add('multiline');
  37237. }
  37238. self.getEl().style.width = layoutRect.minW + 'px';
  37239. layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height);
  37240. }
  37241. return layoutRect;
  37242. },
  37243. /**
  37244. * Repaints the control after a layout operation.
  37245. *
  37246. * @method repaint
  37247. */
  37248. repaint: function() {
  37249. var self = this;
  37250. if (!self.settings.multiline) {
  37251. self.getEl().style.lineHeight = self.layoutRect().h + 'px';
  37252. }
  37253. return self._super();
  37254. },
  37255. severity: function(level) {
  37256. this.classes.remove('error');
  37257. this.classes.remove('warning');
  37258. this.classes.remove('success');
  37259. this.classes.add(level);
  37260. },
  37261. /**
  37262. * Renders the control as a HTML string.
  37263. *
  37264. * @method renderHtml
  37265. * @return {String} HTML representing the control.
  37266. */
  37267. renderHtml: function() {
  37268. var self = this, targetCtrl, forName, forId = self.settings.forId;
  37269. if (!forId && (forName = self.settings.forName)) {
  37270. targetCtrl = self.getRoot().find('#' + forName)[0];
  37271. if (targetCtrl) {
  37272. forId = targetCtrl._id;
  37273. }
  37274. }
  37275. if (forId) {
  37276. return (
  37277. '<label id="' + self._id + '" class="' + self.classes + '"' + (forId ? ' for="' + forId + '"' : '') + '>' +
  37278. self.encode(self.state.get('text')) +
  37279. '</label>'
  37280. );
  37281. }
  37282. return (
  37283. '<span id="' + self._id + '" class="' + self.classes + '">' +
  37284. self.encode(self.state.get('text')) +
  37285. '</span>'
  37286. );
  37287. },
  37288. bindStates: function() {
  37289. var self = this;
  37290. self.state.on('change:text', function(e) {
  37291. self.innerHtml(self.encode(e.value));
  37292. if (self.state.get('rendered')) {
  37293. self.updateLayoutRect();
  37294. }
  37295. });
  37296. return self._super();
  37297. }
  37298. });
  37299. });
  37300. // Included from: js/tinymce/classes/ui/Toolbar.js
  37301. /**
  37302. * Toolbar.js
  37303. *
  37304. * Released under LGPL License.
  37305. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  37306. *
  37307. * License: http://www.tinymce.com/license
  37308. * Contributing: http://www.tinymce.com/contributing
  37309. */
  37310. /**
  37311. * Creates a new toolbar.
  37312. *
  37313. * @class tinymce.ui.Toolbar
  37314. * @extends tinymce.ui.Container
  37315. */
  37316. define("tinymce/ui/Toolbar", [
  37317. "tinymce/ui/Container"
  37318. ], function(Container) {
  37319. "use strict";
  37320. return Container.extend({
  37321. Defaults: {
  37322. role: 'toolbar',
  37323. layout: 'flow'
  37324. },
  37325. /**
  37326. * Constructs a instance with the specified settings.
  37327. *
  37328. * @constructor
  37329. * @param {Object} settings Name/value object with settings.
  37330. */
  37331. init: function(settings) {
  37332. var self = this;
  37333. self._super(settings);
  37334. self.classes.add('toolbar');
  37335. },
  37336. /**
  37337. * Called after the control has been rendered.
  37338. *
  37339. * @method postRender
  37340. */
  37341. postRender: function() {
  37342. var self = this;
  37343. self.items().each(function(ctrl) {
  37344. ctrl.classes.add('toolbar-item');
  37345. });
  37346. return self._super();
  37347. }
  37348. });
  37349. });
  37350. // Included from: js/tinymce/classes/ui/MenuBar.js
  37351. /**
  37352. * MenuBar.js
  37353. *
  37354. * Released under LGPL License.
  37355. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  37356. *
  37357. * License: http://www.tinymce.com/license
  37358. * Contributing: http://www.tinymce.com/contributing
  37359. */
  37360. /**
  37361. * Creates a new menubar.
  37362. *
  37363. * @-x-less MenuBar.less
  37364. * @class tinymce.ui.MenuBar
  37365. * @extends tinymce.ui.Container
  37366. */
  37367. define("tinymce/ui/MenuBar", [
  37368. "tinymce/ui/Toolbar"
  37369. ], function(Toolbar) {
  37370. "use strict";
  37371. return Toolbar.extend({
  37372. Defaults: {
  37373. role: 'menubar',
  37374. containerCls: 'menubar',
  37375. ariaRoot: true,
  37376. defaults: {
  37377. type: 'menubutton'
  37378. }
  37379. }
  37380. });
  37381. });
  37382. // Included from: js/tinymce/classes/ui/MenuButton.js
  37383. /**
  37384. * MenuButton.js
  37385. *
  37386. * Released under LGPL License.
  37387. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  37388. *
  37389. * License: http://www.tinymce.com/license
  37390. * Contributing: http://www.tinymce.com/contributing
  37391. */
  37392. /**
  37393. * Creates a new menu button.
  37394. *
  37395. * @-x-less MenuButton.less
  37396. * @class tinymce.ui.MenuButton
  37397. * @extends tinymce.ui.Button
  37398. */
  37399. define("tinymce/ui/MenuButton", [
  37400. "tinymce/ui/Button",
  37401. "tinymce/ui/Factory",
  37402. "tinymce/ui/MenuBar"
  37403. ], function(Button, Factory, MenuBar) {
  37404. "use strict";
  37405. // TODO: Maybe add as some global function
  37406. function isChildOf(node, parent) {
  37407. while (node) {
  37408. if (parent === node) {
  37409. return true;
  37410. }
  37411. node = node.parentNode;
  37412. }
  37413. return false;
  37414. }
  37415. var MenuButton = Button.extend({
  37416. /**
  37417. * Constructs a instance with the specified settings.
  37418. *
  37419. * @constructor
  37420. * @param {Object} settings Name/value object with settings.
  37421. */
  37422. init: function(settings) {
  37423. var self = this;
  37424. self._renderOpen = true;
  37425. self._super(settings);
  37426. settings = self.settings;
  37427. self.classes.add('menubtn');
  37428. if (settings.fixedWidth) {
  37429. self.classes.add('fixed-width');
  37430. }
  37431. self.aria('haspopup', true);
  37432. self.state.set('menu', settings.menu || self.render());
  37433. },
  37434. /**
  37435. * Shows the menu for the button.
  37436. *
  37437. * @method showMenu
  37438. */
  37439. showMenu: function() {
  37440. var self = this, menu;
  37441. if (self.menu && self.menu.visible()) {
  37442. return self.hideMenu();
  37443. }
  37444. if (!self.menu) {
  37445. menu = self.state.get('menu') || [];
  37446. // Is menu array then auto constuct menu control
  37447. if (menu.length) {
  37448. menu = {
  37449. type: 'menu',
  37450. items: menu
  37451. };
  37452. } else {
  37453. menu.type = menu.type || 'menu';
  37454. }
  37455. if (!menu.renderTo) {
  37456. self.menu = Factory.create(menu).parent(self).renderTo();
  37457. } else {
  37458. self.menu = menu.parent(self).show().renderTo();
  37459. }
  37460. self.fire('createmenu');
  37461. self.menu.reflow();
  37462. self.menu.on('cancel', function(e) {
  37463. if (e.control.parent() === self.menu) {
  37464. e.stopPropagation();
  37465. self.focus();
  37466. self.hideMenu();
  37467. }
  37468. });
  37469. // Move focus to button when a menu item is selected/clicked
  37470. self.menu.on('select', function() {
  37471. self.focus();
  37472. });
  37473. self.menu.on('show hide', function(e) {
  37474. if (e.control == self.menu) {
  37475. self.activeMenu(e.type == 'show');
  37476. }
  37477. self.aria('expanded', e.type == 'show');
  37478. }).fire('show');
  37479. }
  37480. self.menu.show();
  37481. self.menu.layoutRect({w: self.layoutRect().w});
  37482. self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
  37483. },
  37484. /**
  37485. * Hides the menu for the button.
  37486. *
  37487. * @method hideMenu
  37488. */
  37489. hideMenu: function() {
  37490. var self = this;
  37491. if (self.menu) {
  37492. self.menu.items().each(function(item) {
  37493. if (item.hideMenu) {
  37494. item.hideMenu();
  37495. }
  37496. });
  37497. self.menu.hide();
  37498. }
  37499. },
  37500. /**
  37501. * Sets the active menu state.
  37502. *
  37503. * @private
  37504. */
  37505. activeMenu: function(state) {
  37506. this.classes.toggle('active', state);
  37507. },
  37508. /**
  37509. * Renders the control as a HTML string.
  37510. *
  37511. * @method renderHtml
  37512. * @return {String} HTML representing the control.
  37513. */
  37514. renderHtml: function() {
  37515. var self = this, id = self._id, prefix = self.classPrefix;
  37516. var icon = self.settings.icon, image, text = self.state.get('text'),
  37517. textHtml = '';
  37518. image = self.settings.image;
  37519. if (image) {
  37520. icon = 'none';
  37521. // Support for [high dpi, low dpi] image sources
  37522. if (typeof image != "string") {
  37523. image = window.getSelection ? image[0] : image[1];
  37524. }
  37525. image = ' style="background-image: url(\'' + image + '\')"';
  37526. } else {
  37527. image = '';
  37528. }
  37529. if (text) {
  37530. self.classes.add('btn-has-text');
  37531. textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>';
  37532. }
  37533. icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
  37534. self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');
  37535. return (
  37536. '<div id="' + id + '" class="' + self.classes + '" tabindex="-1" aria-labelledby="' + id + '">' +
  37537. '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
  37538. (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  37539. textHtml +
  37540. ' <i class="' + prefix + 'caret"></i>' +
  37541. '</button>' +
  37542. '</div>'
  37543. );
  37544. },
  37545. /**
  37546. * Gets invoked after the control has been rendered.
  37547. *
  37548. * @method postRender
  37549. */
  37550. postRender: function() {
  37551. var self = this;
  37552. self.on('click', function(e) {
  37553. if (e.control === self && isChildOf(e.target, self.getEl())) {
  37554. self.showMenu();
  37555. if (e.aria) {
  37556. self.menu.items()[0].focus();
  37557. }
  37558. }
  37559. });
  37560. self.on('mouseenter', function(e) {
  37561. var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu;
  37562. if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) {
  37563. parent.items().filter('MenuButton').each(function(ctrl) {
  37564. if (ctrl.hideMenu && ctrl != overCtrl) {
  37565. if (ctrl.menu && ctrl.menu.visible()) {
  37566. hasVisibleSiblingMenu = true;
  37567. }
  37568. ctrl.hideMenu();
  37569. }
  37570. });
  37571. if (hasVisibleSiblingMenu) {
  37572. overCtrl.focus(); // Fix for: #5887
  37573. overCtrl.showMenu();
  37574. }
  37575. }
  37576. });
  37577. return self._super();
  37578. },
  37579. bindStates: function() {
  37580. var self = this;
  37581. self.state.on('change:menu', function() {
  37582. if (self.menu) {
  37583. self.menu.remove();
  37584. }
  37585. self.menu = null;
  37586. });
  37587. return self._super();
  37588. },
  37589. /**
  37590. * Removes the control and it's menus.
  37591. *
  37592. * @method remove
  37593. */
  37594. remove: function() {
  37595. this._super();
  37596. if (this.menu) {
  37597. this.menu.remove();
  37598. }
  37599. }
  37600. });
  37601. return MenuButton;
  37602. });
  37603. // Included from: js/tinymce/classes/ui/MenuItem.js
  37604. /**
  37605. * MenuItem.js
  37606. *
  37607. * Released under LGPL License.
  37608. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  37609. *
  37610. * License: http://www.tinymce.com/license
  37611. * Contributing: http://www.tinymce.com/contributing
  37612. */
  37613. /**
  37614. * Creates a new menu item.
  37615. *
  37616. * @-x-less MenuItem.less
  37617. * @class tinymce.ui.MenuItem
  37618. * @extends tinymce.ui.Control
  37619. */
  37620. define("tinymce/ui/MenuItem", [
  37621. "tinymce/ui/Widget",
  37622. "tinymce/ui/Factory",
  37623. "tinymce/Env",
  37624. "tinymce/util/Delay"
  37625. ], function(Widget, Factory, Env, Delay) {
  37626. "use strict";
  37627. return Widget.extend({
  37628. Defaults: {
  37629. border: 0,
  37630. role: 'menuitem'
  37631. },
  37632. /**
  37633. * Constructs a instance with the specified settings.
  37634. *
  37635. * @constructor
  37636. * @param {Object} settings Name/value object with settings.
  37637. * @setting {Boolean} selectable Selectable menu.
  37638. * @setting {Array} menu Submenu array with items.
  37639. * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X
  37640. */
  37641. init: function(settings) {
  37642. var self = this, text;
  37643. self._super(settings);
  37644. settings = self.settings;
  37645. self.classes.add('menu-item');
  37646. if (settings.menu) {
  37647. self.classes.add('menu-item-expand');
  37648. }
  37649. if (settings.preview) {
  37650. self.classes.add('menu-item-preview');
  37651. }
  37652. text = self.state.get('text');
  37653. if (text === '-' || text === '|') {
  37654. self.classes.add('menu-item-sep');
  37655. self.aria('role', 'separator');
  37656. self.state.set('text', '-');
  37657. }
  37658. if (settings.selectable) {
  37659. self.aria('role', 'menuitemcheckbox');
  37660. self.classes.add('menu-item-checkbox');
  37661. settings.icon = 'selected';
  37662. }
  37663. if (!settings.preview && !settings.selectable) {
  37664. self.classes.add('menu-item-normal');
  37665. }
  37666. self.on('mousedown', function(e) {
  37667. e.preventDefault();
  37668. });
  37669. if (settings.menu && !settings.ariaHideMenu) {
  37670. self.aria('haspopup', true);
  37671. }
  37672. },
  37673. /**
  37674. * Returns true/false if the menuitem has sub menu.
  37675. *
  37676. * @method hasMenus
  37677. * @return {Boolean} True/false state if it has submenu.
  37678. */
  37679. hasMenus: function() {
  37680. return !!this.settings.menu;
  37681. },
  37682. /**
  37683. * Shows the menu for the menu item.
  37684. *
  37685. * @method showMenu
  37686. */
  37687. showMenu: function() {
  37688. var self = this, settings = self.settings, menu, parent = self.parent();
  37689. parent.items().each(function(ctrl) {
  37690. if (ctrl !== self) {
  37691. ctrl.hideMenu();
  37692. }
  37693. });
  37694. if (settings.menu) {
  37695. menu = self.menu;
  37696. if (!menu) {
  37697. menu = settings.menu;
  37698. // Is menu array then auto constuct menu control
  37699. if (menu.length) {
  37700. menu = {
  37701. type: 'menu',
  37702. items: menu
  37703. };
  37704. } else {
  37705. menu.type = menu.type || 'menu';
  37706. }
  37707. if (parent.settings.itemDefaults) {
  37708. menu.itemDefaults = parent.settings.itemDefaults;
  37709. }
  37710. menu = self.menu = Factory.create(menu).parent(self).renderTo();
  37711. menu.reflow();
  37712. menu.on('cancel', function(e) {
  37713. e.stopPropagation();
  37714. self.focus();
  37715. menu.hide();
  37716. });
  37717. menu.on('show hide', function(e) {
  37718. e.control.items().each(function(ctrl) {
  37719. ctrl.active(ctrl.settings.selected);
  37720. });
  37721. }).fire('show');
  37722. menu.on('hide', function(e) {
  37723. if (e.control === menu) {
  37724. self.classes.remove('selected');
  37725. }
  37726. });
  37727. menu.submenu = true;
  37728. } else {
  37729. menu.show();
  37730. }
  37731. menu._parentMenu = parent;
  37732. menu.classes.add('menu-sub');
  37733. var rel = menu.testMoveRel(
  37734. self.getEl(),
  37735. self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br']
  37736. );
  37737. menu.moveRel(self.getEl(), rel);
  37738. menu.rel = rel;
  37739. rel = 'menu-sub-' + rel;
  37740. menu.classes.remove(menu._lastRel).add(rel);
  37741. menu._lastRel = rel;
  37742. self.classes.add('selected');
  37743. self.aria('expanded', true);
  37744. }
  37745. },
  37746. /**
  37747. * Hides the menu for the menu item.
  37748. *
  37749. * @method hideMenu
  37750. */
  37751. hideMenu: function() {
  37752. var self = this;
  37753. if (self.menu) {
  37754. self.menu.items().each(function(item) {
  37755. if (item.hideMenu) {
  37756. item.hideMenu();
  37757. }
  37758. });
  37759. self.menu.hide();
  37760. self.aria('expanded', false);
  37761. }
  37762. return self;
  37763. },
  37764. /**
  37765. * Renders the control as a HTML string.
  37766. *
  37767. * @method renderHtml
  37768. * @return {String} HTML representing the control.
  37769. */
  37770. renderHtml: function() {
  37771. var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self.state.get('text'));
  37772. var icon = self.settings.icon, image = '', shortcut = settings.shortcut;
  37773. // Converts shortcut format to Mac/PC variants
  37774. function convertShortcut(shortcut) {
  37775. var i, value, replace = {};
  37776. if (Env.mac) {
  37777. replace = {
  37778. alt: '&#x2325;',
  37779. ctrl: '&#x2318;',
  37780. shift: '&#x21E7;',
  37781. meta: '&#x2318;'
  37782. };
  37783. } else {
  37784. replace = {
  37785. meta: 'Ctrl'
  37786. };
  37787. }
  37788. shortcut = shortcut.split('+');
  37789. for (i = 0; i < shortcut.length; i++) {
  37790. value = replace[shortcut[i].toLowerCase()];
  37791. if (value) {
  37792. shortcut[i] = value;
  37793. }
  37794. }
  37795. return shortcut.join('+');
  37796. }
  37797. if (icon) {
  37798. self.parent().classes.add('menu-has-icons');
  37799. }
  37800. if (settings.image) {
  37801. image = ' style="background-image: url(\'' + settings.image + '\')"';
  37802. }
  37803. if (shortcut) {
  37804. shortcut = convertShortcut(shortcut);
  37805. }
  37806. icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');
  37807. return (
  37808. '<div id="' + id + '" class="' + self.classes + '" tabindex="-1">' +
  37809. (text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') +
  37810. (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
  37811. (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
  37812. (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
  37813. '</div>'
  37814. );
  37815. },
  37816. /**
  37817. * Gets invoked after the control has been rendered.
  37818. *
  37819. * @method postRender
  37820. */
  37821. postRender: function() {
  37822. var self = this, settings = self.settings;
  37823. var textStyle = settings.textStyle;
  37824. if (typeof textStyle == "function") {
  37825. textStyle = textStyle.call(this);
  37826. }
  37827. if (textStyle) {
  37828. var textElm = self.getEl('text');
  37829. if (textElm) {
  37830. textElm.setAttribute('style', textStyle);
  37831. }
  37832. }
  37833. self.on('mouseenter click', function(e) {
  37834. if (e.control === self) {
  37835. if (!settings.menu && e.type === 'click') {
  37836. self.fire('select');
  37837. // Edge will crash if you stress it see #2660
  37838. Delay.requestAnimationFrame(function() {
  37839. self.parent().hideAll();
  37840. });
  37841. } else {
  37842. self.showMenu();
  37843. if (e.aria) {
  37844. self.menu.focus(true);
  37845. }
  37846. }
  37847. }
  37848. });
  37849. self._super();
  37850. return self;
  37851. },
  37852. hover: function() {
  37853. var self = this;
  37854. self.parent().items().each(function(ctrl) {
  37855. ctrl.classes.remove('selected');
  37856. });
  37857. self.classes.toggle('selected', true);
  37858. return self;
  37859. },
  37860. active: function(state) {
  37861. if (typeof state != "undefined") {
  37862. this.aria('checked', state);
  37863. }
  37864. return this._super(state);
  37865. },
  37866. /**
  37867. * Removes the control and it's menus.
  37868. *
  37869. * @method remove
  37870. */
  37871. remove: function() {
  37872. this._super();
  37873. if (this.menu) {
  37874. this.menu.remove();
  37875. }
  37876. }
  37877. });
  37878. });
  37879. // Included from: js/tinymce/classes/ui/Throbber.js
  37880. /**
  37881. * Throbber.js
  37882. *
  37883. * Released under LGPL License.
  37884. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  37885. *
  37886. * License: http://www.tinymce.com/license
  37887. * Contributing: http://www.tinymce.com/contributing
  37888. */
  37889. /**
  37890. * This class enables you to display a Throbber for any element.
  37891. *
  37892. * @-x-less Throbber.less
  37893. * @class tinymce.ui.Throbber
  37894. */
  37895. define("tinymce/ui/Throbber", [
  37896. "tinymce/dom/DomQuery",
  37897. "tinymce/ui/Control",
  37898. "tinymce/util/Delay"
  37899. ], function($, Control, Delay) {
  37900. "use strict";
  37901. /**
  37902. * Constructs a new throbber.
  37903. *
  37904. * @constructor
  37905. * @param {Element} elm DOM Html element to display throbber in.
  37906. * @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll.
  37907. */
  37908. return function(elm, inline) {
  37909. var self = this, state, classPrefix = Control.classPrefix, timer;
  37910. /**
  37911. * Shows the throbber.
  37912. *
  37913. * @method show
  37914. * @param {Number} [time] Time to wait before showing.
  37915. * @param {function} [callback] Optional callback to execute when the throbber is shown.
  37916. * @return {tinymce.ui.Throbber} Current throbber instance.
  37917. */
  37918. self.show = function(time, callback) {
  37919. function render() {
  37920. if (state) {
  37921. $(elm).append(
  37922. '<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>'
  37923. );
  37924. if (callback) {
  37925. callback();
  37926. }
  37927. }
  37928. }
  37929. self.hide();
  37930. state = true;
  37931. if (time) {
  37932. timer = Delay.setTimeout(render, time);
  37933. } else {
  37934. render();
  37935. }
  37936. return self;
  37937. };
  37938. /**
  37939. * Hides the throbber.
  37940. *
  37941. * @method hide
  37942. * @return {tinymce.ui.Throbber} Current throbber instance.
  37943. */
  37944. self.hide = function() {
  37945. var child = elm.lastChild;
  37946. Delay.clearTimeout(timer);
  37947. if (child && child.className.indexOf('throbber') != -1) {
  37948. child.parentNode.removeChild(child);
  37949. }
  37950. state = false;
  37951. return self;
  37952. };
  37953. };
  37954. });
  37955. // Included from: js/tinymce/classes/ui/Menu.js
  37956. /**
  37957. * Menu.js
  37958. *
  37959. * Released under LGPL License.
  37960. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  37961. *
  37962. * License: http://www.tinymce.com/license
  37963. * Contributing: http://www.tinymce.com/contributing
  37964. */
  37965. /**
  37966. * Creates a new menu.
  37967. *
  37968. * @-x-less Menu.less
  37969. * @class tinymce.ui.Menu
  37970. * @extends tinymce.ui.FloatPanel
  37971. */
  37972. define("tinymce/ui/Menu", [
  37973. "tinymce/ui/FloatPanel",
  37974. "tinymce/ui/MenuItem",
  37975. "tinymce/ui/Throbber",
  37976. "tinymce/util/Tools"
  37977. ], function(FloatPanel, MenuItem, Throbber, Tools) {
  37978. "use strict";
  37979. return FloatPanel.extend({
  37980. Defaults: {
  37981. defaultType: 'menuitem',
  37982. border: 1,
  37983. layout: 'stack',
  37984. role: 'application',
  37985. bodyRole: 'menu',
  37986. ariaRoot: true
  37987. },
  37988. /**
  37989. * Constructs a instance with the specified settings.
  37990. *
  37991. * @constructor
  37992. * @param {Object} settings Name/value object with settings.
  37993. */
  37994. init: function(settings) {
  37995. var self = this;
  37996. settings.autohide = true;
  37997. settings.constrainToViewport = true;
  37998. if (typeof settings.items === 'function') {
  37999. settings.itemsFactory = settings.items;
  38000. settings.items = [];
  38001. }
  38002. if (settings.itemDefaults) {
  38003. var items = settings.items, i = items.length;
  38004. while (i--) {
  38005. items[i] = Tools.extend({}, settings.itemDefaults, items[i]);
  38006. }
  38007. }
  38008. self._super(settings);
  38009. self.classes.add('menu');
  38010. },
  38011. /**
  38012. * Repaints the control after a layout operation.
  38013. *
  38014. * @method repaint
  38015. */
  38016. repaint: function() {
  38017. this.classes.toggle('menu-align', true);
  38018. this._super();
  38019. this.getEl().style.height = '';
  38020. this.getEl('body').style.height = '';
  38021. return this;
  38022. },
  38023. /**
  38024. * Hides/closes the menu.
  38025. *
  38026. * @method cancel
  38027. */
  38028. cancel: function() {
  38029. var self = this;
  38030. self.hideAll();
  38031. self.fire('select');
  38032. },
  38033. /**
  38034. * Loads new items from the factory items function.
  38035. *
  38036. * @method load
  38037. */
  38038. load: function() {
  38039. var self = this, time, factory;
  38040. function hideThrobber() {
  38041. if (self.throbber) {
  38042. self.throbber.hide();
  38043. self.throbber = null;
  38044. }
  38045. }
  38046. factory = self.settings.itemsFactory;
  38047. if (!factory) {
  38048. return;
  38049. }
  38050. if (!self.throbber) {
  38051. self.throbber = new Throbber(self.getEl('body'), true);
  38052. if (self.items().length === 0) {
  38053. self.throbber.show();
  38054. self.fire('loading');
  38055. } else {
  38056. self.throbber.show(100, function() {
  38057. self.items().remove();
  38058. self.fire('loading');
  38059. });
  38060. }
  38061. self.on('hide close', hideThrobber);
  38062. }
  38063. self.requestTime = time = new Date().getTime();
  38064. self.settings.itemsFactory(function(items) {
  38065. if (items.length === 0) {
  38066. self.hide();
  38067. return;
  38068. }
  38069. if (self.requestTime !== time) {
  38070. return;
  38071. }
  38072. self.getEl().style.width = '';
  38073. self.getEl('body').style.width = '';
  38074. hideThrobber();
  38075. self.items().remove();
  38076. self.getEl('body').innerHTML = '';
  38077. self.add(items);
  38078. self.renderNew();
  38079. self.fire('loaded');
  38080. });
  38081. },
  38082. /**
  38083. * Hide menu and all sub menus.
  38084. *
  38085. * @method hideAll
  38086. */
  38087. hideAll: function() {
  38088. var self = this;
  38089. this.find('menuitem').exec('hideMenu');
  38090. return self._super();
  38091. },
  38092. /**
  38093. * Invoked before the menu is rendered.
  38094. *
  38095. * @method preRender
  38096. */
  38097. preRender: function() {
  38098. var self = this;
  38099. self.items().each(function(ctrl) {
  38100. var settings = ctrl.settings;
  38101. if (settings.icon || settings.image || settings.selectable) {
  38102. self._hasIcons = true;
  38103. return false;
  38104. }
  38105. });
  38106. if (self.settings.itemsFactory) {
  38107. self.on('postrender', function() {
  38108. if (self.settings.itemsFactory) {
  38109. self.load();
  38110. }
  38111. });
  38112. }
  38113. return self._super();
  38114. }
  38115. });
  38116. });
  38117. // Included from: js/tinymce/classes/ui/ListBox.js
  38118. /**
  38119. * ListBox.js
  38120. *
  38121. * Released under LGPL License.
  38122. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  38123. *
  38124. * License: http://www.tinymce.com/license
  38125. * Contributing: http://www.tinymce.com/contributing
  38126. */
  38127. /**
  38128. * Creates a new list box control.
  38129. *
  38130. * @-x-less ListBox.less
  38131. * @class tinymce.ui.ListBox
  38132. * @extends tinymce.ui.MenuButton
  38133. */
  38134. define("tinymce/ui/ListBox", [
  38135. "tinymce/ui/MenuButton",
  38136. "tinymce/ui/Menu"
  38137. ], function(MenuButton, Menu) {
  38138. "use strict";
  38139. return MenuButton.extend({
  38140. /**
  38141. * Constructs a instance with the specified settings.
  38142. *
  38143. * @constructor
  38144. * @param {Object} settings Name/value object with settings.
  38145. * @setting {Array} values Array with values to add to list box.
  38146. */
  38147. init: function(settings) {
  38148. var self = this, values, selected, selectedText, lastItemCtrl;
  38149. function setSelected(menuValues) {
  38150. // Try to find a selected value
  38151. for (var i = 0; i < menuValues.length; i++) {
  38152. selected = menuValues[i].selected || settings.value === menuValues[i].value;
  38153. if (selected) {
  38154. selectedText = selectedText || menuValues[i].text;
  38155. self.state.set('value', menuValues[i].value);
  38156. return true;
  38157. }
  38158. // If the value has a submenu, try to find the selected values in that menu
  38159. if (menuValues[i].menu) {
  38160. if (setSelected(menuValues[i].menu)) {
  38161. return true;
  38162. }
  38163. }
  38164. }
  38165. }
  38166. self._super(settings);
  38167. settings = self.settings;
  38168. self._values = values = settings.values;
  38169. if (values) {
  38170. if (typeof settings.value != "undefined") {
  38171. setSelected(values);
  38172. }
  38173. // Default with first item
  38174. if (!selected && values.length > 0) {
  38175. selectedText = values[0].text;
  38176. self.state.set('value', values[0].value);
  38177. }
  38178. self.state.set('menu', values);
  38179. }
  38180. self.state.set('text', settings.text || selectedText);
  38181. self.classes.add('listbox');
  38182. self.on('select', function(e) {
  38183. var ctrl = e.control;
  38184. if (lastItemCtrl) {
  38185. e.lastControl = lastItemCtrl;
  38186. }
  38187. if (settings.multiple) {
  38188. ctrl.active(!ctrl.active());
  38189. } else {
  38190. self.value(e.control.value());
  38191. }
  38192. lastItemCtrl = ctrl;
  38193. });
  38194. },
  38195. /**
  38196. * Getter/setter function for the control value.
  38197. *
  38198. * @method value
  38199. * @param {String} [value] Value to be set.
  38200. * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
  38201. */
  38202. bindStates: function() {
  38203. var self = this;
  38204. function activateMenuItemsByValue(menu, value) {
  38205. if (menu instanceof Menu) {
  38206. menu.items().each(function(ctrl) {
  38207. if (!ctrl.hasMenus()) {
  38208. ctrl.active(ctrl.value() === value);
  38209. }
  38210. });
  38211. }
  38212. }
  38213. function getSelectedItem(menuValues, value) {
  38214. var selectedItem;
  38215. if (!menuValues) {
  38216. return;
  38217. }
  38218. for (var i = 0; i < menuValues.length; i++) {
  38219. if (menuValues[i].value === value) {
  38220. return menuValues[i];
  38221. }
  38222. if (menuValues[i].menu) {
  38223. selectedItem = getSelectedItem(menuValues[i].menu, value);
  38224. if (selectedItem) {
  38225. return selectedItem;
  38226. }
  38227. }
  38228. }
  38229. }
  38230. self.on('show', function(e) {
  38231. activateMenuItemsByValue(e.control, self.value());
  38232. });
  38233. self.state.on('change:value', function(e) {
  38234. var selectedItem = getSelectedItem(self.state.get('menu'), e.value);
  38235. if (selectedItem) {
  38236. self.text(selectedItem.text);
  38237. } else {
  38238. self.text(self.settings.text);
  38239. }
  38240. });
  38241. return self._super();
  38242. }
  38243. });
  38244. });
  38245. // Included from: js/tinymce/classes/ui/Radio.js
  38246. /**
  38247. * Radio.js
  38248. *
  38249. * Released under LGPL License.
  38250. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  38251. *
  38252. * License: http://www.tinymce.com/license
  38253. * Contributing: http://www.tinymce.com/contributing
  38254. */
  38255. /**
  38256. * Creates a new radio button.
  38257. *
  38258. * @-x-less Radio.less
  38259. * @class tinymce.ui.Radio
  38260. * @extends tinymce.ui.Checkbox
  38261. */
  38262. define("tinymce/ui/Radio", [
  38263. "tinymce/ui/Checkbox"
  38264. ], function(Checkbox) {
  38265. "use strict";
  38266. return Checkbox.extend({
  38267. Defaults: {
  38268. classes: "radio",
  38269. role: "radio"
  38270. }
  38271. });
  38272. });
  38273. // Included from: js/tinymce/classes/ui/ResizeHandle.js
  38274. /**
  38275. * ResizeHandle.js
  38276. *
  38277. * Released under LGPL License.
  38278. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  38279. *
  38280. * License: http://www.tinymce.com/license
  38281. * Contributing: http://www.tinymce.com/contributing
  38282. */
  38283. /**
  38284. * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events.
  38285. *
  38286. * @-x-less ResizeHandle.less
  38287. * @class tinymce.ui.ResizeHandle
  38288. * @extends tinymce.ui.Widget
  38289. */
  38290. define("tinymce/ui/ResizeHandle", [
  38291. "tinymce/ui/Widget",
  38292. "tinymce/ui/DragHelper"
  38293. ], function(Widget, DragHelper) {
  38294. "use strict";
  38295. return Widget.extend({
  38296. /**
  38297. * Renders the control as a HTML string.
  38298. *
  38299. * @method renderHtml
  38300. * @return {String} HTML representing the control.
  38301. */
  38302. renderHtml: function() {
  38303. var self = this, prefix = self.classPrefix;
  38304. self.classes.add('resizehandle');
  38305. if (self.settings.direction == "both") {
  38306. self.classes.add('resizehandle-both');
  38307. }
  38308. self.canFocus = false;
  38309. return (
  38310. '<div id="' + self._id + '" class="' + self.classes + '">' +
  38311. '<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' +
  38312. '</div>'
  38313. );
  38314. },
  38315. /**
  38316. * Called after the control has been rendered.
  38317. *
  38318. * @method postRender
  38319. */
  38320. postRender: function() {
  38321. var self = this;
  38322. self._super();
  38323. self.resizeDragHelper = new DragHelper(this._id, {
  38324. start: function() {
  38325. self.fire('ResizeStart');
  38326. },
  38327. drag: function(e) {
  38328. if (self.settings.direction != "both") {
  38329. e.deltaX = 0;
  38330. }
  38331. self.fire('Resize', e);
  38332. },
  38333. stop: function() {
  38334. self.fire('ResizeEnd');
  38335. }
  38336. });
  38337. },
  38338. remove: function() {
  38339. if (this.resizeDragHelper) {
  38340. this.resizeDragHelper.destroy();
  38341. }
  38342. return this._super();
  38343. }
  38344. });
  38345. });
  38346. // Included from: js/tinymce/classes/ui/SelectBox.js
  38347. /**
  38348. * SelectBox.js
  38349. *
  38350. * Released under LGPL License.
  38351. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  38352. *
  38353. * License: http://www.tinymce.com/license
  38354. * Contributing: http://www.tinymce.com/contributing
  38355. */
  38356. /**
  38357. * Creates a new select box control.
  38358. *
  38359. * @-x-less SelectBox.less
  38360. * @class tinymce.ui.SelectBox
  38361. * @extends tinymce.ui.Widget
  38362. */
  38363. define("tinymce/ui/SelectBox", [
  38364. "tinymce/ui/Widget"
  38365. ], function(Widget) {
  38366. "use strict";
  38367. function createOptions(options) {
  38368. var strOptions = '';
  38369. if (options) {
  38370. for (var i = 0; i < options.length; i++) {
  38371. strOptions += '<option value="' + options[i] + '">' + options[i] + '</option>';
  38372. }
  38373. }
  38374. return strOptions;
  38375. }
  38376. return Widget.extend({
  38377. Defaults: {
  38378. classes: "selectbox",
  38379. role: "selectbox",
  38380. options: []
  38381. },
  38382. /**
  38383. * Constructs a instance with the specified settings.
  38384. *
  38385. * @constructor
  38386. * @param {Object} settings Name/value object with settings.
  38387. * @setting {Array} values Array with values to add to list box.
  38388. */
  38389. init: function(settings) {
  38390. var self = this;
  38391. self._super(settings);
  38392. if (self.settings.size) {
  38393. self.size = self.settings.size;
  38394. }
  38395. if (self.settings.options) {
  38396. self._options = self.settings.options;
  38397. }
  38398. self.on('keydown', function(e) {
  38399. var rootControl;
  38400. if (e.keyCode == 13) {
  38401. e.preventDefault();
  38402. // Find root control that we can do toJSON on
  38403. self.parents().reverse().each(function(ctrl) {
  38404. if (ctrl.toJSON) {
  38405. rootControl = ctrl;
  38406. return false;
  38407. }
  38408. });
  38409. // Fire event on current text box with the serialized data of the whole form
  38410. self.fire('submit', {data: rootControl.toJSON()});
  38411. }
  38412. });
  38413. },
  38414. /**
  38415. * Getter/setter function for the options state.
  38416. *
  38417. * @method options
  38418. * @param {Array} [state] State to be set.
  38419. * @return {Array|tinymce.ui.SelectBox} Array of string options.
  38420. */
  38421. options: function(state) {
  38422. if (!arguments.length) {
  38423. return this.state.get('options');
  38424. }
  38425. this.state.set('options', state);
  38426. return this;
  38427. },
  38428. renderHtml: function() {
  38429. var self = this, options, size = '';
  38430. options = createOptions(self._options);
  38431. if (self.size) {
  38432. size = ' size = "' + self.size + '"';
  38433. }
  38434. return (
  38435. '<select id="' + self._id + '" class="' + self.classes + '"' + size + '>' +
  38436. options +
  38437. '</select>'
  38438. );
  38439. },
  38440. bindStates: function() {
  38441. var self = this;
  38442. self.state.on('change:options', function(e) {
  38443. self.getEl().innerHTML = createOptions(e.value);
  38444. });
  38445. return self._super();
  38446. }
  38447. });
  38448. });
  38449. // Included from: js/tinymce/classes/ui/Slider.js
  38450. /**
  38451. * Slider.js
  38452. *
  38453. * Released under LGPL License.
  38454. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  38455. *
  38456. * License: http://www.tinymce.com/license
  38457. * Contributing: http://www.tinymce.com/contributing
  38458. */
  38459. /**
  38460. * Slider control.
  38461. *
  38462. * @-x-less Slider.less
  38463. * @class tinymce.ui.Slider
  38464. * @extends tinymce.ui.Widget
  38465. */
  38466. define("tinymce/ui/Slider", [
  38467. "tinymce/ui/Widget",
  38468. "tinymce/ui/DragHelper",
  38469. "tinymce/ui/DomUtils"
  38470. ], function(Widget, DragHelper, DomUtils) {
  38471. "use strict";
  38472. function constrain(value, minVal, maxVal) {
  38473. if (value < minVal) {
  38474. value = minVal;
  38475. }
  38476. if (value > maxVal) {
  38477. value = maxVal;
  38478. }
  38479. return value;
  38480. }
  38481. function setAriaProp(el, name, value) {
  38482. el.setAttribute('aria-' + name, value);
  38483. }
  38484. function updateSliderHandle(ctrl, value) {
  38485. var maxHandlePos, shortSizeName, sizeName, stylePosName, styleValue, handleEl;
  38486. if (ctrl.settings.orientation == "v") {
  38487. stylePosName = "top";
  38488. sizeName = "height";
  38489. shortSizeName = "h";
  38490. } else {
  38491. stylePosName = "left";
  38492. sizeName = "width";
  38493. shortSizeName = "w";
  38494. }
  38495. handleEl = ctrl.getEl('handle');
  38496. maxHandlePos = (ctrl.layoutRect()[shortSizeName] || 100) - DomUtils.getSize(handleEl)[sizeName];
  38497. styleValue = (maxHandlePos * ((value - ctrl._minValue) / (ctrl._maxValue - ctrl._minValue))) + 'px';
  38498. handleEl.style[stylePosName] = styleValue;
  38499. handleEl.style.height = ctrl.layoutRect().h + 'px';
  38500. setAriaProp(handleEl, 'valuenow', value);
  38501. setAriaProp(handleEl, 'valuetext', '' + ctrl.settings.previewFilter(value));
  38502. setAriaProp(handleEl, 'valuemin', ctrl._minValue);
  38503. setAriaProp(handleEl, 'valuemax', ctrl._maxValue);
  38504. }
  38505. return Widget.extend({
  38506. init: function(settings) {
  38507. var self = this;
  38508. if (!settings.previewFilter) {
  38509. settings.previewFilter = function(value) {
  38510. return Math.round(value * 100) / 100.0;
  38511. };
  38512. }
  38513. self._super(settings);
  38514. self.classes.add('slider');
  38515. if (settings.orientation == "v") {
  38516. self.classes.add('vertical');
  38517. }
  38518. self._minValue = settings.minValue || 0;
  38519. self._maxValue = settings.maxValue || 100;
  38520. self._initValue = self.state.get('value');
  38521. },
  38522. renderHtml: function() {
  38523. var self = this, id = self._id, prefix = self.classPrefix;
  38524. return (
  38525. '<div id="' + id + '" class="' + self.classes + '">' +
  38526. '<div id="' + id + '-handle" class="' + prefix + 'slider-handle" role="slider" tabindex="-1"></div>' +
  38527. '</div>'
  38528. );
  38529. },
  38530. reset: function() {
  38531. this.value(this._initValue).repaint();
  38532. },
  38533. postRender: function() {
  38534. var self = this, minValue, maxValue, screenCordName,
  38535. stylePosName, sizeName, shortSizeName;
  38536. function toFraction(min, max, val) {
  38537. return (val + min) / (max - min);
  38538. }
  38539. function fromFraction(min, max, val) {
  38540. return (val * (max - min)) - min;
  38541. }
  38542. function handleKeyboard(minValue, maxValue) {
  38543. function alter(delta) {
  38544. var value;
  38545. value = self.value();
  38546. value = fromFraction(minValue, maxValue, toFraction(minValue, maxValue, value) + (delta * 0.05));
  38547. value = constrain(value, minValue, maxValue);
  38548. self.value(value);
  38549. self.fire('dragstart', {value: value});
  38550. self.fire('drag', {value: value});
  38551. self.fire('dragend', {value: value});
  38552. }
  38553. self.on('keydown', function(e) {
  38554. switch (e.keyCode) {
  38555. case 37:
  38556. case 38:
  38557. alter(-1);
  38558. break;
  38559. case 39:
  38560. case 40:
  38561. alter(1);
  38562. break;
  38563. }
  38564. });
  38565. }
  38566. function handleDrag(minValue, maxValue, handleEl) {
  38567. var startPos, startHandlePos, maxHandlePos, handlePos, value;
  38568. self._dragHelper = new DragHelper(self._id, {
  38569. handle: self._id + "-handle",
  38570. start: function(e) {
  38571. startPos = e[screenCordName];
  38572. startHandlePos = parseInt(self.getEl('handle').style[stylePosName], 10);
  38573. maxHandlePos = (self.layoutRect()[shortSizeName] || 100) - DomUtils.getSize(handleEl)[sizeName];
  38574. self.fire('dragstart', {value: value});
  38575. },
  38576. drag: function(e) {
  38577. var delta = e[screenCordName] - startPos;
  38578. handlePos = constrain(startHandlePos + delta, 0, maxHandlePos);
  38579. handleEl.style[stylePosName] = handlePos + 'px';
  38580. value = minValue + (handlePos / maxHandlePos) * (maxValue - minValue);
  38581. self.value(value);
  38582. self.tooltip().text('' + self.settings.previewFilter(value)).show().moveRel(handleEl, 'bc tc');
  38583. self.fire('drag', {value: value});
  38584. },
  38585. stop: function() {
  38586. self.tooltip().hide();
  38587. self.fire('dragend', {value: value});
  38588. }
  38589. });
  38590. }
  38591. minValue = self._minValue;
  38592. maxValue = self._maxValue;
  38593. if (self.settings.orientation == "v") {
  38594. screenCordName = "screenY";
  38595. stylePosName = "top";
  38596. sizeName = "height";
  38597. shortSizeName = "h";
  38598. } else {
  38599. screenCordName = "screenX";
  38600. stylePosName = "left";
  38601. sizeName = "width";
  38602. shortSizeName = "w";
  38603. }
  38604. self._super();
  38605. handleKeyboard(minValue, maxValue, self.getEl('handle'));
  38606. handleDrag(minValue, maxValue, self.getEl('handle'));
  38607. },
  38608. repaint: function() {
  38609. this._super();
  38610. updateSliderHandle(this, this.value());
  38611. },
  38612. bindStates: function() {
  38613. var self = this;
  38614. self.state.on('change:value', function(e) {
  38615. updateSliderHandle(self, e.value);
  38616. });
  38617. return self._super();
  38618. }
  38619. });
  38620. });
  38621. // Included from: js/tinymce/classes/ui/Spacer.js
  38622. /**
  38623. * Spacer.js
  38624. *
  38625. * Released under LGPL License.
  38626. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  38627. *
  38628. * License: http://www.tinymce.com/license
  38629. * Contributing: http://www.tinymce.com/contributing
  38630. */
  38631. /**
  38632. * Creates a spacer. This control is used in flex layouts for example.
  38633. *
  38634. * @-x-less Spacer.less
  38635. * @class tinymce.ui.Spacer
  38636. * @extends tinymce.ui.Widget
  38637. */
  38638. define("tinymce/ui/Spacer", [
  38639. "tinymce/ui/Widget"
  38640. ], function(Widget) {
  38641. "use strict";
  38642. return Widget.extend({
  38643. /**
  38644. * Renders the control as a HTML string.
  38645. *
  38646. * @method renderHtml
  38647. * @return {String} HTML representing the control.
  38648. */
  38649. renderHtml: function() {
  38650. var self = this;
  38651. self.classes.add('spacer');
  38652. self.canFocus = false;
  38653. return '<div id="' + self._id + '" class="' + self.classes + '"></div>';
  38654. }
  38655. });
  38656. });
  38657. // Included from: js/tinymce/classes/ui/SplitButton.js
  38658. /**
  38659. * SplitButton.js
  38660. *
  38661. * Released under LGPL License.
  38662. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  38663. *
  38664. * License: http://www.tinymce.com/license
  38665. * Contributing: http://www.tinymce.com/contributing
  38666. */
  38667. /**
  38668. * Creates a split button.
  38669. *
  38670. * @-x-less SplitButton.less
  38671. * @class tinymce.ui.SplitButton
  38672. * @extends tinymce.ui.Button
  38673. */
  38674. define("tinymce/ui/SplitButton", [
  38675. "tinymce/ui/MenuButton",
  38676. "tinymce/ui/DomUtils",
  38677. "tinymce/dom/DomQuery"
  38678. ], function(MenuButton, DomUtils, $) {
  38679. return MenuButton.extend({
  38680. Defaults: {
  38681. classes: "widget btn splitbtn",
  38682. role: "button"
  38683. },
  38684. /**
  38685. * Repaints the control after a layout operation.
  38686. *
  38687. * @method repaint
  38688. */
  38689. repaint: function() {
  38690. var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm;
  38691. self._super();
  38692. mainButtonElm = elm.firstChild;
  38693. menuButtonElm = elm.lastChild;
  38694. $(mainButtonElm).css({
  38695. width: rect.w - DomUtils.getSize(menuButtonElm).width,
  38696. height: rect.h - 2
  38697. });
  38698. $(menuButtonElm).css({
  38699. height: rect.h - 2
  38700. });
  38701. return self;
  38702. },
  38703. /**
  38704. * Sets the active menu state.
  38705. *
  38706. * @private
  38707. */
  38708. activeMenu: function(state) {
  38709. var self = this;
  38710. $(self.getEl().lastChild).toggleClass(self.classPrefix + 'active', state);
  38711. },
  38712. /**
  38713. * Renders the control as a HTML string.
  38714. *
  38715. * @method renderHtml
  38716. * @return {String} HTML representing the control.
  38717. */
  38718. renderHtml: function() {
  38719. var self = this, id = self._id, prefix = self.classPrefix, image;
  38720. var icon = self.state.get('icon'), text = self.state.get('text'),
  38721. textHtml = '';
  38722. image = self.settings.image;
  38723. if (image) {
  38724. icon = 'none';
  38725. // Support for [high dpi, low dpi] image sources
  38726. if (typeof image != "string") {
  38727. image = window.getSelection ? image[0] : image[1];
  38728. }
  38729. image = ' style="background-image: url(\'' + image + '\')"';
  38730. } else {
  38731. image = '';
  38732. }
  38733. icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
  38734. if (text) {
  38735. self.classes.add('btn-has-text');
  38736. textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>';
  38737. }
  38738. return (
  38739. '<div id="' + id + '" class="' + self.classes + '" role="button" tabindex="-1">' +
  38740. '<button type="button" hidefocus="1" tabindex="-1">' +
  38741. (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  38742. textHtml +
  38743. '</button>' +
  38744. '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
  38745. //(icon ? '<i class="' + icon + '"></i>' : '') +
  38746. (self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') +
  38747. ' <i class="' + prefix + 'caret"></i>' +
  38748. '</button>' +
  38749. '</div>'
  38750. );
  38751. },
  38752. /**
  38753. * Called after the control has been rendered.
  38754. *
  38755. * @method postRender
  38756. */
  38757. postRender: function() {
  38758. var self = this, onClickHandler = self.settings.onclick;
  38759. self.on('click', function(e) {
  38760. var node = e.target;
  38761. if (e.control == this) {
  38762. // Find clicks that is on the main button
  38763. while (node) {
  38764. if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
  38765. e.stopImmediatePropagation();
  38766. if (onClickHandler) {
  38767. onClickHandler.call(this, e);
  38768. }
  38769. return;
  38770. }
  38771. node = node.parentNode;
  38772. }
  38773. }
  38774. });
  38775. delete self.settings.onclick;
  38776. return self._super();
  38777. }
  38778. });
  38779. });
  38780. // Included from: js/tinymce/classes/ui/StackLayout.js
  38781. /**
  38782. * StackLayout.js
  38783. *
  38784. * Released under LGPL License.
  38785. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  38786. *
  38787. * License: http://www.tinymce.com/license
  38788. * Contributing: http://www.tinymce.com/contributing
  38789. */
  38790. /**
  38791. * This layout uses the browsers layout when the items are blocks.
  38792. *
  38793. * @-x-less StackLayout.less
  38794. * @class tinymce.ui.StackLayout
  38795. * @extends tinymce.ui.FlowLayout
  38796. */
  38797. define("tinymce/ui/StackLayout", [
  38798. "tinymce/ui/FlowLayout"
  38799. ], function(FlowLayout) {
  38800. "use strict";
  38801. return FlowLayout.extend({
  38802. Defaults: {
  38803. containerClass: 'stack-layout',
  38804. controlClass: 'stack-layout-item',
  38805. endClass: 'break'
  38806. },
  38807. isNative: function() {
  38808. return true;
  38809. }
  38810. });
  38811. });
  38812. // Included from: js/tinymce/classes/ui/TabPanel.js
  38813. /**
  38814. * TabPanel.js
  38815. *
  38816. * Released under LGPL License.
  38817. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  38818. *
  38819. * License: http://www.tinymce.com/license
  38820. * Contributing: http://www.tinymce.com/contributing
  38821. */
  38822. /**
  38823. * Creates a tab panel control.
  38824. *
  38825. * @-x-less TabPanel.less
  38826. * @class tinymce.ui.TabPanel
  38827. * @extends tinymce.ui.Panel
  38828. *
  38829. * @setting {Number} activeTab Active tab index.
  38830. */
  38831. define("tinymce/ui/TabPanel", [
  38832. "tinymce/ui/Panel",
  38833. "tinymce/dom/DomQuery",
  38834. "tinymce/ui/DomUtils"
  38835. ], function(Panel, $, DomUtils) {
  38836. "use strict";
  38837. return Panel.extend({
  38838. Defaults: {
  38839. layout: 'absolute',
  38840. defaults: {
  38841. type: 'panel'
  38842. }
  38843. },
  38844. /**
  38845. * Activates the specified tab by index.
  38846. *
  38847. * @method activateTab
  38848. * @param {Number} idx Index of the tab to activate.
  38849. */
  38850. activateTab: function(idx) {
  38851. var activeTabElm;
  38852. if (this.activeTabId) {
  38853. activeTabElm = this.getEl(this.activeTabId);
  38854. $(activeTabElm).removeClass(this.classPrefix + 'active');
  38855. activeTabElm.setAttribute('aria-selected', "false");
  38856. }
  38857. this.activeTabId = 't' + idx;
  38858. activeTabElm = this.getEl('t' + idx);
  38859. activeTabElm.setAttribute('aria-selected', "true");
  38860. $(activeTabElm).addClass(this.classPrefix + 'active');
  38861. this.items()[idx].show().fire('showtab');
  38862. this.reflow();
  38863. this.items().each(function(item, i) {
  38864. if (idx != i) {
  38865. item.hide();
  38866. }
  38867. });
  38868. },
  38869. /**
  38870. * Renders the control as a HTML string.
  38871. *
  38872. * @method renderHtml
  38873. * @return {String} HTML representing the control.
  38874. */
  38875. renderHtml: function() {
  38876. var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix;
  38877. self.preRender();
  38878. layout.preRender(self);
  38879. self.items().each(function(ctrl, i) {
  38880. var id = self._id + '-t' + i;
  38881. ctrl.aria('role', 'tabpanel');
  38882. ctrl.aria('labelledby', id);
  38883. tabsHtml += (
  38884. '<div id="' + id + '" class="' + prefix + 'tab" ' +
  38885. 'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
  38886. self.encode(ctrl.settings.title) +
  38887. '</div>'
  38888. );
  38889. });
  38890. return (
  38891. '<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' +
  38892. '<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
  38893. tabsHtml +
  38894. '</div>' +
  38895. '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' +
  38896. layout.renderHtml(self) +
  38897. '</div>' +
  38898. '</div>'
  38899. );
  38900. },
  38901. /**
  38902. * Called after the control has been rendered.
  38903. *
  38904. * @method postRender
  38905. */
  38906. postRender: function() {
  38907. var self = this;
  38908. self._super();
  38909. self.settings.activeTab = self.settings.activeTab || 0;
  38910. self.activateTab(self.settings.activeTab);
  38911. this.on('click', function(e) {
  38912. var targetParent = e.target.parentNode;
  38913. if (e.target.parentNode.id == self._id + '-head') {
  38914. var i = targetParent.childNodes.length;
  38915. while (i--) {
  38916. if (targetParent.childNodes[i] == e.target) {
  38917. self.activateTab(i);
  38918. }
  38919. }
  38920. }
  38921. });
  38922. },
  38923. /**
  38924. * Initializes the current controls layout rect.
  38925. * This will be executed by the layout managers to determine the
  38926. * default minWidth/minHeight etc.
  38927. *
  38928. * @method initLayoutRect
  38929. * @return {Object} Layout rect instance.
  38930. */
  38931. initLayoutRect: function() {
  38932. var self = this, rect, minW, minH;
  38933. minW = DomUtils.getSize(self.getEl('head')).width;
  38934. minW = minW < 0 ? 0 : minW;
  38935. minH = 0;
  38936. self.items().each(function(item) {
  38937. minW = Math.max(minW, item.layoutRect().minW);
  38938. minH = Math.max(minH, item.layoutRect().minH);
  38939. });
  38940. self.items().each(function(ctrl) {
  38941. ctrl.settings.x = 0;
  38942. ctrl.settings.y = 0;
  38943. ctrl.settings.w = minW;
  38944. ctrl.settings.h = minH;
  38945. ctrl.layoutRect({
  38946. x: 0,
  38947. y: 0,
  38948. w: minW,
  38949. h: minH
  38950. });
  38951. });
  38952. var headH = DomUtils.getSize(self.getEl('head')).height;
  38953. self.settings.minWidth = minW;
  38954. self.settings.minHeight = minH + headH;
  38955. rect = self._super();
  38956. rect.deltaH += headH;
  38957. rect.innerH = rect.h - rect.deltaH;
  38958. return rect;
  38959. }
  38960. });
  38961. });
  38962. // Included from: js/tinymce/classes/ui/TextBox.js
  38963. /**
  38964. * TextBox.js
  38965. *
  38966. * Released under LGPL License.
  38967. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  38968. *
  38969. * License: http://www.tinymce.com/license
  38970. * Contributing: http://www.tinymce.com/contributing
  38971. */
  38972. /**
  38973. * Creates a new textbox.
  38974. *
  38975. * @-x-less TextBox.less
  38976. * @class tinymce.ui.TextBox
  38977. * @extends tinymce.ui.Widget
  38978. */
  38979. define("tinymce/ui/TextBox", [
  38980. "tinymce/ui/Widget",
  38981. "tinymce/util/Tools",
  38982. "tinymce/ui/DomUtils"
  38983. ], function(Widget, Tools, DomUtils) {
  38984. return Widget.extend({
  38985. /**
  38986. * Constructs a instance with the specified settings.
  38987. *
  38988. * @constructor
  38989. * @param {Object} settings Name/value object with settings.
  38990. * @setting {Boolean} multiline True if the textbox is a multiline control.
  38991. * @setting {Number} maxLength Max length for the textbox.
  38992. * @setting {Number} size Size of the textbox in characters.
  38993. */
  38994. init: function(settings) {
  38995. var self = this;
  38996. self._super(settings);
  38997. self.classes.add('textbox');
  38998. if (settings.multiline) {
  38999. self.classes.add('multiline');
  39000. } else {
  39001. self.on('keydown', function(e) {
  39002. var rootControl;
  39003. if (e.keyCode == 13) {
  39004. e.preventDefault();
  39005. // Find root control that we can do toJSON on
  39006. self.parents().reverse().each(function(ctrl) {
  39007. if (ctrl.toJSON) {
  39008. rootControl = ctrl;
  39009. return false;
  39010. }
  39011. });
  39012. // Fire event on current text box with the serialized data of the whole form
  39013. self.fire('submit', {data: rootControl.toJSON()});
  39014. }
  39015. });
  39016. self.on('keyup', function(e) {
  39017. self.state.set('value', e.target.value);
  39018. });
  39019. }
  39020. },
  39021. /**
  39022. * Repaints the control after a layout operation.
  39023. *
  39024. * @method repaint
  39025. */
  39026. repaint: function() {
  39027. var self = this, style, rect, borderBox, borderW, borderH = 0, lastRepaintRect;
  39028. style = self.getEl().style;
  39029. rect = self._layoutRect;
  39030. lastRepaintRect = self._lastRepaintRect || {};
  39031. // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
  39032. var doc = document;
  39033. if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
  39034. style.lineHeight = (rect.h - borderH) + 'px';
  39035. }
  39036. borderBox = self.borderBox;
  39037. borderW = borderBox.left + borderBox.right + 8;
  39038. borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0);
  39039. if (rect.x !== lastRepaintRect.x) {
  39040. style.left = rect.x + 'px';
  39041. lastRepaintRect.x = rect.x;
  39042. }
  39043. if (rect.y !== lastRepaintRect.y) {
  39044. style.top = rect.y + 'px';
  39045. lastRepaintRect.y = rect.y;
  39046. }
  39047. if (rect.w !== lastRepaintRect.w) {
  39048. style.width = (rect.w - borderW) + 'px';
  39049. lastRepaintRect.w = rect.w;
  39050. }
  39051. if (rect.h !== lastRepaintRect.h) {
  39052. style.height = (rect.h - borderH) + 'px';
  39053. lastRepaintRect.h = rect.h;
  39054. }
  39055. self._lastRepaintRect = lastRepaintRect;
  39056. self.fire('repaint', {}, false);
  39057. return self;
  39058. },
  39059. /**
  39060. * Renders the control as a HTML string.
  39061. *
  39062. * @method renderHtml
  39063. * @return {String} HTML representing the control.
  39064. */
  39065. renderHtml: function() {
  39066. var self = this, settings = self.settings, attrs, elm;
  39067. attrs = {
  39068. id: self._id,
  39069. hidefocus: '1'
  39070. };
  39071. Tools.each([
  39072. 'rows', 'spellcheck', 'maxLength', 'size', 'readonly', 'min',
  39073. 'max', 'step', 'list', 'pattern', 'placeholder', 'required', 'multiple'
  39074. ], function(name) {
  39075. attrs[name] = settings[name];
  39076. });
  39077. if (self.disabled()) {
  39078. attrs.disabled = 'disabled';
  39079. }
  39080. if (settings.subtype) {
  39081. attrs.type = settings.subtype;
  39082. }
  39083. elm = DomUtils.create(settings.multiline ? 'textarea' : 'input', attrs);
  39084. elm.value = self.state.get('value');
  39085. elm.className = self.classes;
  39086. return elm.outerHTML;
  39087. },
  39088. value: function(value) {
  39089. if (arguments.length) {
  39090. this.state.set('value', value);
  39091. return this;
  39092. }
  39093. // Make sure the real state is in sync
  39094. if (this.state.get('rendered')) {
  39095. this.state.set('value', this.getEl().value);
  39096. }
  39097. return this.state.get('value');
  39098. },
  39099. /**
  39100. * Called after the control has been rendered.
  39101. *
  39102. * @method postRender
  39103. */
  39104. postRender: function() {
  39105. var self = this;
  39106. self.getEl().value = self.state.get('value');
  39107. self._super();
  39108. self.$el.on('change', function(e) {
  39109. self.state.set('value', e.target.value);
  39110. self.fire('change', e);
  39111. });
  39112. },
  39113. bindStates: function() {
  39114. var self = this;
  39115. self.state.on('change:value', function(e) {
  39116. if (self.getEl().value != e.value) {
  39117. self.getEl().value = e.value;
  39118. }
  39119. });
  39120. self.state.on('change:disabled', function(e) {
  39121. self.getEl().disabled = e.value;
  39122. });
  39123. return self._super();
  39124. },
  39125. remove: function() {
  39126. this.$el.off();
  39127. this._super();
  39128. }
  39129. });
  39130. });
  39131. // Included from: js/tinymce/classes/Register.js
  39132. /**
  39133. * Register.js
  39134. *
  39135. * Released under LGPL License.
  39136. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  39137. *
  39138. * License: http://www.tinymce.com/license
  39139. * Contributing: http://www.tinymce.com/contributing
  39140. */
  39141. /**
  39142. * This registers tinymce in common module loaders.
  39143. *
  39144. * @private
  39145. * @class tinymce.Register
  39146. */
  39147. define("tinymce/Register", [
  39148. ], function() {
  39149. /*eslint consistent-this: 0 */
  39150. var context = this || window;
  39151. var tinymce = function() {
  39152. return context.tinymce;
  39153. };
  39154. if (typeof context.define === "function") {
  39155. // Bolt
  39156. if (!context.define.amd) {
  39157. context.define("ephox/tinymce", [], tinymce);
  39158. }
  39159. }
  39160. return {};
  39161. });
  39162. expose(["tinymce/geom/Rect","tinymce/util/Promise","tinymce/util/Delay","tinymce/Env","tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/util/Tools","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/html/Entities","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/dom/RangeUtils","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/BookmarkManager","tinymce/dom/Selection","tinymce/Formatter","tinymce/UndoManager","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/util/EventDispatcher","tinymce/util/Observable","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/ReflowQueue","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Progress","tinymce/ui/Notification","tinymce/NotificationManager","tinymce/EditorObservable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/ComboBox","tinymce/ui/ColorBox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/util/Color","tinymce/ui/ColorPicker","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/InfoBox","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/MenuItem","tinymce/ui/Throbber","tinymce/ui/Menu","tinymce/ui/ListBox","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/SelectBox","tinymce/ui/Slider","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox"]);
  39163. })(this);